consoleinit

Sets up the console so the kernel can print and processes can read input.

  • cons.lock spinlock initialized; cons holds the circular input buffer and its lock
  • uartinit programs the 16550a chip, memory-mapped at 0x10000000:
    • interrupts disabled first so nothing fires mid-setup
    • baud rate set to 38400
    • word length set to 8 bits, no parity
    • FIFOs reset and enabled
    • transmit and receive interrupts enabled
    • tx_lock spinlock initialized
    • tx_lock, tx_busy, tx_chan used to synchronize interrupt-driven transmit
  • consoleread and consolewrite registered into devsw[CONSOLE]

consoleread requires consoleinit, uartinit, procinit, kvminithart, and a current process

  • Console device read handler.
  • Uses acquire(&cons.lock) and release(&cons.lock) to protect console input state.
  • Waits for consoleintr to commit input into cons.buf by advancing cons.w.
  • cons.lock protects the shared input indexes r, w, and e.
  • If no input is ready, calls killed(myproc()) before sleeping.
  • myproc() comes from the process subsystem; internally it disables interrupts, uses mycpu() to find this hart’s struct cpu, reads c->proc, and restores the interrupt state.
  • killed(p) checks whether the current process has been marked for termination.
  • If the process is killed while waiting, consoleread releases cons.lock and returns -1.
  • If no input is ready and the process is still alive, sleep(&cons.r, &cons.lock) blocks the reader and atomically releases cons.lock while sleeping.
  • consoleintr later calls wakeup(&cons.r) after newline, Ctrl-D, or a full buffer, making sleeping readers runnable again.
  • Copies bytes to the caller with either_copyout(user_dst, dst, &cbuf, 1).
  • either_copyout comes from the process-address-space boundary code; it copies to either a user virtual address or a kernel address depending on user_dst.
  • Newline ends the read because console input is line-oriented.
  • Ctrl-D acts as EOF.
  • A killed process returns -1 while waiting.

consolewrite requires consoleinit, uartinit, kvminithart; user-buffer writes also require a current process page table

  • Console device write handler.
  • Copies output from the caller with either_copyin(buf, user_src, src + i, nn).
  • either_copyin comes from the process/address-space boundary code; it copies from either a user virtual address or a kernel address depending on user_src.
  • Uses a 32-byte kernel buffer for batching.
  • Sends each batch to uartwrite(buf, nn).
  • uartwrite comes from the UART driver and may sleep while waiting for transmit-buffer space and UART transmit interrupts.
  • Returns the number of bytes successfully handed to the UART path.
  • Does not interpret characters because output has no line editing, EOF handling, or kill checks.

consputc [requires uartinit]

  • Low-level console character output helper.
  • Sends one character to the UART with uartputc_sync.
  • Uses the synchronous UART path, so it does not sleep and does not depend on UART transmit interrupts.
  • Safe for interrupt-time use, which matters because consoleintr uses it to echo typed characters and erase backspaces.
  • Used by kernel printing through printf and consputc, and by console input editing through consoleintr.
  • If the character is BACKSPACE, sends \b, space, and \b to visually erase the previous character on the terminal.
  • Otherwise sends the character directly to uartputc_sync(c).

consoleintr [requires consoleinit, uartinit, procinit, trapinithart, and plicinithart]

  • Console input interrupt-side handler.
  • Called by uartintr once for each input character received from the UART.
  • Uses acquire(&cons.lock) and release(&cons.lock) to protect the console input buffer and its indexes.
  • Handles Ctrl-P by calling procdump(), which comes from the process subsystem and prints the current process table.
  • Handles Ctrl-U by deleting characters back to the previous newline, decrementing cons.e, and calling consputc(BACKSPACE) for each erased character.
  • Handles Ctrl-H and Delete by erasing one pending input character if cons.e != cons.w.
  • Converts carriage return \r into newline \n so the console treats Enter consistently.
  • Echoes ordinary accepted input back to the terminal with consputc(c).
  • Stores accepted input in the circular buffer at cons.buf[cons.e++ % INPUT_BUF_SIZE].
  • Commits input by setting cons.w = cons.e when a newline, Ctrl-D, or full input buffer is reached.
  • Calls wakeup(&cons.r) after committing input, which wakes processes sleeping in consoleread.
  • Ignores zero characters and drops input when the circular buffer already holds INPUT_BUF_SIZE unconsumed bytes.

