/* * Copyright (c) 2019 ARM Limited * All rights reserved * * The license below extends only to copyright in the software and shall * not be construed as granting a license to any other intellectual * property including but not limited to intellectual property relating * to a hardware implementation of the functionality of the software * licensed hereunder. You may use the software subject to the license * terms below provided that you ensure that this notice is replicated * unmodified and in its entirety in all distributions of the software, * modified or unmodified, in source code or in binary form. * * Copyright (c) 2001-2005 The Regents of The University of Michigan * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer; * redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution; * neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Authors: Nathan Binkert * Ali Saidi */ /* @file * Implements the user interface to a serial terminal */ #include #if defined(__FreeBSD__) #include #else #include #endif #include "dev/serial/terminal.hh" #include #include #include #include #include #include #include #include #include "base/atomicio.hh" #include "base/logging.hh" #include "base/output.hh" #include "base/socket.hh" #include "base/trace.hh" #include "debug/Terminal.hh" #include "debug/TerminalVerbose.hh" #include "dev/platform.hh" #include "dev/serial/uart.hh" using namespace std; /* * Poll event for the listen socket */ Terminal::ListenEvent::ListenEvent(Terminal *t, int fd, int e) : PollEvent(fd, e), term(t) { } void Terminal::ListenEvent::process(int revent) { term->accept(); } /* * Poll event for the data socket */ Terminal::DataEvent::DataEvent(Terminal *t, int fd, int e) : PollEvent(fd, e), term(t) { } void Terminal::DataEvent::process(int revent) { // As a consequence of being called from the PollQueue, we might // have been called from a different thread. Migrate to "our" // thread. EventQueue::ScopedMigration migrate(term->eventQueue()); if (revent & POLLIN) term->data(); else if (revent & POLLNVAL) term->detach(); } /* * Terminal code */ Terminal::Terminal(const Params *p) : SerialDevice(p), listenEvent(NULL), dataEvent(NULL), number(p->number), data_fd(-1), txbuf(16384), rxbuf(16384), outfile(terminalDump(p)) #if TRACING_ON == 1 , linebuf(16384) #endif { if (outfile) outfile->stream()->setf(ios::unitbuf); if (p->port) listen(p->port); } Terminal::~Terminal() { if (data_fd != -1) ::close(data_fd); if (listenEvent) delete listenEvent; if (dataEvent) delete dataEvent; } OutputStream * Terminal::terminalDump(const TerminalParams* p) { switch (p->outfile) { case Enums::TerminalDump::none: return nullptr; case Enums::TerminalDump::stdoutput: return simout.findOrCreate("stdout"); case Enums::TerminalDump::stderror: return simout.findOrCreate("stderr"); case Enums::TerminalDump::file: return simout.findOrCreate(p->name); default: panic("Invalid option\n"); } } /////////////////////////////////////////////////////////////////////// // socket creation and terminal attach // void Terminal::listen(int port) { if (ListenSocket::allDisabled()) { warn_once("Sockets disabled, not accepting terminal connections"); return; } while (!listener.listen(port, true)) { DPRINTF(Terminal, ": can't bind address terminal port %d inuse PID %d\n", port, getpid()); port++; } ccprintf(cerr, "%s: Listening for connections on port %d\n", name(), port); listenEvent = new ListenEvent(this, listener.getfd(), POLLIN); pollQueue.schedule(listenEvent); } void Terminal::accept() { if (!listener.islistening()) panic("%s: cannot accept a connection if not listening!", name()); int fd = listener.accept(true); if (data_fd != -1) { char message[] = "terminal already attached!\n"; atomic_write(fd, message, sizeof(message)); ::close(fd); return; } data_fd = fd; dataEvent = new DataEvent(this, data_fd, POLLIN); pollQueue.schedule(dataEvent); stringstream stream; ccprintf(stream, "==== m5 slave terminal: Terminal %d ====", number); // we need an actual carriage return followed by a newline for the // terminal stream << "\r\n"; write((const uint8_t *)stream.str().c_str(), stream.str().size()); DPRINTFN("attach terminal %d\n", number); char buf[1024]; for (size_t i = 0; i < txbuf.size(); i += sizeof(buf)) { const size_t chunk_len(std::min(txbuf.size() - i, sizeof(buf))); txbuf.peek(buf, i, chunk_len); write((const uint8_t *)buf, chunk_len); } } void Terminal::detach() { if (data_fd != -1) { ::close(data_fd); data_fd = -1; } pollQueue.remove(dataEvent); delete dataEvent; dataEvent = NULL; DPRINTFN("detach terminal %d\n", number); } void Terminal::data() { uint8_t buf[1024]; int len; len = read(buf, sizeof(buf)); if (len) { rxbuf.write((char *)buf, len); notifyInterface(); } } size_t Terminal::read(uint8_t *buf, size_t len) { if (data_fd < 0) panic("Terminal not properly attached.\n"); ssize_t ret; do { ret = ::read(data_fd, buf, len); } while (ret == -1 && errno == EINTR); if (ret < 0) DPRINTFN("Read failed.\n"); if (ret <= 0) { detach(); return 0; } return ret; } // Terminal output. size_t Terminal::write(const uint8_t *buf, size_t len) { if (data_fd < 0) panic("Terminal not properly attached.\n"); ssize_t ret = atomic_write(data_fd, buf, len); if (ret < len) detach(); return ret; } #define MORE_PENDING (ULL(1) << 61) #define RECEIVE_SUCCESS (ULL(0) << 62) #define RECEIVE_NONE (ULL(2) << 62) #define RECEIVE_ERROR (ULL(3) << 62) uint8_t Terminal::readData() { uint8_t c; assert(!rxbuf.empty()); rxbuf.read((char *)&c, 1); DPRINTF(TerminalVerbose, "in: \'%c\' %#02x more: %d\n", isprint(c) ? c : ' ', c, !rxbuf.empty()); return c; } uint64_t Terminal::console_in() { uint64_t value; if (dataAvailable()) { value = RECEIVE_SUCCESS | readData(); if (!rxbuf.empty()) value |= MORE_PENDING; } else { value = RECEIVE_NONE; } DPRINTF(TerminalVerbose, "console_in: return: %#x\n", value); return value; } void Terminal::writeData(uint8_t c) { #if TRACING_ON == 1 if (DTRACE(Terminal)) { static char last = '\0'; if ((c != '\n' && c != '\r') || (last != '\n' && last != '\r')) { if (c == '\n' || c == '\r') { int size = linebuf.size(); char *buffer = new char[size + 1]; linebuf.read(buffer, size); buffer[size] = '\0'; DPRINTF(Terminal, "%s\n", buffer); delete [] buffer; } else { linebuf.write(&c, 1); } } last = c; } #endif txbuf.write(&c, 1); if (data_fd >= 0) write(c); if (outfile) outfile->stream()->put((char)c); DPRINTF(TerminalVerbose, "out: \'%c\' %#02x\n", isprint(c) ? c : ' ', (int)c); } Terminal * TerminalParams::create() { return new Terminal(this); }