meow
Find a file
2026-04-12 16:21:07 +00:00
idk.c init 2026-03-08 17:30:43 +05:30
irq_pit.md Add irq_pit.md 2026-04-12 16:21:07 +00:00
README.md Add README.md 2026-04-11 16:51:24 +00:00

Serial Port (UART 16550A) Deep Dive Guide

Table of Contents

  1. What is a Serial Port?
  2. Hardware Level: UART in x86 Architecture
  3. UART 16550A Register Map
  4. I/O Port Addresses
  5. How Serial Port Works in Your Hypervisor
  6. Detailed Instruction Examples
  7. Memory Map and Architecture Diagram
  8. CPU and UART Communication Flow
  9. Extra UART Ports for Linux Kernel x86
  10. Virtualization Strategy

What is a Serial Port?

A serial port is a physical interface that transfers data one bit at a time (sequentially). In x86 systems, serial communication is handled by a UART (Universal Asynchronous Receiver/Transmitter) chip, historically the Intel 8250 or the more advanced National Semiconductor 16550A.

Why "UART"?

  • Universal: Works with various protocols
  • Asynchronous: No clock signal shared between sender/receiver
  • Receiver/Transmitter: Handles both incoming and outgoing data

Hardware Level: UART in x86 Architecture

Physical Hardware Evolution

┌─────────────────────────────────────────────────────────────────┐
│                    Hardware Evolution                           │
│                                                                 │
│  1980s: Intel 8250 UART (1-byte buffer)                        │
│       ↓                                                         │
│  1987: National Semi 16550 (buggy FIFO)                        │
│       ↓                                                         │
│  1989: National Semi 16550A (16-byte FIFO, working)            │
│       ↓                                                         │
│  Modern: Integrated into chipset/Super I/O or emulated in QEMU │
└─────────────────────────────────────────────────────────────────┘

Physical Location on Motherboard

In real hardware, the UART exists in one of these forms:

  1. Discrete Chip (old systems): A physical 16550A chip on the motherboard
  2. Super I/O Chip (modern legacy): Integrated into a Super I/O controller (e.g., Nuvoton NCT6779D)
  3. Southbridge/PCH (modern): Part of the Platform Controller Hub
  4. Virtual/Emulated (VMs/Hypervisors): Emulated in software by QEMU/KVM
┌─────────────────────────────────────────────────────────────────┐
│                   Motherboard Layout (Simplified)               │
│                                                                 │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐      │
│  │     CPU      │◄──►│  Northbridge │◄──►│    RAM       │      │
│  │  (Core i7)   │    │  (Memory     │    │  (DDR4)      │      │
│  │              │    │   Controller)│    │              │      │
│  └──────┬───────┘    └──────────────┘    └──────────────┘      │
│         │                                                       │
│         │ DMI/LPC Bus                                           │
│         ▼                                                       │
│  ┌──────────────────────────────────────────────────────┐      │
│  │              Southbridge / PCH                        │      │
│  │  ┌────────────────────────────────────────────┐     │      │
│  │  │         Super I/O Controller                │     │      │
│  │  │  ┌────────┐ ┌────────┐ ┌────────┐         │     │      │
│  │  │  │ UART #1│ │ UART #2│ │  UART  │  ...    │     │      │
│  │  │  │0x3F8   │ │0x2F8   │ │0x3E8   │         │     │      │
│  │  │  └────────┘ └────────┘ └────────┘         │     │      │
│  │  └────────────────────────────────────────────┘     │      │
│  │                                                      │      │
│  │  Also contains: USB, SATA, PCIe, RTC, etc.          │      │
│  └──────────────────────────────────────────────────────┘      │
│                                                                 │
│  Serial Port Connector (DB9) ──► External Cable                │
└─────────────────────────────────────────────────────────────────┘

How CPU Communicates with UART

The CPU talks to UART through I/O ports (not memory-mapped I/O on x86). This uses special instructions:

┌──────────────────────────────────────────────────────────────┐
│              CPU-to-UART Communication Path                   │
│                                                              │
│  CPU executes:                                               │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  IN  AL, 0x3F8     ; Read from UART port 0x3F8     │    │
│  │  OUT 0x3F8, AL     ; Write to UART port 0x3F8      │    │
│  └──────────────────────┬──────────────────────────────┘    │
│                         │                                     │
│                         ▼                                     │
│  ┌──────────────────────────────────────────────────────┐    │
│  │               CPU Bus Interface                       │    │
│  │         (I/O Space, not Memory Space)                 │    │
│  └──────────────────────┬──────────────────────────────┘    │
│                         │ I/O Read/Write Cycle              │
│                         ▼                                     │
│  ┌──────────────────────────────────────────────────────┐    │
│  │        LPC Bus (Low Pin Count) / ISA Bus              │    │
│  │  (Legacy bus for low-speed peripherals)               │    │
│  └──────────────────────┬──────────────────────────────┘    │
│                         │                                     │
│                         ▼                                     │
│  ┌──────────────────────────────────────────────────────┐    │
│  │           UART 16550A Chip                            │    │
│  │  ┌─────────────────────────────────────────────┐    │    │
│  │  │  I/O Decoder: Detects 0x3F8-0x3FF range     │    │    │
│  │  └──────────────────┬──────────────────────────┘    │    │
│  │                     │                                 │    │
│  │                     ▼                                 │    │
│  │  ┌─────────────────────────────────────────────┐    │    │
│  │  │        Register File (8 registers)           │    │    │
│  │  │  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ...      │    │    │
│  │  │  │ RBR │ │ IER │ │ FCR │ │ LCR │           │    │    │
│  │  │  │0x00 │ │0x01 │ │0x02 │ │0x03 │           │    │    │
│  │  │  └─────┘ └─────┘ └─────┘ └─────┘           │    │    │
│  │  └─────────────────────────────────────────────┘    │    │
│  └──────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────┘

UART 16550A Register Map

The UART has 8 registers, each 8 bits wide, accessed via I/O ports. The register accessed depends on the port address offset (0-7) and sometimes the LCR register state.

Complete Register Map

