misc: Replaced master/slave terminology
[gem5.git] / src / dev / serial / terminal.cc
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
41 /* @file
42 * Implements the user interface to a serial terminal
43 */
44
45 #include <sys/ioctl.h>
46
47 #if defined(__FreeBSD__)
48 #include <termios.h>
49
50 #else
51 #include <sys/termios.h>
52
53 #endif
54 #include "dev/serial/terminal.hh"
55
56 #include <poll.h>
57 #include <unistd.h>
58
59 #include <cctype>
60 #include <cerrno>
61 #include <fstream>
62 #include <iostream>
63 #include <sstream>
64 #include <string>
65
66 #include "base/atomicio.hh"
67 #include "base/logging.hh"
68 #include "base/output.hh"
69 #include "base/socket.hh"
70 #include "base/trace.hh"
71 #include "debug/Terminal.hh"
72 #include "debug/TerminalVerbose.hh"
73 #include "dev/platform.hh"
74 #include "dev/serial/uart.hh"
75
76 using namespace std;
77
78
79 /*
80 * Poll event for the listen socket
81 */
82 Terminal::ListenEvent::ListenEvent(Terminal *t, int fd, int e)
83 : PollEvent(fd, e), term(t)
84 {
85 }
86
87 void
88 Terminal::ListenEvent::process(int revent)
89 {
90 term->accept();
91 }
92
93 /*
94 * Poll event for the data socket
95 */
96 Terminal::DataEvent::DataEvent(Terminal *t, int fd, int e)
97 : PollEvent(fd, e), term(t)
98 {
99 }
100
101 void
102 Terminal::DataEvent::process(int revent)
103 {
104 // As a consequence of being called from the PollQueue, we might
105 // have been called from a different thread. Migrate to "our"
106 // thread.
107 EventQueue::ScopedMigration migrate(term->eventQueue());
108
109 if (revent & POLLIN)
110 term->data();
111 else if (revent & POLLNVAL)
112 term->detach();
113 }
114
115 /*
116 * Terminal code
117 */
118 Terminal::Terminal(const Params *p)
119 : SerialDevice(p), listenEvent(NULL), dataEvent(NULL),
120 number(p->number), data_fd(-1), txbuf(16384), rxbuf(16384),
121 outfile(terminalDump(p))
122 #if TRACING_ON == 1
123 , linebuf(16384)
124 #endif
125 {
126 if (outfile)
127 outfile->stream()->setf(ios::unitbuf);
128
129 if (p->port)
130 listen(p->port);
131 }
132
133 Terminal::~Terminal()
134 {
135 if (data_fd != -1)
136 ::close(data_fd);
137
138 if (listenEvent)
139 delete listenEvent;
140
141 if (dataEvent)
142 delete dataEvent;
143 }
144
145 OutputStream *
146 Terminal::terminalDump(const TerminalParams* p)
147 {
148 switch (p->outfile) {
149 case TerminalDump::none:
150 return nullptr;
151 case TerminalDump::stdoutput:
152 return simout.findOrCreate("stdout");
153 case TerminalDump::stderror:
154 return simout.findOrCreate("stderr");
155 case TerminalDump::file:
156 return simout.findOrCreate(p->name);
157 default:
158 panic("Invalid option\n");
159 }
160 }
161
162 ///////////////////////////////////////////////////////////////////////
163 // socket creation and terminal attach
164 //
165
166 void
167 Terminal::listen(int port)
168 {
169 if (ListenSocket::allDisabled()) {
170 warn_once("Sockets disabled, not accepting terminal connections");
171 return;
172 }
173
174 while (!listener.listen(port, true)) {
175 DPRINTF(Terminal,
176 ": can't bind address terminal port %d inuse PID %d\n",
177 port, getpid());
178 port++;
179 }
180
181 ccprintf(cerr, "%s: Listening for connections on port %d\n",
182 name(), port);
183
184 listenEvent = new ListenEvent(this, listener.getfd(), POLLIN);
185 pollQueue.schedule(listenEvent);
186 }
187
188 void
189 Terminal::accept()
190 {
191 if (!listener.islistening())
192 panic("%s: cannot accept a connection if not listening!", name());
193
194 int fd = listener.accept(true);
195 if (data_fd != -1) {
196 char message[] = "terminal already attached!\n";
197 atomic_write(fd, message, sizeof(message));
198 ::close(fd);
199 return;
200 }
201
202 data_fd = fd;
203 dataEvent = new DataEvent(this, data_fd, POLLIN);
204 pollQueue.schedule(dataEvent);
205
206 stringstream stream;
207 ccprintf(stream, "==== m5 terminal: Terminal %d ====", number);
208
209 // we need an actual carriage return followed by a newline for the
210 // terminal
211 stream << "\r\n";
212
213 write((const uint8_t *)stream.str().c_str(), stream.str().size());
214
215 DPRINTFN("attach terminal %d\n", number);
216 char buf[1024];
217 for (size_t i = 0; i < txbuf.size(); i += sizeof(buf)) {
218 const size_t chunk_len(std::min(txbuf.size() - i, sizeof(buf)));
219 txbuf.peek(buf, i, chunk_len);
220 write((const uint8_t *)buf, chunk_len);
221 }
222 }
223
224 void
225 Terminal::detach()
226 {
227 if (data_fd != -1) {
228 ::close(data_fd);
229 data_fd = -1;
230 }
231
232 pollQueue.remove(dataEvent);
233 delete dataEvent;
234 dataEvent = NULL;
235
236 DPRINTFN("detach terminal %d\n", number);
237 }
238
239 void
240 Terminal::data()
241 {
242 uint8_t buf[1024];
243 int len;
244
245 len = read(buf, sizeof(buf));
246 if (len) {
247 rxbuf.write((char *)buf, len);
248 notifyInterface();
249 }
250 }
251
252 size_t
253 Terminal::read(uint8_t *buf, size_t len)
254 {
255 if (data_fd < 0)
256 panic("Terminal not properly attached.\n");
257
258 ssize_t ret;
259 do {
260 ret = ::read(data_fd, buf, len);
261 } while (ret == -1 && errno == EINTR);
262
263
264 if (ret < 0)
265 DPRINTFN("Read failed.\n");
266
267 if (ret <= 0) {
268 detach();
269 return 0;
270 }
271
272 return ret;
273 }
274
275 // Terminal output.
276 size_t
277 Terminal::write(const uint8_t *buf, size_t len)
278 {
279 if (data_fd < 0)
280 panic("Terminal not properly attached.\n");
281
282 ssize_t ret = atomic_write(data_fd, buf, len);
283 if (ret < len)
284 detach();
285
286 return ret;
287 }
288
289 #define MORE_PENDING (ULL(1) << 61)
290 #define RECEIVE_SUCCESS (ULL(0) << 62)
291 #define RECEIVE_NONE (ULL(2) << 62)
292 #define RECEIVE_ERROR (ULL(3) << 62)
293
294 uint8_t
295 Terminal::readData()
296 {
297 uint8_t c;
298
299 assert(!rxbuf.empty());
300 rxbuf.read((char *)&c, 1);
301
302 DPRINTF(TerminalVerbose, "in: \'%c\' %#02x more: %d\n",
303 isprint(c) ? c : ' ', c, !rxbuf.empty());
304
305 return c;
306 }
307
308 uint64_t
309 Terminal::console_in()
310 {
311 uint64_t value;
312
313 if (dataAvailable()) {
314 value = RECEIVE_SUCCESS | readData();
315 if (!rxbuf.empty())
316 value |= MORE_PENDING;
317 } else {
318 value = RECEIVE_NONE;
319 }
320
321 DPRINTF(TerminalVerbose, "console_in: return: %#x\n", value);
322
323 return value;
324 }
325
326 void
327 Terminal::writeData(uint8_t c)
328 {
329 #if TRACING_ON == 1
330 if (DTRACE(Terminal)) {
331 static char last = '\0';
332
333 if ((c != '\n' && c != '\r') || (last != '\n' && last != '\r')) {
334 if (c == '\n' || c == '\r') {
335 int size = linebuf.size();
336 char *buffer = new char[size + 1];
337 linebuf.read(buffer, size);
338 buffer[size] = '\0';
339 DPRINTF(Terminal, "%s\n", buffer);
340 delete [] buffer;
341 } else {
342 linebuf.write(&c, 1);
343 }
344 }
345
346 last = c;
347 }
348 #endif
349
350 txbuf.write(&c, 1);
351
352 if (data_fd >= 0)
353 write(c);
354
355 if (outfile)
356 outfile->stream()->put((char)c);
357
358 DPRINTF(TerminalVerbose, "out: \'%c\' %#02x\n",
359 isprint(c) ? c : ' ', (int)c);
360
361 }
362
363 Terminal *
364 TerminalParams::create()
365 {
366 return new Terminal(this);
367 }