xv6, line by line
lab 25
Lab 2525 An e1000 network driver and UDP sockets

Lab 25 · reveal · 19 steps · 7 commits

An e1000 network driver and UDP sockets: the reference solution

xv6 already has one device driver that works by DMA: the virtio disk, where the kernel writes descriptors into memory, pokes a register, and an interrupt says the device is done (Tour 29: A disk read, end to end). In this lab you write a second one, for QEMU’s emulation of the Intel e1000 network card, and put a small network stack and a socket interface on top of it, so that a user program can exchange UDP packets with a program on the host.

The disk is a polite device: it only speaks when spoken to, and exactly one request waits for each answer. A network card is not. Packets arrive whenever the outside world sends them, on whichever hart the interrupt controller picks, while processes on the other two harts are sending. That raises the questions this lab is about. Where is the card, and how does the kernel even reach its registers? How do the driver and the card hand descriptors back and forth without a lock they could share? What may code do that runs inside an interrupt handler, and what must it never do? How does a process sleep until a packet arrives without missing the wakeup that an interrupt on another hart delivers? And what does “the device sees memory in the right order” mean on RISC-V?

The reference solution is seven commits: find the card on the PCI bus, give it descriptor rings, transmit, receive from the interrupt, answer ARP and parse IPv4/UDP, add sockets, and add the test with its host-side partner. On three harts a UDP round trip through QEMU’s user-mode network took a median of 149 to 243 microseconds, measured from the host on three boots (measured while the computer was busy with other work; your times will differ). Some driver bugs are invisible on QEMU: Clinic 2 is one that no test here can show, and Clinic 6 one that shows only when the card is made to fall behind.

Each step shows one change on the branch ext/25-e1000, the code around it, and the state of the machine when that code runs.

The route
  1. 1A network card on QEMU's command line Makefile
  2. 2Two new windows in the kernel page table kernel/vm.c
  3. 3Find the card and choose its address kernel/pci.c
  4. 4Two rings, each in a page of RAM kernel/e1000.c
  5. 5Reset, then give the card its rings kernel/e1000.c
  6. 6Transmit: is the slot at the tail free? kernel/e1000.c
  7. 7Transmit: fill the slot, fence, move the tail kernel/e1000.c
  8. 8IRQ 33, enabled on every hart kernel/plic.c
  9. 9devintr claims 33 kernel/trap.c
  10. 10Harvest the filled slots under the lock kernel/e1000.c
  11. 11Deliver outside the lock kernel/e1000.c
  12. 12Headers and byte order kernel/net.h
  13. 13Answering ARP inside the interrupt kernel/net.c
  14. 14Checking an IPv4/UDP packet kernel/net.c
  15. 15Building a packet to send kernel/net.c
  16. 16Sockets, and the locks that guard them kernel/socket.c
  17. 17The interrupt queues the packet and wakes the reader kernel/socket.c
  18. 18recv sleeps without losing the wakeup kernel/socket.c
  19. 19The test, with a watchdog user/nettest.c

Keys: ← → step · Home start