terminal.cc revision 12239:ae1686aaebc5
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/misc.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    int p1, p2;
157    p2 = name().rfind('.') - 1;
158    p1 = name().rfind('.', p2);
159    ccprintf(cerr, "Listening for %s connection on port %d\n",
160            name().substr(p1+1,p2-p1), port);
161
162    listenEvent = new ListenEvent(this, listener.getfd(), POLLIN);
163    pollQueue.schedule(listenEvent);
164}
165
166void
167Terminal::accept()
168{
169    if (!listener.islistening())
170        panic("%s: cannot accept a connection if not listening!", name());
171
172    int fd = listener.accept(true);
173    if (data_fd != -1) {
174        char message[] = "terminal already attached!\n";
175        atomic_write(fd, message, sizeof(message));
176        ::close(fd);
177        return;
178    }
179
180    data_fd = fd;
181    dataEvent = new DataEvent(this, data_fd, POLLIN);
182    pollQueue.schedule(dataEvent);
183
184    stringstream stream;
185    ccprintf(stream, "==== m5 slave terminal: Terminal %d ====", number);
186
187    // we need an actual carriage return followed by a newline for the
188    // terminal
189    stream << "\r\n";
190
191    write((const uint8_t *)stream.str().c_str(), stream.str().size());
192
193    DPRINTFN("attach terminal %d\n", number);
194    char buf[1024];
195    for (size_t i = 0; i < txbuf.size(); i += sizeof(buf)) {
196        const size_t chunk_len(std::min(txbuf.size() - i, sizeof(buf)));
197        txbuf.peek(buf, i, chunk_len);
198        write((const uint8_t *)buf, chunk_len);
199    }
200}
201
202void
203Terminal::detach()
204{
205    if (data_fd != -1) {
206        ::close(data_fd);
207        data_fd = -1;
208    }
209
210    pollQueue.remove(dataEvent);
211    delete dataEvent;
212    dataEvent = NULL;
213
214    DPRINTFN("detach terminal %d\n", number);
215}
216
217void
218Terminal::data()
219{
220    uint8_t buf[1024];
221    int len;
222
223    len = read(buf, sizeof(buf));
224    if (len) {
225        rxbuf.write((char *)buf, len);
226        notifyInterface();
227    }
228}
229
230size_t
231Terminal::read(uint8_t *buf, size_t len)
232{
233    if (data_fd < 0)
234        panic("Terminal not properly attached.\n");
235
236    ssize_t ret;
237    do {
238      ret = ::read(data_fd, buf, len);
239    } while (ret == -1 && errno == EINTR);
240
241
242    if (ret < 0)
243        DPRINTFN("Read failed.\n");
244
245    if (ret <= 0) {
246        detach();
247        return 0;
248    }
249
250    return ret;
251}
252
253// Terminal output.
254size_t
255Terminal::write(const uint8_t *buf, size_t len)
256{
257    if (data_fd < 0)
258        panic("Terminal not properly attached.\n");
259
260    ssize_t ret = atomic_write(data_fd, buf, len);
261    if (ret < len)
262        detach();
263
264    return ret;
265}
266
267#define MORE_PENDING (ULL(1) << 61)
268#define RECEIVE_SUCCESS (ULL(0) << 62)
269#define RECEIVE_NONE (ULL(2) << 62)
270#define RECEIVE_ERROR (ULL(3) << 62)
271
272uint8_t
273Terminal::readData()
274{
275    uint8_t c;
276
277    assert(!rxbuf.empty());
278    rxbuf.read((char *)&c, 1);
279
280    DPRINTF(TerminalVerbose, "in: \'%c\' %#02x more: %d\n",
281            isprint(c) ? c : ' ', c, !rxbuf.empty());
282
283    return c;
284}
285
286uint64_t
287Terminal::console_in()
288{
289    uint64_t value;
290
291    if (dataAvailable()) {
292        value = RECEIVE_SUCCESS | readData();
293        if (!rxbuf.empty())
294            value  |= MORE_PENDING;
295    } else {
296        value = RECEIVE_NONE;
297    }
298
299    DPRINTF(TerminalVerbose, "console_in: return: %#x\n", value);
300
301    return value;
302}
303
304void
305Terminal::writeData(uint8_t c)
306{
307#if TRACING_ON == 1
308    if (DTRACE(Terminal)) {
309        static char last = '\0';
310
311        if ((c != '\n' && c != '\r') || (last != '\n' && last != '\r')) {
312            if (c == '\n' || c == '\r') {
313                int size = linebuf.size();
314                char *buffer = new char[size + 1];
315                linebuf.read(buffer, size);
316                buffer[size] = '\0';
317                DPRINTF(Terminal, "%s\n", buffer);
318                delete [] buffer;
319            } else {
320                linebuf.write(&c, 1);
321            }
322        }
323
324        last = c;
325    }
326#endif
327
328    txbuf.write(&c, 1);
329
330    if (data_fd >= 0)
331        write(c);
332
333    if (outfile)
334        outfile->stream()->put((char)c);
335
336    DPRINTF(TerminalVerbose, "out: \'%c\' %#02x\n",
337            isprint(c) ? c : ' ', (int)c);
338
339}
340
341Terminal *
342TerminalParams::create()
343{
344    return new Terminal(this);
345}
346