Skip to main content
The serial port driver provides basic RS-232 serial communication capabilities, primarily used for kernel debugging and logging output. It communicates with the first serial port (COM1) using standard x86 I/O ports.

Overview

The serial driver configures COM1 (I/O base 0x3F8) for 9600 baud, 8 data bits, no parity, and 1 stop bit (8N1). This is a transmit-only implementation focused on output for debugging purposes.
The serial port is particularly useful during early boot when the framebuffer may not yet be available, and for remote debugging via serial console.

Hardware Configuration

The driver uses the following x86 I/O ports:
PortRegisterPurpose
0x3F8DataTransmit/receive data
0x3F9Interrupt EnableInterrupt configuration (divisor high byte when DLAB=1)
0x3FAFIFO ControlFIFO buffer configuration
0x3FBLine ControlSerial line parameters
0x3FCModem ControlModem control signals
0x3FDLine StatusTransmission status

API Functions

serial_init

Initializes the serial port hardware with standard 8N1 configuration at 9600 baud.
void serial_init(void);
Implementation from serial.c:4-15:
void serial_init()
{
    uint16_t divisor = 115200 / 9600;
    uint8_t divlow = divisor & 0xFF;
    uint8_t divhigh = divisor >> 8;
    port_outb(0x3fb, 0x80);
    port_outb(0x3f8, divlow);
    port_outb(0x3f9, divhigh);
    port_outb(0x3fb, 0x03);
    port_outb(0x3fa, 0xC7);
    port_outb(0x3fc, 0x03);
}

Initialization Sequence

  1. Calculate baud rate divisor: 115200 / 9600 = 12
  2. Set DLAB (Divisor Latch Access Bit): Write 0x80 to Line Control Register (0x3FB)
  3. Set divisor: Write low byte to 0x3F8, high byte to 0x3F9
  4. Configure line parameters: Write 0x03 to 0x3FB (8 data bits, no parity, 1 stop bit)
  5. Enable FIFO: Write 0xC7 to FIFO Control Register (0x3FA)
  6. Set modem control: Write 0x03 to Modem Control Register (0x3FC)
The baud rate divisor is calculated as 115200 / desired_baud_rate. The base frequency of 115200 Hz is the standard for PC serial ports.

serial_send

Transmits a single byte over the serial port.
void serial_send(char byte);
Implementation from serial.c:24-32:
void serial_send(char byte)
{
    while (is_transmit_empty() == 0)
        ;

    if (byte == '\n')
        port_outb(0x3f8, '\r');
    port_outb(0x3f8, byte);
}
The function:
  1. Waits for the transmit buffer to be empty
  2. Sends a carriage return (\r) before newlines for proper terminal formatting
  3. Transmits the byte
This is a blocking function - it will wait indefinitely if the serial port hardware is not ready. This is acceptable for debugging but should not be used in time-critical code paths.

is_transmit_empty (Internal)

Checks if the transmit buffer is ready for data.
static int is_transmit_empty();
Implementation from serial.c:17-22:
static int is_transmit_empty()
{
    uint8_t in;
    port_inb(0x3fd, &in);
    return in & 0x20;
}
Reads the Line Status Register (0x3FD) and checks bit 5 (Transmit Holding Register Empty).

Baud Rate Configuration

The serial port is configured for 9600 baud, calculated using:
Divisor = Base Frequency / Desired Baud Rate
Divisor = 115200 / 9600 = 12
To change the baud rate, modify the divisor calculation in serial_init:
Baud RateDivisor
1152001
576002
384003
192006
960012
480024
240048

Line Termination

The driver automatically converts Unix-style line endings (\n) to Windows-style (\r\n) for compatibility with most serial terminal programs:
if (byte == '\n')
    port_outb(0x3f8, '\r');
port_outb(0x3f8, byte);

Usage Example

Typical usage pattern for kernel logging:
// Initialize during kernel boot
serial_init();

// Send debug messages
const char* msg = "Kernel initialized\n";
while (*msg) {
    serial_send(*msg++);
}

Hardware Requirements

The driver assumes a standard 16550-compatible UART at I/O base 0x3F8 (COM1). This is present in virtually all x86/x86_64 systems and emulators.

Source Files

kernel/dev/serial/serial.h

Build docs developers (and LLMs) love