Repository navigation
Serial Port Driver
Driver for serial ports (COM1-4) with UART register access via I/O ports. Handles communication via RS-232 serial interface.
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.
| 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 |
AsyncSerialConsoleWriter.java |
Bounded queue + daemon drain thread for console output; keeps a stalled UART from blocking the shell. See Async-Serial-Console-Writer |
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 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 LCRSerialPortFinder 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.
The driver implements ByteChannel for NIO-based I/O:
-
read(ByteBuffer)- reads bytes into buffer viareadSingle()polling -
write(ByteBuffer)- writes bytes viawriteSingle()polling -
isOpen()/close()- channel state management
Both read and write use busy-wait loops polling the line status register (LSR).
-
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. -
No interrupt support: The driver does not use UART interrupts for data availability notification. Interrupt-driven I/O would be more efficient.
-
FIFO not utilized: Although the UART supports 16-byte transmit/receive FIFOs, the driver does not enable or utilize them, potentially limiting throughput.
-
Limited port detection: Only COM1 (0x3f8) and COM2 (0x2f8) are registered. COM3/COM4 at 0x3e8 and 0x2e8 are not automatically discovered.
-
No DMA support: Serial port DMA transfers are not implemented.
JNode supports interactive serial console operation via SerialConsolePlugin, which auto-starts during boot:
File: shell/src/shell/org/jnode/shell/command/driver/console/SerialConsolePlugin.java
When the plugin system starts, SerialConsolePlugin.startPlugin():
- Opens
serial1(COM2, I/O 0x2f8) at 115200 baud - Creates a
SerialAgentConsolewith raw text mode - Starts a
CommandShellthread with prompt[JNODE_AGENT_READY] - Thread is daemon — dies with the system
No manual activation needed — the plugin starts automatically.
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 best-effort. The scheduler no longer resurrects a stopped thread that was sleeping (Core-Thread-Scheduling), but a worker that refuses to die is still reported honestly as "stop requested" rather than "has been killed".
File: core/src/driver/org/jnode/driver/serial/console/AsyncSerialConsoleWriter.java
A ConsoleWriter that never performs the busy-waiting UART write on the caller's thread. It copies output into an 8 KiB bounded queue and a daemon serial-console-output-drain thread owns the port:
-
write()appends to aStringBuilderunder a short lock, drops the excess when the queue is full, accounts the loss, and notifies the drain thread. A stalled UART can no longer hold the console writer's monitor or wedge the shell. - The drain thread sleeps on the queue, snapshots each chunk, writes it (expanding
\nto\r\n), and appends[... truncated N chars ...]when output was dropped. -
close()is bounded: it interrupts and joins the drain thread for at mostCLOSE_JOIN_TIMEOUT_MS = 1000ms, even if the port is permanently stalled. - A runtime exception from the port latches the writer
failed; all later writes are dropped with accounting instead of retried.
The 7 host-runnable tests in AsyncSerialConsoleWriterTest are part of Core-Test-Suite. See Async-Serial-Console-Writer for the queue, truncation marker and failure-latch details.
File: shell/src/shell/org/jnode/shell/command/driver/console/SerialConsoleCommand.java
Interactive shell command for manual serial console:
serialconsole -p serial1 -b 115200 -aParameters: -p port, -b baud rate, -a agent mode
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
lkdboot flag -
Lazy initialization: Uses
DeviceUtils.getAPI()to acquireSerialPortAPI— 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
SerialPortAppenderfails to acquire the device, falls back toUnsafeDebugAppender(routes throughUnsafe.debug()which outputs to both VGA and serial at assembly level). CatchesRuntimeException(not checkedException) since device API failures are unchecked.
When lkd is present, Log4jConfigurePlugin adds SerialAppender to the root logger, routing all Log4j output to COM1.
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).
- Driver-Framework - Base driver architecture
- Device-Manager - Device registration and lifecycle
- DeviceAPI - Device API pattern
- Resource-Management - I/O port allocation
- Input-Drivers - Related input device drivers
- Async-Serial-Console-Writer - Bounded async console output over a stalled UART
- Core-Thread-Scheduling - Why stopped sleeping threads no longer run
- JNode-Serial-Skill - Agent console and KDB over serial