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