Chapter 29 Key Takeaways: Device I/O
-
Two I/O paradigms exist on x86-64. Port-mapped I/O uses a separate 16-bit address space and the
IN/OUTinstructions (available only in ring 0 by default). Memory-mapped I/O places device registers in the physical address space, accessed with ordinaryMOVinstructions from any ring that has the pages mapped. -
IN/OUTare privileged instructions. Ring-3 code that executes them receives a #GP fault unless the I/O Permission Bitmap in the TSS grants access to specific ports. Theiopl()syscall modifies this permission globally; the IOPB enables per-port granularity. -
The PIT oscillator runs at exactly 1.193182 MHz. The divisor formula is
reload = 1,193,182 / desired_Hz. For 100Hz: reload = 11,931. Write the Mode/Command byte to port 0x43, then write low byte and high byte of the divisor to port 0x40. Mode 3 (square wave) is the standard choice for a periodic system timer. -
Every hardware interrupt handler must send EOI. For the PIC:
OUT 0x20, 0x20. For the LAPIC:MOV [lapic_base + 0xB0], 0. Without EOI, the interrupt controller masks that IRQ line permanently. -
The 16550 UART is the bare-metal programmer's best friend. Available before the display system works, before interrupts are configured, before the kernel heap exists. Three writes to configure (disable interrupts, set baud rate, set 8N1), then check LSR bit 5 before each
OUT 0x3F8, al. QEMU redirects serial output to your terminal with-serial stdio. -
UART baud rate is set via the Divisor Latch. Set DLAB=1 (bit 7 of LCR) to access the 16-bit divisor registers at offsets +0 and +1. For 115200 baud: divisor = 1. Always clear DLAB after setting the divisor.
-
The LAPIC is memory-mapped at 0xFEE00000. It requires no PIC initialization — it is the modern replacement. Key registers: SVR (enable APIC), EOI (acknowledge interrupt), LVT Timer (configure timer), Initial Count (set timer period). The LAPIC provides one timer per CPU core, with much higher resolution than the PIT.
-
PCI configuration space is accessible via two I/O ports. Write the 32-bit address (bus/device/function/register/enable-bit) to port 0xCF8, read or write the 32-bit data value from port 0xCFC. Vendor ID = 0xFFFF means no device is present.
-
ARM64 has no port I/O — everything is MMIO. ARM64 also has a weaker memory model than x86-64, requiring explicit barriers (
DSB,DMB,ISB) around MMIO accesses to prevent the CPU from reordering reads and writes to device registers. -
The serial console enables debug output before any other subsystem is ready. The pattern is: initialize serial first in
kernel_main, log every subsequent initialization step, catch any crash with a useful message rather than a silent hang. This applies to OS development, embedded firmware, and any bare-metal code. -
PIT calibration using RDTSC connects hardware time to CPU time. By measuring RDTSC delta across one PIT tick, you can estimate the CPU clock frequency. This calibration enables accurate microsecond-precision delays using RDTSC directly, without the 10ms granularity limit of the 100Hz PIT.