┌──────────────────────────────────────────────────────────────────────────────────────────┐
│                        UART 16550A Register Map (I/O Port Offsets)                       │
├──────────┬───────────┬─────────┬─────────────────────────────────────────────────────────┤
│ Offset   │ Register  │ Access  │ Description                                             │
│ (hex)    │ Name      │         │                                                         │
├──────────┼───────────┼─────────┼─────────────────────────────────────────────────────────┤
│ 0x00     │ RBR       │ Read    │ Receiver Buffer Register (incoming data)                │
│          │ THR       │ Write   │ Transmitter Holding Register (outgoing data)            │
│          │ DLL       │ R/W*    │ Divisor Latch Low (when LCR[7] = 1, DLAB mode)         │
├──────────┼───────────┼─────────┼─────────────────────────────────────────────────────────┤
│ 0x01     │ IER       │ Write   │ Interrupt Enable Register                               │
│          │ DLM       │ R/W*    │ Divisor Latch High (when LCR[7] = 1, DLAB mode)        │
├──────────┼───────────┼─────────┼─────────────────────────────────────────────────────────┤
│ 0x02     │ IIR       │ Read    │ Interrupt Identification Register                        │
│          │ FCR       │ Write   │ FIFO Control Register                                    │
├──────────┼───────────┼─────────┼─────────────────────────────────────────────────────────┤
│ 0x03     │ LCR       │ R/W     │ Line Control Register (data length, parity, stop bits)  │
├──────────┼───────────┼─────────┼─────────────────────────────────────────────────────────┤
│ 0x04     │ MCR       │ R/W     │ Modem Control Register (RTS, DTR, OUT1, OUT2)          │
├──────────┼───────────┼─────────┼─────────────────────────────────────────────────────────┤
│ 0x05     │ LSR       │ Read    │ Line Status Register (TX/RX ready, errors)             │
├──────────┼───────────┼─────────┼─────────────────────────────────────────────────────────┤
│ 0x06     │ MSR       │ Read    │ Modem Status Register (CTS, DSR, RI, DCD)              │
├──────────┼───────────┼─────────┼─────────────────────────────────────────────────────────┤
│ 0x07     │ SCR       │ R/W     │ Scratch Register (general-purpose storage)             │
└──────────┴───────────┴─────────┴─────────────────────────────────────────────────────────┘

* DLL/DLM accessed only when LCR bit 7 (DLAB) = 1

Detailed Register Bit Fields

1. RBR/THR/DLL (Offset 0x00)

┌─────────────────────────────────────────────────────────────────┐
│  RBR (Read) - Receiver Buffer Register                          │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  Bit 7 │ Bit 6 │ Bit 5 │ Bit 4 │ Bit 3 │ Bit 2 │ Bit 1  │  │
│  ├──────────────────────────────────────────────────────────┤  │
│  │                    Received Data Byte                     │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│  THR (Write) - Transmitter Holding Register                     │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  Bit 7 │ Bit 6 │ Bit 5 │ Bit 4 │ Bit 3 │ Bit 2 │ Bit 1  │  │
│  ├──────────────────────────────────────────────────────────┤  │
│  │                   Data to Transmit                        │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

2. IER/DLM (Offset 0x01)

┌─────────────────────────────────────────────────────────────────┐
│  IER (Write) - Interrupt Enable Register                        │
│  ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐    │
│  │ Bit7 │ Bit6 │ Bit5 │ Bit4 │ Bit3 │ Bit2 │ Bit1 │ Bit0 │    │
│  ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤    │
│  │  0   │  0   │  0   │  0   │ Sleep│  LSE  │  RTLS │ RDA  │    │
│  │      │      │      │      │      │       │       │      │    │
│  │      │      │      │      │      │       │       └──────┤    │
│  │      │      │      │      │      │       │   Received   │    │
│  │      │      │      │      │      │       │   Data       │    │
│  │      │      │      │      │      │       │   Available  │    │
│  │      │      │      │      │      │       └──────────────┘    │
│  │      │      │      │      │      └───────────────────────────┤    │
│  │      │      │      │      │   Transmitter Holding Register   │    │
│  │      │      │      │      │          Empty (THRE)            │    │
│  └──────┴──────┴──────┴──────┴──────────────────────────────────┘    │
│                                                                      │
│  Bit 0 (RDA): Received Data Available Interrupt                       │
│  Bit 1 (THRE): Transmitter Holding Register Empty Interrupt          │
│  Bit 2 (RTLS): Receiver Line Status Interrupt                         │
└─────────────────────────────────────────────────────────────────────┘

3. IIR/FCR (Offset 0x02)

┌─────────────────────────────────────────────────────────────────┐
│  IIR (Read) - Interrupt Identification Register                 │
│  ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐    │
│  │ Bit7 │ Bit6 │ Bit5 │ Bit4 │ Bit3 │ Bit2 │ Bit1 │ Bit0 │    │
│  ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤    │
│  │ FIFO │ FIFO │ INT  │ INT  │ INT  │      │      │ NO   │    │
│  │  1   │  0   │  3   │  2   │  1   │  0   │  0   │ INT  │    │
│  │      │      │      │      │      │      │      │      │    │
│  └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘    │
│                                                                 │
│  Bit 0: 0 = Interrupt Pending, 1 = No Interrupt                │
│  Bits 1-3: Interrupt Type (if Bit 0 = 0):                      │
│    0110 = Receiver Line Status (highest priority)               │
│    0100 = Received Data Available                               │
│    0110 = Character Timeout (FIFO mode)                         │
│    0010 = THR Empty (transmitter ready)                         │
│    0000 = Modem Status (lowest priority)                        │
│  Bits 6-7: FIFO status (11 = FIFO enabled)                     │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│  FCR (Write) - FIFO Control Register                            │
│  ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐    │
│  │ Bit7 │ Bit6 │ Bit5 │ Bit4 │ Bit3 │ Bit2 │ Bit1 │ Bit0 │    │
│  ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤    │
│  │ RX   │ RX   │ TX   │ TX   │ DMA  │ Clear│ Clear│ FIFO │    │
│  │ Trig1│ Trig0│ Trig1│ Trig0│ Mode │ RX   │ TX   │ Enbl │    │
│  └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘    │
│                                                                 │
│  Bit 0: 1 = Enable FIFO (must be 1 for 16550A)                  │
│  Bit 1: 1 = Clear RX FIFO (self-clearing)                       │
│  Bit 2: 1 = Clear TX FIFO (self-clearing)                       │
│  Bit 3: 1 = DMA Mode Select                                     │
│  Bits 6-7: RX Trigger Level:                                    │
│    00 = 1 byte, 01 = 4 bytes, 10 = 8 bytes, 11 = 14 bytes      │
└─────────────────────────────────────────────────────────────────┘

4. LCR (Offset 0x03) - Line Control Register

