1/*
2 * Copyright (c) 2019 ARM Limited
3 * All rights reserved
4 *
5 * The license below extends only to copyright in the software and shall
6 * not be construed as granting a license to any other intellectual
7 * property including but not limited to intellectual property relating
8 * to a hardware implementation of the functionality of the software
9 * licensed hereunder.  You may use the software subject to the license
10 * terms below provided that you ensure that this notice is replicated
11 * unmodified and in its entirety in all distributions of the software,
12 * modified or unmodified, in source code or in binary form.
13 *
14 * Copyright (c) 2001-2005 The Regents of The University of Michigan
15 * All rights reserved.
16 *
17 * Redistribution and use in source and binary forms, with or without
18 * modification, are permitted provided that the following conditions are
19 * met: redistributions of source code must retain the above copyright
20 * notice, this list of conditions and the following disclaimer;
21 * redistributions in binary form must reproduce the above copyright
22 * notice, this list of conditions and the following disclaimer in the
23 * documentation and/or other materials provided with the distribution;
24 * neither the name of the copyright holders nor the names of its
25 * contributors may be used to endorse or promote products derived from
26 * this software without specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 *
40 * Authors: Nathan Binkert
41 *          Ali Saidi
42 */
43
44/* @file
45 * Implements the user interface to a serial terminal
46 */
47
48#include <sys/ioctl.h>
49
50#if defined(__FreeBSD__)
51#include <termios.h>
52
53#else
54#include <sys/termios.h>
55
56#endif
57#include "dev/serial/terminal.hh"
58
59#include <poll.h>
60#include <unistd.h>
61
62#include <cctype>
63#include <cerrno>
64#include <fstream>
65#include <iostream>
66#include <sstream>
67#include <string>
68
69#include "base/atomicio.hh"
70#include "base/logging.hh"
71#include "base/output.hh"
72#include "base/socket.hh"
73#include "base/trace.hh"
74#include "debug/Terminal.hh"
75#include "debug/TerminalVerbose.hh"
76#include "dev/platform.hh"
77#include "dev/serial/uart.hh"
78
79using namespace std;
80
81
82/*
83 * Poll event for the listen socket
84 */
85Terminal::ListenEvent::ListenEvent(Terminal *t, int fd, int e)
86    : PollEvent(fd, e), term(t)
87{
88}
89
90void
91Terminal::ListenEvent::process(int revent)
92{
93    term->accept();
94}
95
96/*
97 * Poll event for the data socket
98 */
99Terminal::DataEvent::DataEvent(Terminal *t, int fd, int e)
100    : PollEvent(fd, e), term(t)
101{
102}
103
104void
105Terminal::DataEvent::process(int revent)
106{
107    // As a consequence of being called from the PollQueue, we might
108    // have been called from a different thread. Migrate to "our"
109    // thread.
110    EventQueue::ScopedMigration migrate(term->eventQueue());
111
112    if (revent & POLLIN)
113        term->data();
114    else if (revent & POLLNVAL)
115        term->detach();
116}
117
118/*
119 * Terminal code
120 */
121Terminal::Terminal(const Params *p)
122    : SerialDevice(p), listenEvent(NULL), dataEvent(NULL),
123      number(p->number), data_fd(-1), txbuf(16384), rxbuf(16384),
124      outfile(terminalDump(p))
125#if TRACING_ON == 1
126      , linebuf(16384)
127#endif
128{
129    if (outfile)
130        outfile->stream()->setf(ios::unitbuf);
131
132    if (p->port)
133        listen(p->port);
134}
135
136Terminal::~Terminal()
137{
138    if (data_fd != -1)
139        ::close(data_fd);
140
141    if (listenEvent)
142        delete listenEvent;
143
144    if (dataEvent)
145        delete dataEvent;
146}
147
148OutputStream *
149Terminal::terminalDump(const TerminalParams* p)
150{
151    switch (p->outfile) {
152      case Enums::TerminalDump::none:
153        return nullptr;
154      case Enums::TerminalDump::stdoutput:
155        return simout.findOrCreate("stdout");
156      case Enums::TerminalDump::stderror:
157        return simout.findOrCreate("stderr");
158      case Enums::TerminalDump::file:
159        return simout.findOrCreate(p->name);
160      default:
161        panic("Invalid option\n");
162    }
163}
164
165///////////////////////////////////////////////////////////////////////
166// socket creation and terminal attach
167//
168
169void
170Terminal::listen(int port)
171{
172    if (ListenSocket::allDisabled()) {
173        warn_once("Sockets disabled, not accepting terminal connections");
174        return;
175    }
176
177    while (!listener.listen(port, true)) {
178        DPRINTF(Terminal,
179                ": can't bind address terminal port %d inuse PID %d\n",
180                port, getpid());
181        port++;
182    }
183
184    ccprintf(cerr, "%s: Listening for connections on port %d\n",
185             name(), port);
186
187    listenEvent = new ListenEvent(this, listener.getfd(), POLLIN);
188    pollQueue.schedule(listenEvent);
189}
190
191void
192Terminal::accept()
193{
194    if (!listener.islistening())
195        panic("%s: cannot accept a connection if not listening!", name());
196
197    int fd = listener.accept(true);
198    if (data_fd != -1) {
199        char message[] = "terminal already attached!\n";
200        atomic_write(fd, message, sizeof(message));
201        ::close(fd);
202        return;
203    }
204
205    data_fd = fd;
206    dataEvent = new DataEvent(this, data_fd, POLLIN);
207    pollQueue.schedule(dataEvent);
208
209    stringstream stream;
210    ccprintf(stream, "==== m5 slave terminal: Terminal %d ====", number);
211
212    // we need an actual carriage return followed by a newline for the
213    // terminal
214    stream << "\r\n";
215
216    write((const uint8_t *)stream.str().c_str(), stream.str().size());
217
218    DPRINTFN("attach terminal %d\n", number);
219    char buf[1024];
220    for (size_t i = 0; i < txbuf.size(); i += sizeof(buf)) {
221        const size_t chunk_len(std::min(txbuf.size() - i, sizeof(buf)));
222        txbuf.peek(buf, i, chunk_len);
223        write((const uint8_t *)buf, chunk_len);
224    }
225}
226
227void
228Terminal::detach()
229{
230    if (data_fd != -1) {
231        ::close(data_fd);
232        data_fd = -1;
233    }
234
235    pollQueue.remove(dataEvent);
236    delete dataEvent;
237    dataEvent = NULL;
238
239    DPRINTFN("detach terminal %d\n", number);
240}
241
242void
243Terminal::data()
244{
245    uint8_t buf[1024];
246    int len;
247
248    len = read(buf, sizeof(buf));
249    if (len) {
250        rxbuf.write((char *)buf, len);
251        notifyInterface();
252    }
253}
254
255size_t
256Terminal::read(uint8_t *buf, size_t len)
257{
258    if (data_fd < 0)
259        panic("Terminal not properly attached.\n");
260
261    ssize_t ret;
262    do {
263      ret = ::read(data_fd, buf, len);
264    } while (ret == -1 && errno == EINTR);
265
266
267    if (ret < 0)
268        DPRINTFN("Read failed.\n");
269
270    if (ret <= 0) {
271        detach();
272        return 0;
273    }
274
275    return ret;
276}
277
278// Terminal output.
279size_t
280Terminal::write(const uint8_t *buf, size_t len)
281{
282    if (data_fd < 0)
283        panic("Terminal not properly attached.\n");
284
285    ssize_t ret = atomic_write(data_fd, buf, len);
286    if (ret < len)
287        detach();
288
289    return ret;
290}
291
292#define MORE_PENDING (ULL(1) << 61)
293#define RECEIVE_SUCCESS (ULL(0) << 62)
294#define RECEIVE_NONE (ULL(2) << 62)
295#define RECEIVE_ERROR (ULL(3) << 62)
296
297uint8_t
298Terminal::readData()
299{
300    uint8_t c;
301
302    assert(!rxbuf.empty());
303    rxbuf.read((char *)&c, 1);
304
305    DPRINTF(TerminalVerbose, "in: \'%c\' %#02x more: %d\n",
306            isprint(c) ? c : ' ', c, !rxbuf.empty());
307
308    return c;
309}
310
311uint64_t
312Terminal::console_in()
313{
314    uint64_t value;
315
316    if (dataAvailable()) {
317        value = RECEIVE_SUCCESS | readData();
318        if (!rxbuf.empty())
319            value  |= MORE_PENDING;
320    } else {
321        value = RECEIVE_NONE;
322    }
323
324    DPRINTF(TerminalVerbose, "console_in: return: %#x\n", value);
325
326    return value;
327}
328
329void
330Terminal::writeData(uint8_t c)
331{
332#if TRACING_ON == 1
333    if (DTRACE(Terminal)) {
334        static char last = '\0';
335
336        if ((c != '\n' && c != '\r') || (last != '\n' && last != '\r')) {
337            if (c == '\n' || c == '\r') {
338                int size = linebuf.size();
339                char *buffer = new char[size + 1];
340                linebuf.read(buffer, size);
341                buffer[size] = '\0';
342                DPRINTF(Terminal, "%s\n", buffer);
343                delete [] buffer;
344            } else {
345                linebuf.write(&c, 1);
346            }
347        }
348
349        last = c;
350    }
351#endif
352
353    txbuf.write(&c, 1);
354
355    if (data_fd >= 0)
356        write(c);
357
358    if (outfile)
359        outfile->stream()->put((char)c);
360
361    DPRINTF(TerminalVerbose, "out: \'%c\' %#02x\n",
362            isprint(c) ? c : ' ', (int)c);
363
364}
365
366Terminal *
367TerminalParams::create()
368{
369    return new Terminal(this);
370}
371