Serial Consoles

TEMU provides a number of console models that bridge a serial port of the emulated system, normally a UART, to something on the host:

Console

Logs the serial traffic to stdout or to a file, and can inject strings into the receiver. Typically called tty<X> in the examples.

ConsoleXTerm

Opens an xterm window running on the host.

ConsolePTY

Creates a pseudo terminal on the host, which a terminal program such as screen can then attach to.

ConsoleTelnet

Listens on a TCP port and serves the console to a telnet client.

Connecting a Console

A console is connected to the transmit port of a UART model. The connect command wires up both directions. ConsolePTY and ConsoleTelnet read their host file descriptors asynchronously, so they also need a TEMU time source:

Connecting a Telnet Console to a GRLIB APBUART
import ConsoleTelnet

ConsoleTelnet.new name=telnet0
connect-timesource obj=telnet0 ts=cpu0
telnet0.start port=9023

connect a=apbuart0.tx b=telnet0:SerialIface

PTY Console

The ConsolePTY model allocates a pseudo terminal when the object is created. The host path (e.g. /dev/pts/42) is printed in the log message emitted at creation, and is also available as a read-only object property.

Input is always echoed back from the emulated side, so the terminal program must not echo locally as well. screen works without any extra flags, while other PTY clients may need one, e.g. socat -,raw,echo=0 /dev/pts/42.

The following properties are available:

slavePath

Read-only path of the allocated PTY slave device.

config.rawSlave

Put the slave side into raw mode (default 1). In raw mode the host terminal driver does not add its own echo and line editing on top of what the target software already does. Set it to 0 if the attached program configures the terminal itself.

config.txQueueLimit

Maximum number of bytes buffered towards the PTY (default 4096).

config.rxQueueLimit

Maximum number of bytes buffered towards the UART (default 4096).

While its transmit queue is full, the model reports that it is not clear to receive, so a UART that honours flow control (see Serial Flow Control) holds the data back instead.

Bridging a UART to a PTY
import ConsolePTY

ConsolePTY.new name=pty0
connect-timesource obj=pty0 ts=cpu0
connect a=apbuart0.tx b=pty0:SerialIface

# Print the path to attach a terminal to
pty0.slavePath

Telnet Console

The ConsoleTelnet model serves the console over TCP. The listen socket is not opened when the object is created, but by the start command:

telnet0.start port=9023

If port is omitted, or 0 is given, the operating system selects a free port. The port that was actually bound can be read from the listenPort property.

By default the model only binds the loopback interface. Set config.bindAny to 1 to accept connections from other hosts.

The following properties are supported:

listenPort

Read-only TCP port the model is listening on.

config.port

Port used by start when it is called without a port argument (default 0, i.e. let the operating system pick one).

config.bindAny

Bind 0.0.0.0 instead of 127.0.0.1 (default 0).

config.telnetMode

Speak the telnet protocol (default 1). Set to 0 for a raw TCP byte stream.

config.rfc2217Mode

Announce the RFC 2217 com-port option (default 0).

config.txQueueLimit

Maximum number of bytes buffered towards the client (default 4096).

config.rxQueueLimit

Maximum number of bytes buffered towards the UART (default 4096).

In telnet mode (the default) the model implements the option negotiation of RFC 854. On connect it offers and requests binary transmission and offers to do the input echoing itself. A normal telnet client can therefore be used directly:

$ telnet localhost 9023

Interactive commands (IAC sequences) from the client are interpreted and removed from the byte stream handed to the UART. Unless binary transmission has been negotiated, line endings are translated: a CR NUL or CR LF from the client is passed on as a single CR, and a bare CR from the target is sent as CR LF. A target that already emits CR LF is not modified. By default the server always offers binary transmission.

Setting config.rfc2217Mode to 1 additionally requests the com-port control option (RFC 2217) from the client. Baud rate, parity and other line settings are not applied to the UART model. Only the negotiation itself is handled.

Serial Flow Control

A serial endpoint can tell its peer whether it is currently able to accept data. This is used both by the UART models and by the console models to apply back pressure instead of dropping bytes when one side is faster than the other.

The mechanism is an optional addition to the SerialIface interface:

typedef struct temu_SerialIface {
  void (*write)(void *Obj, uint8_t Data);

  //! Notify this endpoint that the peer may now be clear to receive data.
  void (*cts)(void *Obj);

  //! Return whether this endpoint is currently clear to receive data.
  //! Optional. If NULL, we assume it is always clear to receive.
  bool (*isClearToReceive)(void *Obj);
} temu_SerialIface;

Implementing isClearToReceive is optional. An endpoint that leaves it NULL is always treated as ready, which is how all serial endpoints behaved before.

The TEMU UART hardware device models and the consoles implement isClearToReceive since TEMU 5.1.

Capturing Console Traffic

The Console model can keep the console output in memory in addition to printing it. Setting config.recordTraffic to 1 enables the recording, and the dump-traffic command prints what has been captured so far:

tty0.config.recordTraffic = 1

# Later, print everything that has been captured
tty0.dump-traffic

# ... or only the lines from line 20 and onwards
tty0.dump-traffic from=20

The command takes an optional from argument, which is the index of the first line to print, so that a script or an external tool can poll for new output without repeating what it has already seen. This is how the MCP server gives an agent access to the console output of the target software.