┌─────────────────────────────────────────────────────────────────┐
│  LCR - Line Control Register (R/W)                              │
│  ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐    │
│  │ Bit7 │ Bit6 │ Bit5 │ Bit4 │ Bit3 │ Bit2 │ Bit1 │ Bit0 │    │
│  ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤    │
│  │ DLAB │ Set  │ Stick│ Even │ Parity│ Stop │ Word │ Word │    │
│  │      │ Break│ Parity│Parity│ Enable│ Bits │ Len1 │ Len0 │    │
│  └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘    │
│                                                                 │
│  Bit 7 (DLAB): Divisor Latch Access Bit                         │
│    0 = Access RBR/THR/IER                                       │
│    1 = Access DLL/DLM (baud rate divisor)                       │
│                                                                 │
│  Bits 0-1: Word Length                                          │
│    00 = 5 bits, 01 = 6 bits, 10 = 7 bits, 11 = 8 bits          │
│                                                                 │
│  Bit 2: Stop Bits                                               │
│    0 = 1 stop bit, 1 = 2 stop bits (1.5 if 5-bit word)         │
│                                                                 │
│  Bit 3: Parity Enable                                           │
│    0 = No parity, 1 = Parity enabled                            │
│                                                                 │
│  Bit 4: Even Parity Select                                      │
│    0 = Odd parity, 1 = Even parity                              │
│                                                                 │
│  Bit 5: Stick Parity                                            │
│    0 = Normal, 1 = Stick parity (forced)                        │
│                                                                 │
│  Bit 6: Set Break                                               │
│    1 = Force TX to logic 0 (break condition)                    │
│                                                                 │
│  Bit 7 (DLAB): Divisor Latch Access                             │
└─────────────────────────────────────────────────────────────────┘

5. MCR (Offset 0x04) - Modem Control Register

┌─────────────────────────────────────────────────────────────────┐
│  MCR - Modem Control Register (R/W)                             │
│  ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐    │
│  │ Bit7 │ Bit6 │ Bit5 │ Bit4 │ Bit3 │ Bit2 │ Bit1 │ Bit0 │    │
│  ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤    │
│  │  0   │  0   │  0   │  0   │  0   │ LOOP │ OUT2 │ OUT1 │    │
│  │      │      │      │      │      │ BACK │      │      │    │
│  └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘    │
│         └────────────────────┬─────────────────┬──────┬──────┘    │
│                              │                 │      └──┐        │
│  Bit 0: OUT1 - Auxiliary Output 1                                │
│  Bit 1: OUT2 - Auxiliary Output 2 (enables interrupts)         │
│  Bit 4: LOOP - Loopback Mode (for testing)                      │
└─────────────────────────────────────────────────────────────────┘

6. LSR (Offset 0x05) - Line Status Register MOST IMPORTANT

┌─────────────────────────────────────────────────────────────────┐
│  LSR - Line Status Register (Read) ⭐                            │
│  ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐    │
│  │ Bit7 │ Bit6 │ Bit5 │ Bit4 │ Bit3 │ Bit2 │ Bit1 │ Bit0 │    │
│  ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤    │
│  │FIFO  │ TEMT │ THRE │  BI  │  FE  │  PE  │  OE  │ DR   │    │
│  │Err   │      │      │      │      │      │      │      │    │
│  └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘    │
│                                                                 │
│  Bit 0 (DR): Data Ready                                         │
│    0 = No data in RBR, 1 = Data received in RBR                │
│                                                                 │
│  Bit 1 (OE): Overrun Error                                      │
│    1 = Received data overwritten before being read             │
│                                                                 │
│  Bit 2 (PE): Parity Error                                       │
│    1 = Parity error detected on received data                  │
│                                                                 │
│  Bit 3 (FE): Framing Error                                      │
│    1 = No valid stop bit received                               │
│                                                                 │
│  Bit 4 (BI): Break Interrupt                                    │
│    1 = RX line held low longer than one character              │
│                                                                 │
│  Bit 5 (THRE): Transmitter Holding Register Empty ⭐             │
│    0 = THR full (still transmitting), 1 = THR empty (ready)    │
│    → Set to 1 immediately after OUT instruction                  │
│    → Cleared to 0 when CPU writes to THR                        │
│                                                                 │
│  Bit 6 (TEMT): Transmitter Empty                               │
│    0 = Transmitting in progress, 1 = Transmitter AND THR empty │
│    → Set to 1 only when both THR and shift register are empty  │
│                                                                 │
│  Bit 7: FIFO Data Error (16550A FIFO mode only)                │
│    1 = At least one error in FIFO                               │
└─────────────────────────────────────────────────────────────────┘

  Common LSR Values and Meaning:
  ┌──────────┬────────────────────────────────────────────────┐
  │  Value   │  Meaning                                       │
  ├──────────┼────────────────────────────────────────────────┤
  │  0x00    │  All clear, nothing ready                      │
  │  0x01    │  Data ready in RBR (DR=1)                      │
  │  0x20    │  THR empty, ready to transmit (THRE=1)        │
  │  0x40    │  Transmitter completely empty (TEMT=1)        │
  │  0x60    │  THRE + TEMT = 1 (ready & idle) ⭐             │
  │  0x61    │  Data ready AND THR empty                     │
  └──────────┴────────────────────────────────────────────────┘

7. MSR (Offset 0x06) - Modem Status Register

┌─────────────────────────────────────────────────────────────────┐
│  MSR - Modem Status Register (Read)                             │
│  ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐    │
│  │ Bit7 │ Bit6 │ Bit5 │ Bit4 │ Bit3 │ Bit2 │ Bit1 │ Bit0 │    │
│  ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤    │
│  │ DCD  │ RI   │ DSR  │ CTS  │ΔDCD  │ΔRI   │ΔDSR  │ΔCTS  │    │
│  │      │      │      │      │      │      │      │      │    │
│  └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘    │
│                                                                 │
│  Bits 0-3: Delta bits (changed since last read)                │
│  Bits 4-7: Current state of modem control lines                │
│    DCD: Data Carrier Detect                                    │
│    RI: Ring Indicator                                          │
│    DSR: Data Set Ready                                        │
│    CTS: Clear To Send                                          │
└─────────────────────────────────────────────────────────────────┘

8. SCR (Offset 0x07) - Scratch Register

┌─────────────────────────────────────────────────────────────────┐
│  SCR - Scratch Register (R/W)                                   │
│  General-purpose 8-bit storage for software use                 │
│  Often used to test UART presence:                              │
│    1. Write value (e.g., 0xA5) to SCR                           │
│    2. Read back from SCR                                        │
│    3. If value matches, UART is present                         │
└─────────────────────────────────────────────────────────────────┘

I/O Port Addresses

