Skip to content

Lab 001 · e1000 网卡驱动验证

对应 document/book/17-net/001/。验证档 B 档:本章交付的是 NIC 驱动(检测 + MAC + 收发环),还没协议栈,所以验不了 ping(那是 002 的事)。验证靠真实流量 round-trip——驱动把包发给 QEMU 的 SLIRP 网关,网关应答,驱动再把应答收回来:单播 ARP reply、广播 DHCP OFFER 都能收到,就证明这块网卡真能收能发。

目标

确认四件事:

  1. e1000 被 PCI 找到、MAC 从 EEPROM 读对(默认 52:54:00:12:34:56)、链路拉起;
  2. 单播收包通(test_arp_roundtrip:发 ARP、收 reply);
  3. 广播收包通(test_broadcast_rx:发 DHCPDISCOVER、收 DHCPOFFER);
  4. 那个轮询坑的修法在源码里(poll_rx 开头读 RDH),MMIO 槽位在 +0x50000(不撞别人)。

步骤

1. e1000 探测 + MAC(检测 + 取 MAC)

run-kernel-test 默认挂了 -device e1000,netdev=net0 -netdev user,id=net0(在 cmake/qemu.cmake 里),所以直接跑就有真 NIC:

bash
cmake --build build --target run-kernel-test 2>&1 | grep -aE "e1000|E1000" | head

应看到真探测输出:[PCI] e1000 found: 00:04.0 BAR0=0xfebc0000[e1000] BAR0=... MAC=52:54:00:12:34:56 link=1 FD=1test_e1000::test_detect_and_mac [PASS]。MAC 是 QEMU user-net 的默认值,读到它就证明「PCI 找卡 → BAR0 映射 → 复位 → EEPROM 取 MAC → 链路」整条路通了。

想看 PCI 按什么匹配的(vendor/device 不是 class):

bash
sed -n '71,80p' kernel/drivers/pci/pci.hpp

应看到 is_e1000_device 先查 vendor != 0x8086 返回 false,再按 device ID(0x100E 是 82540em,QEMU 默认)匹配——不按 class 0x02 是怕误绑 virtio-net。

2. 单播收包:test_arp_roundtrip

这是「真能收」的铁证。驱动 TX 一个广播 ARP「who has 10.0.2.2」,QEMU SLIRP 网关应答一个单播 ARP reply,驱动 RX 把它收回来:

bash
cmake --build build --target run-kernel-test 2>&1 | grep -aA1 "test_arp_roundtrip\|test_broadcast_rx" | head

应看到 test_e1000::test_arp_roundtrip [PASS]test_e1000::test_broadcast_rx [PASS]test_arp_roundtrip 验单播(收发给自己 MAC 的帧),test_broadcast_rx 验广播(TX DHCPDISCOVER、收广播 DHCPOFFER,目的 MAC ff:ff:ff:ff:ff:ff)。两个都过,说明 RAL0 单播过滤 + RCTL.BAM 广播过滤都设对了。最终 945 passed, 0 failed

3. 轮询收包那个坑:poll_rx 开头读 RDH

本章的明星坑。去看修法的现场:

bash
sed -n '237,250p' kernel/drivers/net/e1000.cpp

应看到 poll_rx 第一行就是 (void)reg_read(e1000reg::RDH);——读一个 MMIO 寄存器。注释会解释:在 QEMU TCG 下,把 SLIRP 回包拉进 ring 需要设备模型主循环跑起来,而光轮询内存里的描述符 status 不会 trap、不会驱动主循环;读 RDH(MMIO)才 trap 出去、让主循环跑、把包投递进 ring。真机上这行无害(包一到就进 ring)。别把这行当成可省的废操作删掉——删了在 QEMU 上就收不到包。

4. MMIO 槽位 +0x50000(不撞别人)

bash
sed -n '42,48p' kernel/drivers/net/e1000.cpp

应看到注释列了已占的 MMIO 偏移(AHCI +0x0、LAPIC +0x10000、IOAPIC +0x11000、xHCI +0x20000、MSI-X Table +0x40000、PBA +0x41000),e1000 槽在 +0x50000(kE1000MmioVirt = KMEM_MMIO_BASE + 0x50000)。这块是本仓致命坑(xHCI/MSI-X 当年 BAR 撞过崩),新设备进 MMIO 窗口必须 grep 确认不撞、还得留够页数(e1000 用到 ~0x5404,取 8 页)。

5. §14 CINUX_NET gate(又见 054b 的规矩)

bash
grep -n 'CINUX_NET\|net_stub\|e1000_init\|e1000.cpp' kernel/drivers/CMakeLists.txt
grep -n 'net::init' kernel/main.cpp kernel/drivers/net/net_stub.cpp

drivers/CMakeLists.txt 应是 if(CINUX_NET) net/e1000.cpp + e1000_init.cpp else net/net_stub.cpp;main.cppnet::init() #ifdef(开关归 CMake,CINUX_NET off 时编 stub 的空 net::init())。和 054b 把 #ifdef CINUX_GUI 赶去 CMake 同款。

验收清单

  • [ ] run-kernel-test 出真探测:e1000 found 00:04.0MAC=52:54:00:12:34:56test_detect_and_mac PASS
  • [ ] test_arp_roundtrip(单播)+ test_broadcast_rx(广播)都 PASS;945/0。
  • [ ] poll_rx 开头有 (void)reg_read(RDH)(e1000.cpp:248),注释解释 QEMU 主循环 trap 机制——知道这行不能删。
  • [ ] MMIO 槽 +0x50000(e1000.cpp:47),注释列了已占偏移,不撞别人。
  • [ ] §14 gate:if(CINUX_NET) 选编真驱动 / stub,main.cpp#ifdef

别做这些

  • 指望 ping——这一章只到 L2(以太网帧),没 IP/ICMP。test_arp_roundtrip 收的是 ARP reply(ethertype 0x0806),不是 ICMP echo reply。ping 留 002。
  • poll_rx 开头那个 (void)reg_read(RDH)——在 QEMU 上删了就收不到包(模拟器主循环不跑、包不投递)。真机上它无害,留着也对。
  • 按 class 0x02 找 e1000——会误绑 virtio-net(也是 class 0x02,寄存器布局完全不同)。按 vendor 0x8086 + device ID 配。
  • 给 e1000 随便挑个 MMIO 偏移——窗口里 AHCI/LAPIC/IOAPIC/xHCI/MSI-X 已占了一堆,撞了会崩。先 grep 已占偏移、选空隙(这里是 +0x50000)、留够页数(e1000 到 ~0x5404 取 8 页)。
  • 以为这驱动走中断——它是轮询的(嵌套 KVM 下 MSI-X 不可靠 + 82540em 只支持 MSI)。中断收包留后续。

035_multi_terminal-43-g8c0c4bc · 8c0c4bc · 2026-07-25