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