x86 architecture has a separate I/O address space (64KB, ports 0x0000-0xFFFF) distinct from memory. UART uses this I/O space.

Standard COM Port Assignments

┌─────────────────────────────────────────────────────────────────┐
│              Standard x86 COM Port Assignments                   │
├──────────┬───────────────┬───────────────┬──────────────────────┤
│   Port   │   I/O Range   │   IRQ         │      Description     │
├──────────┼───────────────┼───────────────┼──────────────────────┤
│  COM1    │  0x3F8-0x3FF  │   IRQ 4       │  Primary Serial      │
│  COM2    │  0x2F8-0x2FF  │   IRQ 3       │  Secondary Serial    │
│  COM3    │  0x3E8-0x3EF  │   IRQ 4       │  Tertiary Serial     │
│  COM4    │  0x2E8-0x2EF  │   IRQ 3       │  Quaternary Serial   │
└──────────┴───────────────┴───────────────┴──────────────────────┘

Note: Your hypervisor handles BOTH 0x3F8-0x3FF AND 0xF8-0xFF
      (0xF8 is likely a short-address decode quirk in KVM)

I/O Port to Register Mapping for COM1 (0x3F8 base)

┌─────────────────────────────────────────────────────────────────┐
│           COM1 Register Map (Base 0x3F8)                         │
├─────────────┬───────────────┬──────────────────────────────────┤
│ I/O Port    │ Register      │ Access                           │
├─────────────┼───────────────┼──────────────────────────────────┤
│  0x3F8      │ RBR/THR/DLL   │ Read/Write                       │
│  0x3F9      │ IER/DLM       │ Read/Write                       │
│  0x3FA      │ IIR           │ Read                             │
│  0x3FA      │ FCR           │ Write                            │
│  0x3FB      │ LCR           │ Read/Write                       │
│  0x3FC      │ MCR           │ Read/Write                       │
│  0x3FD      │ LSR           │ Read                             │
│  0x3FE      │ MSR           │ Read                             │
│  0x3FF      │ SCR           │ Read/Write                       │
└─────────────┴───────────────┴──────────────────────────────────┘

How Serial Port Works in Your Hypervisor

Architecture Overview

┌──────────────────────────────────────────────────────────────────┐
│                    Your Hypervisor Architecture                   │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    Guest OS (Payload)                    │    │
│  │                                                         │    │
│  │  payload.asm executes:                                  │    │
│  │  ┌──────────────────────────────────────────────────┐  │    │
│  │  │  in al, 0x3FD    ; Read LSR register             │  │    │
│  │  │  out 0x3F8, al   ; Write character to THR        │  │    │
│  │  └──────────────────────┬───────────────────────────┘  │    │
│  │                         │                                │    │
│  └─────────────────────────┼────────────────────────────────┘    │
│                            │ KVM intercepts I/O instructions     │
│                            ▼                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                         KVM                              │    │
│  │                                                         │    │
│  │  KVM_RUN exits with:                                    │    │
│  │  run->exit_reason = KVM_EXIT_IO                         │    │
│  │  run->io.port = 0x3F8 or 0x3FD                          │    │
│  │  run->io.direction = KVM_EXIT_IO_IN or OUT              │    │
│  │  run->io.data_offset → points to data buffer            │    │
│  │                                                         │    │
│  └─────────────────────────┬───────────────────────────────┘    │
│                            │                                     │
│                            ▼                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              Your VMM (vmm.c)                           │    │
│  │                                                         │    │
│  │  while(1) {                                             │    │
│  │    KVM_RUN                                              │    │
│  │    switch(run->exit_reason) {                           │    │
│  │      case KVM_EXIT_IO:                                  │    │
│  │        handle_serial_io(run);  // serial.c              │    │
│  │    }                                                    │    │
│  │  }                                                      │    │
│  └─────────────────────────┬───────────────────────────────┘    │
│                            │                                     │
│                            ▼                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │            handle_serial_io() in serial.c               │    │
│  │                                                         │    │
│  │  uint8_t *io_data = (uint8_t *)run + run->io.data_offset│    │
│  │  int offset = run->io.port & 0x7;  // register select   │    │
│  │                                                         │    │
│  │  if (OUT) {                                             │    │
│  │    if (offset == 0) putchar(*io_data);  // Print char!  │    │
│  │  } else {                                                 │    │
│  │    switch(offset) {                                     │    │
│  │      case 0: *io_data = 0x00;  // RBR: no data          │    │
│  │      case 5: *io_data = 0x60;  // LSR: THRE+TEMT=1     │    │
│  │      ...                                                │    │
│  │    }                                                    │    │
│  │  }                                                      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                            │                                     │
│                            ▼                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              Host Console (stdout)                       │    │
│  │  putchar() → "Hello yo\n" displayed on your terminal    │    │
│  └─────────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────────┘

Why This Approach Works

  1. No Hardware Required: Your hypervisor emulates UART in software
  2. KVM Intercepts: When guest executes IN/OUT, KVM traps to your VMM
  3. Software Simulation: You return fake register values that make guest think UART exists
  4. Character Output: When guest writes to THR (0x3F8), you just putchar() to host console

Detailed Instruction Examples

Example 1: Reading LSR (Line Status Register)

; From payload.asm:
in al, 0x3FD          ; Read LSR register

Step-by-step CPU execution:

┌──────────────────────────────────────────────────────────────────┐
│  Instruction: IN AL, 0x3FD                                       │
│  Machine Code: E5 FD                                             │
│                                                                  │
│  Step 1: CPU Decode                                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  CPU fetches instruction from memory at RIP              │    │
│  │  Decodes as: IN instruction, port=0x3FD, dest=AL        │    │
│  └────────────────────┬────────────────────────────────────┘    │
│                       │                                          │
│  Step 2: I/O Access Attempt                                    │
│  ┌────────────────────┴────────────────────────────────────┐    │
│  │  CPU attempts I/O read on port 0x3FD                    │    │
│  │  KVM has configured I/O trapping for this port          │    │
│  │  → VM Exit triggered!                                    │    │
│  └────────────────────┬────────────────────────────────────┘    │
│                       │                                          │
│  Step 3: KVM Exit to VMM                                       │
│  ┌────────────────────┴────────────────────────────────────┐    │
│  │  kvm_run structure populated:                            │    │
│  │  ┌──────────────────────────────────────────────────┐  │    │
│  │  │  run->exit_reason = KVM_EXIT_IO (value: 2)       │  │    │
│  │  │  run->io.direction = KVM_EXIT_IO_IN (value: 0)   │  │    │
│  │  │  run->io.size = 1  (1 byte access)                │  │    │
│  │  │  run->io.port = 0x3FD                             │  │    │
│  │  │  run->io.count = 1                                │  │    │
│  │  └──────────────────────────────────────────────────┘  │    │
│  └────────────────────┬────────────────────────────────────┘    │
│                       │                                          │
│  Step 4: VMM Handler (serial.c)                                │
│  ┌────────────────────┴────────────────────────────────────┐    │
│  │  handle_serial_io(run) called:                          │    │
│  │                                                         │    │
│  │  offset = run->io.port & 0x7 = 0x3FD & 0x7 = 5        │    │
│  │  direction = KVM_EXIT_IO_IN (read)                      │    │
│  │                                                         │    │
│  │  Switch case 5:                                         │    │
│  │    *io_data = 0x60  ← THIS IS THE MAGIC VALUE          │    │
│  │                                                         │    │
│  │  Memory layout at io_data:                              │    │
│  │  ┌──────────────────────────────────────────────────┐  │    │
│  │  │  Before: [??] (undefined)                         │  │    │
│  │  │  After:  [0x60] = 0110 0000 binary               │  │    │
│  │  └──────────────────────────────────────────────────┘  │    │
│  │                                                         │    │
│  │  0x60 = 0110 0000 binary                               │    │
│  │       Bit 6 (TEMT) = 1  → Transmitter empty           │    │
│  │       Bit 5 (THRE) = 1  → THR empty, ready to send    │    │
│  └────────────────────┬────────────────────────────────────┘    │
│                       │                                          │
│  Step 5: Return to Guest                                       │
│  ┌────────────────────┴────────────────────────────────────┐    │
│  │  KVM copies io_data value (0x60) into guest's AL register│    │
│  │  Guest resumes execution with AL = 0x60                 │    │
│  │                                                         │    │
│  │  Guest code continues:                                  │    │
│  │  test al, 0x20        ; Check THRE bit (bit 5)          │    │
│  │  ; 0x60 & 0x20 = 0x20 → ZF = 0 (not zero)              │    │
│  │  jz .wait             ; NOT taken, continue sending!    │    │
│  └─────────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────────┘

Example 2: Writing Character to THR

; From payload.asm:
mov al, 'H'             ; Load character 'H' (0x48)
out 0x3F8, al           ; Write to THR

Step-by-step execution:

┌──────────────────────────────────────────────────────────────────┐
│  Instruction: OUT 0x3F8, AL                                      │
│  Machine Code: E6 F8 48  (assuming AL=0x48='H')                 │
│                                                                  │
│  Step 1: CPU State Before                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  AL = 0x48 (ASCII 'H' = 0100 1000 binary)               │    │
│  │  RIP points to OUT instruction                           │    │
│  └────────────────────┬────────────────────────────────────┘    │
│                       │                                          │
│  Step 2: CPU Executes OUT                                      │
│  ┌────────────────────┴────────────────────────────────────┐    │
│  │  CPU attempts I/O write to port 0x3F8 with value 0x48   │    │
│  │  → VM Exit triggered!                                    │    │
│  └────────────────────┬────────────────────────────────────┘    │
│                       │                                          │
│  Step 3: KVM Exit to VMM                                       │
│  ┌────────────────────┴────────────────────────────────────┐    │
│  │  run->exit_reason = KVM_EXIT_IO                          │    │
│  │  run->io.direction = KVM_EXIT_IO_OUT (value: 1)         │    │
│  │  run->io.port = 0x3F8                                    │    │
│  │  run->io.size = 1                                        │    │
│  │                                                         │    │
│  │  io_data points to data buffer containing 0x48          │    │
│  └────────────────────┬────────────────────────────────────┘    │
│                       │                                          │
│  Step 4: VMM Handler (serial.c)                                │
│  ┌────────────────────┴────────────────────────────────────┐    │
│  │  handle_serial_io(run) called:                          │    │
│  │                                                         │    │
│  │  offset = run->io.port & 0x7 = 0x3F8 & 0x7 = 0        │    │
│  │  direction = KVM_EXIT_IO_OUT (write)                    │    │
│  │                                                         │    │
│  │  if (offset == 0) {  // THR register                   │    │
│  │    putchar((char)*io_data);  // *io_data = 0x48        │    │
│  │    fflush(stdout);                                      │    │
│  │  }                                                      │    │
│  │                                                         │    │
│  │  Output to host terminal:                                │    │
│  │  ┌──────────────────────────────────────────────────┐  │    │
│  │  │  'H' character appears on your terminal!          │  │    │
│  │  └──────────────────────────────────────────────────┘  │    │
│  └────────────────────┬────────────────────────────────────┘    │
│                       │                                          │
│  Step 5: Return to Guest                                       │
│  ┌────────────────────┴────────────────────────────────────┐    │
│  │  Guest resumes execution                                 │    │
│  │  In real hardware, LSR THRE bit would now be set        │    │
│  │  Our emulation always returns THRE=1, so guest thinks   │    │
│  │  UART is always ready (simplified but works!)           │    │
│  └─────────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────────┘

Why Value 0x60 Works

┌──────────────────────────────────────────────────────────────────┐
│  LSR = 0x60 = 0110 0000 binary                                   │
│                                                                  │
│  Bit 7  (FIFO Error)  = 0  → No FIFO errors                     │
│  Bit 6  (TEMT)        = 1  → Transmitter AND THR empty          │
│  Bit 5  (THRE)        = 1  → THR empty, ready for new data      │
│  Bit 4  (Break Int)   = 0  → No break condition                 │
│  Bit 3  (Frame Error) = 0  → No framing error                   │
│  Bit 2  (Parity Error)= 0  → No parity error                    │
│  Bit 1  (Overrun Err) = 0  → No overrun error                   │
│  Bit 0  (Data Ready)  = 0  → No data in receive buffer          │
│                                                                  │
│  This is the "ideal" state: UART is ready to transmit!           │
│  Guest code checks THRE (bit 5) with: test al, 0x20             │
│  0x60 & 0x20 = 0x20 (non-zero) → ZF=0 → transmitter ready!      │
└──────────────────────────────────────────────────────────────────┘

Memory Map and Architecture Diagram

Complete x86 I/O and Memory Map

