Skip to content

Serial Port Driver

opencode-agent[bot] edited this page Sep 13, 2026 · 5 revisions

Serial Port Driver

Driver for serial ports (COM1-4) with UART register access via I/O ports. Handles communication via RS-232 serial interface.

Overview

The Serial Port Driver provides access to standard PC serial ports (COM1-COM4) using the 16550/16450 UART hardware. It implements the SerialPortAPI interface and extends the Driver framework, integrating with JNode's device management system. The driver uses programmed I/O (PIO) to access UART registers through I/O ports, supporting configurable baud rate, data bits, stop bits, and parity.

Key Components

Class/File Purpose
SerialPortDriver.java Main driver implementation - extends Driver, implements SerialPortAPI and ByteChannel
SerialPortAPI.java API interface defining baud rate constants and configuration methods
SerialPortFinder.java Device finder that registers serial0 and serial1 devices at boot

How It Works

UART Register Layout

The driver accesses UART registers via I/O ports starting at a configurable base address. Each register is at offset basePort + n:

Offset Register Access Purpose
+0 Data/ divisor LSB R/W DLAB=0: receive/transmit data; DLAB=1: divisor bits 0-7
+1 IER / divisor MSB R/W DLAB=0: interrupt enable; DLAB=1: divisor bits 8-15
+2 FCR / IIR W/R FIFO control (write) / FIFO status (read)
+3 LCR R/W Line control: DLAB, parity, stop bits, data length
+4 MCR R/W Modem control: DTR, RTS, loopback
+5 LSR R/W Line status: data ready, overrun, framing, parity
+6 MSR R/W Modem status: CTS, DSR, RI, CD
+7 Scratch R/W 1-byte scratch register

Baud Rate Configuration

Baud rate is configured via a divisor value using the formula: baudRate = 115200 / divisor

Standard baud rate divisors:

  • 9600 baud: divisor = 12 (BAUD9600 constant)
  • 4800 baud: divisor = 24 (BAUD4800 constant)
  • 2400 baud: divisor = 48 (BAUD2400 constant)
  • 1200 baud: divisor = 96 (BAUD1200 constant)

Configuration sequence (from SerialPortDriver.java:121-124):

port.outPortByte(basePort + 3, control | 128);  // Set DLAB=1
port.outPortByte(basePort + 0, divisor);        // Divisor LSB
port.outPortByte(basePort + 1, divisor >> 8);   // Divisor MSB
port.outPortByte(basePort + 3, control);        // Clear DLAB, set LCR

Device Discovery

SerialPortFinder implements DeviceFinder to register serial ports at boot:

Device Name I/O Port Base Standard Mapping
serial0 0x3f8 COM1
serial1 0x2f8 COM2

The finder creates a Device instance for each port, associates it with a SerialPortDriver instance, and registers it with the DeviceManager.

ByteChannel Implementation

The driver implements ByteChannel for NIO-based I/O:

  • read(ByteBuffer) - reads bytes into buffer via readSingle() polling
  • write(ByteBuffer) - writes bytes via writeSingle() polling
  • isOpen() / close() - channel state management

Both read and write use busy-wait loops polling the line status register (LSR).

Gotchas

  1. Busy-wait I/O: The current implementation uses busy-wait polling for both read and readSingle (SerialPortDriver.java:136-138) and write (SerialPortDriver.java:143-145). This is CPU-intensive and blocks the calling thread.

  2. No interrupt support: The driver does not use UART interrupts for data availability notification. Interrupt-driven I/O would be more efficient.

  3. FIFO not utilized: Although the UART supports 16-byte transmit/receive FIFOs, the driver does not enable or utilize them, potentially limiting throughput.

  4. Limited port detection: Only COM1 (0x3f8) and COM2 (0x2f8) are registered. COM3/COM4 at 0x3e8 and 0x2e8 are not automatically discovered.

  5. No DMA support: Serial port DMA transfers are not implemented.

Serial Console & Agent Mode

JNode supports interactive serial console operation via SerialConsolePlugin, which auto-starts during boot:

SerialConsolePlugin

File: shell/src/shell/org/jnode/shell/command/driver/console/SerialConsolePlugin.java

When the plugin system starts, SerialConsolePlugin.startPlugin():

  1. Opens serial1 (COM2, I/O 0x2f8) at 115200 baud
  2. Creates a SerialAgentConsole with raw text mode
  3. Starts a CommandShell thread with prompt [JNODE_AGENT_READY]
  4. Thread is daemon — dies with the system

No manual activation needed — the plugin starts automatically.

SerialAgentConsole

File: core/src/driver/org/jnode/driver/serial/console/SerialAgentConsole.java

Implements agent-mode serial console with:

  • RawKeyboardReader — reads raw serial input bytes via a daemon pump thread
  • SerialTextScreen — text screen backed by serial I/O
  • SerialTextScreenManager — manages screen dimensions

Job Control (Ctrl-C / Ctrl-Z): The agent console translates control characters into keyboard events so shell job control works over serial as on VGA. The RawKeyboardReader daemon pump thread drains the serial port continuously. When it sees ETX (0x03, Ctrl-C) or SUB (0x1A, Ctrl-Z), it fires a KeyboardEvent via AbstractConsole.enqueueKeyboardEvent() -- bypassing the focus check, since agent consoles are never focused. All other bytes are queued for the shell's read() calls.

The enqueueKeyboardEvent() method on AbstractConsole adds events directly to the keyboard event processor's queue, allowing async dispatch without blocking the serial input pump. This is critical because the shell may be blocked waiting for a listener that writes to a stalled UART.

Limitation: Thread.stop() remains ineffective against timer-sleeping proclet workers (JVM-level issue). The Ctrl-C/Ctrl-Z keystroke is delivered and diagnostics are printed, but the kill itself may not terminate the worker.

SerialConsoleCommand

File: shell/src/shell/org/jnode/shell/command/driver/console/SerialConsoleCommand.java

Interactive shell command for manual serial console:

serialconsole -p serial1 -b 115200 -a

Parameters: -p port, -b baud rate, -a agent mode

Serial Appender (Log4j)

File: core/src/core/org/jnode/log4j/config/SerialAppender.java

A log4j WriterAppender that writes to a serial port via SerialPortDriver's API:

  • Port: serial0 (COM1, 0x3F8) by default
  • Data format: 8N1
  • Conversion: \n → \r\n
  • Activation: Requires lkd boot flag
  • Lazy initialization: Uses DeviceUtils.getAPI() to acquire SerialPortAPI — the serial port device may not be available when this appender is created (plugins start before device finders). Pending output is buffered (up to 8KB) until the device becomes available, then drained.
  • Fallback: If SerialPortAppender fails to acquire the device, falls back to UnsafeDebugAppender (routes through Unsafe.debug() which outputs to both VGA and serial at assembly level). Catches RuntimeException (not checked Exception) since device API failures are unchecked.

When lkd is present, Log4jConfigurePlugin adds SerialAppender to the root logger, routing all Log4j output to COM1.

SerialPortFinder

File: core/src/driver/org/jnode/driver/serial/SerialPortFinder.java

Discovers and registers serial port devices at boot. Handles registration failures gracefully — if a device name is already registered or a driver cannot be set, a warning is logged but boot continues. Registers both serial0 (0x3f8) and serial1 (0x2f8).

Related Pages

Clone this wiki locally