Nadir serial COM1 polling driver

This commit is contained in:
Pierre-Emmanuel Gob. 2026-09-18 20:17:35 +02:00
parent 7ee953925a
commit 47e83cf795
2 changed files with 100 additions and 0 deletions

35
include/serial.h Normal file
View file

@ -0,0 +1,35 @@
/* Nadir serial log: COM1 (16550 UART) polling driver.
*
* Runs in 64-bit long mode at Ring 0 (CPL 0); uses `inb`/`outb` which fault
* with #GP if executed above CPL 0, so this driver is kernel-only.
* Ports: COM1 0x3F8-0x3FF (data, int-enable, divisor latch, FIFO, line
* control, modem control, line status). QEMU/SeaBIOS provides COM1; the
* VGA console stays primary and serial is the secondary log.
* Fault behavior: port I/O cannot fault at CPL 0. The TX poll spins on
* LSR bit 5 and hangs only if the UART hardware is absent or wedged —
* call serial_ok() first to check LSR sanity.
*
* Preconditions: call serial_init() once before any put. serial_puts()
* requires a valid NUL-terminated string (NULL is ignored, not crashed
* on). Failure modes: on absent hardware, init writes go nowhere and
* serial_ok() returns 0; TX polls may then spin forever.
*/
#ifndef NADIR_SERIAL_H
#define NADIR_SERIAL_H
/* Program COM1 for 38400 8N1 polling (interrupts off, FIFO on).
* Ring 0 only. Safe to call again to re-program the port. */
void serial_init(void);
/* Block until the THR is empty (LSR bit 5), then emit one byte. */
void serial_putchar(char c);
/* Emit a NUL-terminated string via serial_putchar(). NULL is a no-op. */
void serial_puts(const char *s);
/* Nonzero if a UART looks present (LSR reads back sane, not 0xFF
* as an open bus returns). Zero means "do not trust TX polls". */
int serial_ok(void);
#endif /* NADIR_SERIAL_H */

View file

@ -0,0 +1,65 @@
/* COM1 16550 UART polling log (see serial.h). */
#include <stdint.h>
#include "io.h"
#include "serial.h"
/* Register offsets from the COM1 base. */
#define COM1_BASE 0x3F8U
#define COM_DATA 0x3F8U /* THR/RBR (DLAB=0) / divisor low (DLAB=1) */
#define COM_IER 0x3F9U /* interrupt enable / divisor high (DLAB=1) */
#define COM_FIFO 0x3FAU /* FIFO control */
#define COM_LCTL 0x3FBU /* line control + DLAB bit 7 */
#define COM_MCTL 0x3FCU /* modem control */
#define COM_LSR 0x3FDU /* line status: bit 5 = THR empty, bit 0 = RX ready */
#define LSR_TX_EMPTY 0x20U /* THR holds no byte: safe to write */
#define LSR_RX_READY 0x01U /* RBR holds a byte (reserved for future RX) */
/* Port I/O owned by include/io.h; no local inb/outb duplicates. */
void serial_init(void)
{
/* No UART interrupts: pure polling, so the 8259/IDT stays untouched. */
outb(COM_IER, 0x00);
/* DLAB on to program the divisor. */
outb(COM_LCTL, 0x80);
/* Divisor 3 = 115200/3 = 38400 baud. */
outb(COM_DATA, 0x03);
outb(COM_IER, 0x00);
/* 8N1: 8 bits, no parity, one stop bit; DLAB back off. */
outb(COM_LCTL, 0x03);
/* FIFO on, clear both queues, 14-byte threshold. */
outb(COM_FIFO, 0xC7);
/* DTR+RTS on, OUT2 on so the UART drives interrupts (unused). */
outb(COM_MCTL, 0x0B);
}
void serial_putchar(char c)
{
/* Spin until THR is empty; hangs only if hardware is absent/wedged,
* which is why callers gate on serial_ok() when unsure. */
while ((inb(COM_LSR) & LSR_TX_EMPTY) == 0) {
/* Poll. */
}
outb(COM_DATA, (uint8_t)c);
}
void serial_puts(const char *s)
{
if (s == 0) {
return;
}
while (*s != '\0') {
serial_putchar(*s);
s++;
}
}
int serial_ok(void)
{
/* An absent port reads back 0xFF (open bus); a real 16550 never
* reports all status bits set at once, so that means no UART. */
return inb(COM_LSR) != 0xFFU;
}