┌──────────────────────────────────────────────────────────────────┐
│                    x86 Address Space Overview                     │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              MEMORY SPACE (4GB in 32-bit)                │    │
│  │                                                         │    │
│  │  0x00000000 ┌─────────────────────────────────────┐    │    │
│  │             │         RAM (Your VM: 4MB)           │    │    │
│  │             │  0x00000000 - 0x003FFFFF             │    │    │
│  │             │  ┌───────────────────────────────┐   │    │    │
│  │  0x00100000 │  │ Your payload.bin loaded here  │   │    │    │
│  │             │  │ (RIP starts at 0x100000)       │   │    │    │
│  │             │  └───────────────────────────────┘   │    │    │
│  │  0x003FFFFF └─────────────────────────────────────┘    │    │
│  │             ... (rest unused in your simple VM)        │    │
│  │  0xFFFFFFFF                                             │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              I/O SPACE (64KB: 0x0000-0xFFFF)             │    │
│  │                                                         │    │
│  │  0x0000 ┌──────────────────────────────────────────┐   │    │
│  │         │  DMA Controller, PIC, PIT, etc.          │   │    │
│  │  0x0020 │  (Not emulated in your hypervisor)       │   │    │
│  │         │                                          │   │    │
│  │  0x00F8 │  ┌──────────────────────────────────┐   │   │    │
│  │  0x00FF │  │ Serial Port (ALT - COM1 alias)   │   │   │    │
│  │         │  │ Your hypervisor handles this!    │   │   │    │
│  │         │  └──────────────────────────────────┘   │   │    │
│  │         │                                          │   │    │
│  │  0x02F8 │  ┌──────────────────────────────────┐   │   │    │
│  │  0x02FF │  │ COM2 (Not emulated)              │   │   │    │
│  │         │  └──────────────────────────────────┘   │   │    │
│  │         │                                          │   │    │
│  │  0x03E8 │  ┌──────────────────────────────────┐   │   │    │
│  │  0x03EF │  │ COM3 (Not emulated)              │   │   │    │
│  │         │  └──────────────────────────────────┘   │   │    │
│  │         │                                          │   │    │
│  │  0x03F8 │  ┌──────────────────────────────────┐   │   │    │
│  │  0x03FF │  │  COM1 (Primary Serial) ⭐          │   │   │    │
│  │         │  │  Your hypervisor handles this!    │   │   │    │
│  │         │  │  ┌────────────────────────────┐  │   │   │    │
│  │         │  │  │ 0x3F8: THR/RBR             │  │   │   │    │
│  │         │  │  │ 0x3F9: IER/DLM             │  │   │   │    │
│  │         │  │  │ 0x3FA: IIR/FCR             │  │   │   │    │
│  │         │  │  │ 0x3FB: LCR                 │  │   │   │    │
│  │         │  │  │ 0x3FC: MCR                 │  │   │   │    │
│  │         │  │  │ 0x3FD: LSR ← 0x60          │  │   │   │    │
│  │         │  │  │ 0x3FE: MSR                 │  │   │   │    │
│  │         │  │  │ 0x3FF: SCR                 │  │   │   │    │
│  │         │  │  └────────────────────────────┘  │   │   │    │
│  │         │  └──────────────────────────────────┘   │   │    │
│  │         │                                          │   │    │
│  │  0x0378 │  ┌──────────────────────────────────┐   │   │    │
│  │  0x037F │  │ Parallel Port (LPT1)             │   │   │    │
│  │         │  └──────────────────────────────────┘   │   │    │
│  │  0xFFFF └──────────────────────────────────────────┘   │    │
│  └─────────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────────┘

UART Internal Architecture (Hardware Level)

┌──────────────────────────────────────────────────────────────────┐
│                  UART 16550A Internal Block Diagram               │
│                                                                  │
│         CPU Side (I/O Bus)                    Serial Line Side    │
│  ┌────────────────────────┐                   ┌──────────────┐  │
│  │                        │                   │              │  │
│  │  ┌──────────────────┐  │    ┌───────────┐  │   TX Pin     ├──┼──► To Serial Cable
│  │  │   I/O Decoder    │  │    │           │  │              │  │
│  │  │  (Selects Reg)   │  │    │   TX      │  │              │  │
│  │  └────────┬─────────┘  │    │  Shift    │  │   RX Pin     │  │
│  │           │            │    │ Register  │  │              ├──┼──◄ From Serial Cable
│  │  ┌────────┴─────────┐  │    └─────┬─────┘  │              │  │
│  │  │   Data Bus       │  │          │        │              │  │
│  │  │   (8 bits)       │  │    ┌─────▼─────┐  └──────────────┘  │
│  │  └────────┬─────────┘  │    │   RX      │                    │
│  │           │            │    │  Shift    │                    │
│  │  ┌────────▼─────────┐  │    │ Register  │                    │
│  │  │ Register File    │  │    └─────┬─────┘                    │
│  │  │ ┌──┬──┬──┬──┬──┐ │  │          │                           │
│  │  │ │RBR│IER│IIR│LCR│ │  │    ┌─────▼─────┐                    │
│  │  │ │THR│   │FCR│MCR│ │  │    │   RX      │                    │
│  │  │ │DLL│DLM│   │   │ │  │    │   FIFO     │                    │
│  │  │ │LSR│MSR│SCR│  │ │  │    │ (16 bytes)  │                    │
│  │  │ └──┴──┴──┴──┴──┘ │  │    └─────────────┘                    │
│  │  └──────────────────┘  │                                       │
│  │                        │    ┌──────────────────────────┐      │
│  │  ┌──────────────────┐  │    │   Baud Rate Formula:      │      │
│  │  │  Baud Rate       │  │    │                           │      │
│  │  │  Generator       │  │    │  Divisor =                │      │
│  │  │  (DLL + DLM)     │  │    │  115200 / Baud Rate       │      │
│  │  │                  │  │    │                           │      │
│  │  │  Divisor =       │  │    │  Example: 115200 baud     │      │
│  │  │  115200 / Target │  │    │  Divisor = 115200/115200  │      │
│  │  └──────────────────┘  │    │  Divisor = 1 = 0x0001     │      │
│  │                        │    │                           │      │
│  │  ┌──────────────────┐  │    │  Example: 9600 baud       │      │
│  │  │  Interrupt Logic │  │    │  Divisor = 115200/9600    │      │
│  │  │  (IIR + IER)     │  │    │  Divisor = 12 = 0x000C    │      │
│  │  └──────────────────┘  │    │                           │      │
│  │                        │    └──────────────────────────┘      │
│  └────────────────────────┘                                       │
└──────────────────────────────────────────────────────────────────┘

CPU and UART Communication Flow

Complete Execution Flow for "Hello yo" Output