UART Device Driver

---
config:
  layout: dagre
---
flowchart LR
 subgraph P["caller side"]
    direction TB
        USER_IO["user process<br><br>read(fd)<br>write(fd)"]
        KERNEL_OUT["kernel output<br><br>printf<br>console echo"]
  end
 subgraph C["console boundary"]
    direction TB
        DEVSW["devsw[CONSOLE]<br><br>read = consoleread<br>write = consolewrite"]
        CONSOLE["console.c<br><br>line buffering<br>consoleintr(c)<br>consoleread / consolewrite"]
  end
 subgraph U["UART driver interface"]
    direction TB
        UARTINIT["uartinit()<br><br>configure 16550a UART<br>enable RX/TX interrupts"]
        UARTOUT["UART output path<br><br>uartwrite<br>uartputc_sync"]
        UARTINTR["uartintr()<br><br>handle RX-ready<br>handle TX-ready"]
  end
 subgraph S["UART driver state"]
    direction TB
        TXSTATE["TX state<br><br>tx_lock<br>tx_busy<br>tx_chan"]
  end
 subgraph H["UART hardware source"]
    direction TB
        UART_REGS["16550a UART registers<br><br>RHR / THR<br>IER / ISR<br>LSR"]
        UART_IRQ["UART0_IRQ<br><br>external interrupt"]
  end
 subgraph I["interrupt delivery"]
    direction TB
        PLIC_PATH["PLIC + devintr()<br><br>claim UART IRQ<br>call uartintr<br>complete IRQ"]
  end
    USER_IO -- console fd read/write --> DEVSW
    DEVSW -- dispatches to --> CONSOLE
    CONSOLE -- consolewrite sends bytes --> UARTOUT
    KERNEL_OUT -- direct/synchronous output --> UARTOUT
    UARTINIT -- configures --> UART_REGS
    UARTOUT -- writes THR / checks LSR --> UART_REGS
    UARTOUT -- uses --> TXSTATE
    UART_REGS -- input ready or TX ready --> UART_IRQ
    UART_IRQ -- external interrupt --> PLIC_PATH
    PLIC_PATH -- calls --> UARTINTR
    UARTINTR -- reads/writes UART registers --> UART_REGS
    UARTINTR -- updates TX completion --> TXSTATE
    UARTINTR -- passes input chars upward --> CONSOLE

     USER_IO:::process
     KERNEL_OUT:::process
     DEVSW:::iface
     CONSOLE:::iface
     UARTINIT:::iface
     UARTOUT:::iface
     UARTINTR:::iface
     TXSTATE:::source
     UART_REGS:::source
     UART_IRQ:::source
     PLIC_PATH:::backend
    classDef process fill:#F3EFE2,stroke:#111,stroke-width:2px,color:#111
    classDef source fill:#E9F1FF,stroke:#111,stroke-width:2px,color:#111
    classDef iface fill:#EDE7D4,stroke:#111,stroke-width:2px,color:#111
    classDef backend fill:#F8F8F8,stroke:#111,stroke-width:2px,color:#111

Virtio Disk Driver

---
config:
  layout: dagre
