Interrupts and Device Drivers

Driver Architecture and Interrupt Handling

  • Drivers manage specific hardware devices by configuring hardware, initiating operations, handling resulting interrupts, and interacting with waiting processes.
  • Device interrupts are a class of traps routed through the kernel’s trap handling logic (e.g., devintr).
  • Driver execution is structured into two concurrent contexts:
    • Top half: Executes within a process’s kernel thread via system calls (e.g., read, write). Asks the hardware to initiate operations and yields the CPU to wait for completion.
    • Bottom half: Executes asynchronously at interrupt time. Identifies completed operations, wakes waiting processes, and issues the next pending hardware command.
  • Separating device management into process-driven top halves and asynchronous bottom halves provides the architectural foundation for handling unpredictable external events, such as console input.

Console Input Mechanism

  • The console driver processes human input via UART serial-port hardware, interacting with an emulated 16550 chip.
  • UART hardware is exposed as memory-mapped control registers starting at physical address UART0 (0x10000000).
    • LSR (Line Status Register): Contains flag bits indicating if unread characters are waiting.
    • RHR (Receive Holding Register): Provides the waiting character; reading it triggers the hardware to dequeue the byte from its internal FIFO.
    • THR (Transmit Holding Register): Accepts software bytes for hardware transmission.
  • Initialization (consoleinit) configures the UART to raise distinct interrupts upon receiving a byte and upon completing a byte transmission.
  • Input execution flow bridges the hardware and the reading process:
    • A process invokes read, entering consoleread, which sleeps waiting for an accumulated line in cons.buf.
    • Hardware receives a character and raises an interrupt, entering the trap handler.
    • devintr queries the PLIC (Platform-Level Interrupt Controller) to identify the device and routes execution to uartintr.
    • uartintr extracts the character from the hardware and passes it to consoleintr.
    • consoleintr buffers characters in cons.buf and processes special sequences (e.g., backspace).
    • Upon detecting a newline, consoleintr wakes the sleeping consoleread thread, which then copies the buffered line to user space.
  • While console input halts a process until external data arrives, console output demonstrates how buffering can unblock processes before slow hardware finishes its work.

Console Output Mechanism

  • A write system call targeted at the console routes to the uartputc function.
  • uartputc appends outgoing characters to a driver-maintained buffer (uart_tx_buf) and invokes uartstart to initiate hardware transmission.
  • The writing process returns immediately without waiting for transmission, yielding to the sleep state only if uart_tx_buf is entirely full.
  • When the UART hardware finishes transmitting a byte, it raises a transmit-complete interrupt:
    • The interrupt invokes uartintr, which routes back to uartstart.
    • uartstart verifies the hardware’s ready state and feeds the next buffered byte into the THR.
  • This buffering architecture achieves I/O concurrency, decoupling high-speed process execution from slow hardware transmission constraints.
  • Decoupling processes from asynchronous hardware interrupts necessitates robust synchronization to maintain data integrity across shared buffers.

Concurrency and Driver Synchronization

  • Driver data structures are vulnerable to three distinct concurrency vectors that require lock protection:
    • Simultaneous execution of top-half routines by multiple processes on different CPUs.
    • Hardware interrupting a CPU while it is mid-execution in a top-half routine.
    • Hardware delivering an interrupt on a secondary CPU concurrently with top-half execution on a primary CPU.
  • Interrupt handlers operate outside the context of the interrupted process.
    • Handlers cannot rely on process-specific state. For instance, invoking copyout is unsafe because the active page table belongs to the arbitrarily interrupted process.
    • Bottom-half handler logic is strictly minimized to moving data and signaling wakeups, deferring complex memory operations to the awakened top-half thread.
  • While device drivers utilize standard supervisor-mode traps for hardware management, the system’s core timekeeping demands specialized, higher-privilege interrupt routing.

Timer Interrupts and Machine Mode

  • Periodic timer interrupts drive the system clock and enforce preemptive thread scheduling via yield.
  • RISC-V architecture mandates that timer interrupts trap into machine mode, executing with full privileges and without virtual memory paging.
  • Timer initialization occurs in start.c before standard kernel execution:
    • Programs the Core-Local Interruptor (CLINT) hardware to trigger at a defined interval.
    • Establishes a dedicated scratch memory area to preserve register state independent of process trap frames.
    • Configures the mtvec register to point to timervec and enables machine-mode interrupts.
  • Timer interrupts execute without disturbing supervisor-mode kernel code:
    • timervec saves critical registers to the machine-mode scratch area.
    • Programs the CLINT for the subsequent timer interval.
    • Triggers a RISC-V software interrupt and executes an immediate return.
    • The hardware delivers the software interrupt to supervisor mode via the standard trap mechanism (devintr), safely invoking the kernel scheduler without violating machine-mode isolation.
  • The delegation of timer events via software interrupts exemplifies one specific hardware interaction model; varied hardware interfaces require distinct optimization strategies.

Real-World Driver Optimization Techniques

  • Programmed I/O: The driver explicitly reads and writes hardware control registers byte-by-byte (as seen in the UART driver). This is structurally simple but incurs massive CPU overhead at high data rates.
  • Direct Memory Access (DMA): Hardware reads and writes directly to physical RAM. The driver populates memory buffers and issues a single control command to trigger bulk data transfer. DMA is standard for modern disk and network controllers.
  • Interrupt Mitigation vs. Polling:
    • Interrupts carry high CPU context-switch overhead, making them detrimental for continuous, high-speed data streams.
    • High-throughput drivers batch events, raising a single interrupt for an entire queue of completed requests.
    • Under heavy load, drivers disable interrupts entirely and switch to polling (periodically reading device status registers). Dynamic switching between polling and interrupts optimizes CPU utilization based on real-time load.
  • Zero-copy architectures: Eliminate the CPU overhead of moving data between the kernel buffer and user space by directly mapping hardware DMA targets into user-space memory.
  • Out-of-band control: Operations that violate standard byte-stream read/write semantics (e.g., configuring baud rates) are managed via the ioctl system call interface.
  • Real-time execution: Standard kernel architectures cannot guarantee hard real-time bounded response limits due to non-deterministic scheduling and extended critical sections where interrupts are globally disabled.