┌──────────────────────────────────────────────────────────────────┐
│              Complete Execution Flow (First Character 'H')        │
│                                                                  │
│  Guest (payload.asm)                    Host (vmm.c + serial.c) │
│  ┌────────────────────────┐            ┌──────────────────────┐ │
│  │                        │            │                      │ │
│  │  mov esi, hello_str    │            │                      │ │
│  │  movzx eax, byte [esi] │            │                      │ │
│  │  ; AL = 'H' (0x48)     │            │                      │ │
│  │                        │            │                      │ │
│  │  ; Check LSR:          │            │                      │ │
│  │  in al, 0x3FD          │            │                      │ │
│  │    │                    │            │                      │ │
│  │    │ VM Exit (KVM)     │───────────►│                      │ │
│  │    │                    │            │  handle_serial_io()  │ │
│  │    │                    │            │  offset = 0x3FD&7=5  │ │
│  │    │                    │            │  *io_data = 0x60     │ │
│  │    │                    │            │  (THRE+TEMT set)     │ │
│  │    │◄───────────────────│            │                      │ │
│  │    │ VM Resume         │            │                      │ │
│  │                        │            │                      │ │
│  │  ; AL = 0x60 now       │            │                      │ │
│  │  test al, 0x20         │            │                      │ │
│  │  ; 0x60 & 0x20 = 0x20 │            │                      │ │
│  │  jz .wait              │            │                      │ │
│  │  ; NOT taken (ZF=0)    │            │                      │ │
│  │                        │            │                      │ │
│  │  mov al, [esi]         │            │                      │ │
│  │  ; AL = 'H' (0x48)     │            │                      │ │
│  │  out 0x3F8, al         │            │                      │ │
│  │    │                    │            │                      │ │
│  │    │ VM Exit (KVM)     │───────────►│                      │ │
│  │    │                    │            │  handle_serial_io()  │ │
│  │    │                    │            │  offset = 0x3F8&7=0  │ │
│  │    │                    │            │  direction = OUT     │ │
│  │    │                    │            │  putchar('H') ★     │ │
│  │    │                    │            │  fflush(stdout)      │ │
│  │    │                    │            │                      │ │
│  │    │                    │            │  Terminal shows: H   │ │
│  │    │◄───────────────────│            │                      │ │
│  │    │ VM Resume         │            │                      │ │
│  │                        │            │                      │ │
│  │  inc esi               │            │                      │ │
│  │  jmp .loop             │            │                      │ │
│  │  ; Repeat for 'e'...   │            │                      │ │
│  │                        │            │                      │ │
│  └────────────────────────┘            └──────────────────────┘ │
│                                                                  │
│  Final Terminal Output: "Hello yo\n"                            │
└──────────────────────────────────────────────────────────────────┘

Extra UART Ports for Linux Kernel x86

Linux kernel on x86 expects up to 4 COM ports. To boot Linux, you need to emulate more than just COM1.

Required UART Ports for Full Linux Support

┌──────────────────────────────────────────────────────────────────┐
│          UART Ports Needed for Linux Kernel (x86)                │
├─────────┬──────────────┬──────────┬─────────────────────────────┤
│  Port   │ I/O Range    │ IRQ      │ Priority for Linux Boot     │
├─────────┼──────────────┼──────────┼─────────────────────────────┤
│  COM1   │ 0x3F8-0x3FF  │ IRQ 4    │ ★★★★ Essential (default    │
│         │              │          │   console: ttyS0)           │
├─────────┼──────────────┼──────────┼─────────────────────────────┤
│  COM2   │ 0x2F8-0x2FF  │ IRQ 3    │ ★★★ Useful (ttyS1)         │
├─────────┼──────────────┼──────────┼─────────────────────────────┤
│  COM3   │ 0x3E8-0x3EF  │ IRQ 4    │ ★★ Optional (ttyS2)        │
├─────────┼──────────────┼──────────┼─────────────────────────────┤
│  COM4   │ 0x2E8-0x2EF  │ IRQ 3    │ ★ Optional (ttyS3)         │
└─────────┴──────────────┴──────────┴─────────────────────────────┘

Note: Linux kernel can boot with just COM1 (ttyS0).
      For minimal kernel: COM1 only is sufficient!

What Linux Kernel Expects

When Linux kernel boots with console=ttyS0,115200, it:

┌──────────────────────────────────────────────────────────────────┐
│              Linux Kernel Serial Initialization                   │
│                                                                  │
│  1. Kernel boot with: console=ttyS0,115200n8                     │
│     │                                                            │
│     ▼                                                            │
│  2. 8250 serial driver probes COM ports                          │
│     ┌────────────────────────────────────────────────────────┐  │
│     │  Probe sequence for each COM port:                     │  │
│     │                                                         │  │
│     │  a) Write 0xFF to LCR (offset 3)                       │  │
│     │  b) Read back LCR                                     │  │
│     │  c) If value == 0xFF, UART might exist                │  │
│     │                                                         │  │
│     │  d) Write test pattern to SCR (offset 7)               │  │
│     │  e) Read back SCR                                     │  │
│     │  f) If value matches, UART confirmed!                 │  │
│     │                                                         │  │
│     │  g) Try to enable FIFO with FCR (offset 2)            │  │
│     │  h) Read IIR to check FIFO status                     │  │
│     │                                                         │  │
│     │  i) Calculate UART type (8250, 16450, 16550A, etc.)   │  │
│     └────────────────────────────────────────────────────────┘  │
│     │                                                            │
│     ▼                                                            │
│  3. Register ttyS0 device                                        │
│     ┌────────────────────────────────────────────────────────┐  │
│     │  Configure UART:                                       │  │
│     │  - Set baud rate (DLL/DLM)                             │  │
│     │  - Set line format (LCR: 8N1 = 8 bits, No parity, 1 stop)│
│     │  - Enable interrupts (IER)                             │  │
│     │  - Enable FIFO (FCR)                                   │  │
│     └────────────────────────────────────────────────────────┘  │
│     │                                                            │
│     ▼                                                            │
│  4. Console ready for printk() output                            │
└──────────────────────────────────────────────────────────────────┘

Implementation Guide for Additional Ports

To support COM2, COM3, COM4, modify your vmm.c:

// Current code handles:
if ((run->io.port >= 0x3F8 && run->io.port <= 0x3FF) ||
    (run->io.port >= 0xF8 && run->io.port <= 0xFF)) {
    handle_serial_io(run);
}