---
flowchart LR
 subgraph C["caller side"]
    direction TB
        FS["filesystem / log layer<br><br>readi / writei<br>log_write / install_trans"]
        BCACHE["buffer cache<br><br>bread<br>bwrite"]
        BUF["struct buf<br><br>dev<br>blockno<br>valid<br>disk<br>data[BSIZE]"]
  end
 subgraph VDI["virtio disk interface"]
    direction TB
        VINIT["virtio_disk_init()<br><br>verify device<br>negotiate features<br>initialize queue 0"]
        VRW["virtio_disk_rw(b, write)<br><br>build disk request<br>submit to virtqueue<br>sleep until done"]
        VINTR["virtio_disk_intr()<br><br>ack interrupt<br>process used ring<br>wake completed buffers"]
  end
 subgraph VDS["virtio disk state"]
    direction TB
        DISK["struct disk<br><br>vdisk_lock<br>free[]<br>used_idx<br>info[]<br>ops[]"]
        VQ["virtqueue<br><br>desc[]<br>avail ring<br>used ring"]
  end
 subgraph HW["virtio disk hardware source"]
    direction TB
        MMIO["virtio MMIO registers<br><br>STATUS<br>QUEUE_NOTIFY<br>INTERRUPT_STATUS<br>INTERRUPT_ACK"]
        DEVICE["qemu virtio-blk device<br><br>reads / writes fs.img blocks"]
        IRQ["VIRTIO0_IRQ<br><br>external interrupt"]
  end
 subgraph I["interrupt delivery"]
    direction TB
        PLIC_PATH["PLIC + devintr()<br><br>claim virtio IRQ<br>call virtio_disk_intr<br>complete IRQ"]
  end
    FS -- needs disk block --> BCACHE
    BCACHE -- uses locked buffer --> BUF
    BCACHE -- cache miss or writeback --> VRW
    VINIT -- sets up --> DISK
    VINIT -- allocates and registers --> VQ
    VINIT -- configures --> MMIO
    VRW -- uses buffer data --> BUF
    VRW -- allocates 3 descriptors<br>header + data + status --> VQ
    VRW -- "records in-flight request" --> DISK
    VRW -- notifies queue 0 --> MMIO
    MMIO -- device sees available request --> DEVICE
    DEVICE -- DMA reads/writes buffer block --> VQ
    DEVICE -- completion interrupt --> IRQ
    IRQ -- external interrupt --> PLIC_PATH
    PLIC_PATH -- calls --> VINTR
    VINTR -- acknowledges interrupt --> MMIO
    VINTR -- reads completed entries --> VQ
    VINTR -- "clears b->disk and wakeup(b)" --> BUF

     FS:::process
     BCACHE:::iface
     BUF:::file
     VINIT:::iface
     VRW:::iface
     VINTR:::iface
     DISK:::source
     VQ:::source
     MMIO:::source
     DEVICE:::source
     IRQ:::source
     PLIC_PATH:::backend
    classDef process fill:#F3EFE2,stroke:#111,stroke-width:2px,color:#111
    classDef file fill:#FFFFFF,stroke:#111,stroke-width:3px,color:#111
    classDef source fill:#E9F1FF,stroke:#111,stroke-width:2px,color:#111
    classDef iface fill:#EDE7D4,stroke:#111,stroke-width:2px,color:#111
    classDef backend fill:#F8F8F8,stroke:#111,stroke-width:2px,color:#111

disk is the global virtio driver state.

FieldMeaning
descDescriptor table used to describe request buffers.
availRing where the driver publishes descriptor chains for the device.
usedRing where the device publishes completed descriptor chains.
free[NUM]Tracks which descriptors are free.
used_idxTracks how far the driver has processed the used ring.
info[NUM]Maps an in-flight request back to its struct buf and status byte.
ops[NUM]Request headers, one per descriptor slot.
vdisk_lockProtects virtio disk driver state.

The virtqueue has three main parts.

PartOwnerPurpose
Descriptor tableDriver writes, device readsDescribes memory buffers for a request.
Available ringDriver writes, device readsTells the device which descriptor chain to process.
Used ringDevice writes, driver readsTells the driver which request finished.