Master the fundamental concepts of build a mini kernel through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksSerial port COM1 (0x3F8) gives early kernel logs when VGA is not ready. 16550 UART needs divisor latch for baud rate, line control for frame format, and FIFO enable for fewer overruns.
Minimum init:
115200 baudFor example, divisor 1 at 1.8432 MHz base yields 115200 bps 8N1 output many bootloaders use.
The UART 16550 you're programming here is why qemu -serial stdio and real hardware debug cables both work identically , serial output is often the only way to debug a kernel before VGA or any other driver is initialized. Forgetting to poll the Line Status Register before sending a byte is a common bug that silently drops characters when the transmit buffer isn't actually empty yet.
Before you call the implementation done, walk failure modes on purpose. Test empty structures, single-element edge cases, maximum concurrency, and errno paths that must not crash the program. OS code usually fails in production when happy-path tests pass but invariants break under contention or memory pressure.
Keep structures small and name fields after kernel counterparts when possible. That lets you read man pages and kernel source side by side while you work. Print observable events during development; remove noisy logs once tests pass reliably.
You will implement serial_putc polling line status bit 0x20 (THRE). This exercise requires printing panic messages on serial when exceptions fire before VGA init.
Simulate a kernel's COM1 serial driver for a 16550 UART at I/O base 0x3F8. Print every port write the driver makes, the bits each byte puts on the wire, and how many times the driver has to poll the Line Status Register (LSR) before it may write the next byte.
cLoading…
init. The divisor is 115200 / BAUD. If BAUD is 0 or does not divide 115200, print init BAUD: unsupported baud rate (115200 is not divisible by it) and change nothing. If FORMAT is invalid (data bits must be 5-8, parity N, E or O, stop bits 1 or 2), print init BAUD FORMAT: bad line format. In both cases the port keeps its previous settings. Otherwise, the line control byte is (data-5) | (stop==2 ? 0x04 : 0) | (O ? 0x08 : E ? 0x18 : 0). Print a header, then these seven writes:
| Port | Value | Meaning |
|---|---|---|
| 0x3f9 | 0x00 | interrupts off |
| 0x3fb | 0x80 | DLAB on |
| 0x3f8 | divisor low byte | |
| 0x3f9 | divisor high byte | |
| 0x3fb | line control | DLAB off |
| 0x3fa | 0xc7 | enable and clear FIFOs |
| 0x3fc | 0x0b | DTR, RTS, OUT2 |
send. Newlines are sent as CR LF, so each \n becomes the bytes 0x0d and 0x0a. Each frame has 1 start bit, the data bits, 1 parity bit if the port uses parity, and the stop bits. One byte takes byte_us = frame_bits * 1000000 / baud microseconds, using integer division. Total time is bytes * byte_us. The driver polls LSR once per microsecond. The first byte finds the transmitter empty after 1 poll, and every later byte needs byte_us + 1 polls. For each byte, print its wire image: start bit 0, the low data-bits bits of the byte LSB first, the parity bit if any (even parity makes the count of 1s even, odd makes it odd), then the stop bits as 1s, with the groups separated by single spaces. Printable bytes (32-126) are shown as 'c', and other bytes as 0xNN. Before any init, send prints send: port not initialised.
lsr. Print the names of the set bits, from bit 0 upward: data ready, overrun error, parity error, framing error, break, transmit holding empty, transmitter empty, FIFO error. If no bit is set, print (none).
loopback. Write 0x1e to MCR, send test byte 0xae, read it back, and restore MCR to 0x0f. Before any init, print loopback: port not initialised.
cLoading…
All values are lower-case hex. The send header echoes TEXT exactly as it was typed.
Input:
cLoading…
Output:
cLoading…
Hidden tests cover 7-bit, 5-bit and parity formats, two stop bits, slow baud rates whose divisor needs the high byte, rejected baud rates and formats, LSR error bits, and the loopback self-test.