terminal.cc (12693:4db8d6442b44) terminal.cc (14249:e6d5e7d248c5)
1/*
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 *
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),
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),
112 outfile(p->output ? simout.findOrCreate(p->name) : NULL)
124 outfile(terminalDump(p))
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
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::stdout:
155 return simout.findOrCreate("stdout");
156 case Enums::TerminalDump::stderr:
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}
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}
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}