// Need to add for full Linux support:
if ((run->io.port >= 0x3F8 && run->io.port <= 0x3FF) ||  // COM1
    (run->io.port >= 0x2F8 && run->io.port <= 0x2FF) ||  // COM2
    (run->io.port >= 0x3E8 && run->io.port <= 0x3EF) ||  // COM3
    (run->io.port >= 0x2E8 && run->io.port <= 0x2EF) ||  // COM4
    (run->io.port >= 0xF8 && run->io.port <= 0xFF)) {    // COM1 alias
    handle_serial_io(run);
}

Note: All COM ports can share the same handler function since they have identical register layouts at different base addresses!


Virtualization Strategy

How Your Hypervisor Virtualizes UART

┌──────────────────────────────────────────────────────────────────┐
│              UART Virtualization Strategy                         │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              TRAPPING (Current Approach)                 │    │
│  │                                                         │    │
│  │  Guest executes IN/OUT → VM Exit → VMM handles → Resume │    │
│  │                                                         │    │
│  │  Pros: Simple, works without hardware                   │    │
│  │  Cons: High overhead (VM Exit per I/O instruction)      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              SIMULATION (Your serial.c)                  │    │
│  │                                                         │    │
│  │  No real UART hardware needed!                          │    │
│  │  Return hardcoded values that make guest happy:         │    │
│  │                                                         │    │
│  │  LSR = 0x60  → "I'm ready to transmit"                  │    │
│  │  RBR = 0x00  → "No input data available"                │    │
│  │  IIR = 0x01  → "No interrupt pending"                   │    │
│  │                                                         │    │
│  │  This is "polling mode" emulation - guest polls LSR     │    │
│  │  instead of using interrupts. Perfect for simple boot!  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              ADVANCED (For Linux Support)                │    │
│  │                                                         │    │
│  │  To boot Linux, you'll need:                            │    │
│  │                                                         │    │
│  │  1. Proper SCR read/write (UART detection)              │    │
│  │  2. LCR read/write (line configuration)                 │    │
│  │  3. DLL/DLM support (baud rate setting)                 │    │
│  │  4. FCR write (FIFO enable)                             │    │
│  │  5. IER read (interrupt enable)                         │    │
│  │  6. MCR read/write (modem control)                      │    │
│  │  7. Interrupt injection (KVM_INTERRUPT)                 │    │
│  │                                                         │    │
│  │  Your current code only handles:                        │    │
│  │  - THR write (offset 0, OUT) ✓                         │    │
│  │  - RBR read (offset 0, IN) ✓                           │    │
│  │  - LSR read (offset 5) ✓                               │    │
│  │  - IIR read (offset 2) ✓                               │    │
│  │                                                         │    │
│  │  Missing for Linux: SCR, LCR, DLL/DLM, FCR, IER, MCR  │    │
│  └─────────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────────┘

Minimal Implementation for Linux Boot

To boot Linux with console=ttyS0, you need to add:

// In serial.c, expand handle_serial_io():

// Add a scratch register (persists across calls)
static uint8_t uart_scr = 0x00;
static uint8_t uart_lcr = 0x00;
static uint8_t uart_ier = 0x00;
static uint8_t uart_mcr = 0x00;
static uint16_t uart_divisor = 0x00C;  // Default 9600 baud

void handle_serial_io(struct kvm_run *run) {
  uint8_t *io_data = (uint8_t *)run + run->io.data_offset;
  int offset = run->io.port & 0x7;
  int is_dlab = uart_lcr & 0x80;  // Check DLAB bit

  if (run->io.direction == KVM_EXIT_IO_OUT) {
    switch (offset) {
      case 0:  // THR or DLL
        if (is_dlab) {
          uart_divisor = (uart_divisor & 0xFF00) | *io_data;
        } else {
          putchar((char)*io_data);
          fflush(stdout);
        }
        break;
      case 1:  // IER or DLM
        if (is_dlab) {
          uart_divisor = (uart_divisor & 0x00FF) | (*io_data << 8);
        } else {
          uart_ier = *io_data;
        }
        break;
      case 2:  // FCR
        // Just acknowledge FIFO enable
        break;
      case 3:  // LCR
        uart_lcr = *io_data;
        break;
      case 4:  // MCR
        uart_mcr = *io_data;
        break;
      case 7:  // SCR
        uart_scr = *io_data;
        break;
    }
  } else {  // IN
    switch (offset) {
      case 0:  // RBR or DLL
        if (is_dlab) {
          *io_data = uart_divisor & 0xFF;
        } else {
          *io_data = 0x00;  // No input
        }
        break;
      case 1:  // IER or DLM
        if (is_dlab) {
          *io_data = (uart_divisor >> 8) & 0xFF;
        } else {
          *io_data = uart_ier;
        }
        break;
      case 2:  // IIR
        *io_data = 0x01;  // No interrupt
        break;
      case 3:  // LCR
        *io_data = uart_lcr;
        break;
      case 4:  // MCR
        *io_data = uart_mcr;
        break;
      case 5:  // LSR
        *io_data = 0x60;  // THRE + TEMT
        break;
      case 6:  // MSR
        *io_data = 0x60;  // DSR + CTS asserted
        break;
      case 7:  // SCR
        *io_data = uart_scr;
        break;
    }
  }
}

Summary

What Your Code Does

  1. Traps I/O instructions (IN/OUT) via KVM
  2. Decodes which UART register is accessed (port & 0x7)
  3. Simulates UART behavior with hardcoded values
  4. Outputs characters to host console via putchar()

Key Values Explained

Register Value Binary Meaning
LSR 0x60 0110 0000 THRE=1 (ready), TEMT=1 (empty)
IIR 0x01 0000 0001 No interrupt pending
RBR 0x00 0000 0000 No received data
MSR 0x00 0000 0000 No modem status

Why It Works

  • Guest polls LSR (doesn't use interrupts)
  • Your hypervisor always returns ready (0x60)
  • Guest writes character → KVM exits → VMM calls putchar() → Character appears
  • No real hardware needed, pure software emulation!

Next Steps for Linux

To boot a real Linux kernel, you need to:

  1. Implement all UART registers (SCR, LCR, DLL/DLM, FCR, IER, MCR)
  2. Support baud rate configuration
  3. Handle UART detection probe (SCR test)
  4. Optionally support interrupts (not just polling)
  5. Add COM2/COM3/COM4 port ranges

References

  • 16550A Datasheet: National Semiconductor (now TI)
  • Intel SDM Vol 3: I/O Instructions (IN, OUT)
  • KVM Documentation: KVM_EXIT_IO exit reason
  • Linux 8250 Driver: drivers/tty/serial/8250/
  • OSDev Wiki: Serial Ports article