Lab 001 · e1000 网卡驱动验证
对应
document/book/17-net/001/。验证档 B 档:本章交付的是 NIC 驱动(检测 + MAC + 收发环),还没协议栈,所以验不了ping(那是 002 的事)。验证靠真实流量 round-trip——驱动把包发给 QEMU 的 SLIRP 网关,网关应答,驱动再把应答收回来:单播 ARP reply、广播 DHCP OFFER 都能收到,就证明这块网卡真能收能发。
目标
确认四件事:
- e1000 被 PCI 找到、MAC 从 EEPROM 读对(默认
52:54:00:12:34:56)、链路拉起; - 单播收包通(
test_arp_roundtrip:发 ARP、收 reply); - 广播收包通(
test_broadcast_rx:发 DHCPDISCOVER、收 DHCPOFFER); - 那个轮询坑的修法在源码里(
poll_rx开头读RDH),MMIO 槽位在+0x50000(不撞别人)。
步骤
1. e1000 探测 + MAC(检测 + 取 MAC)
run-kernel-test 默认挂了 -device e1000,netdev=net0 -netdev user,id=net0(在 cmake/qemu.cmake 里),所以直接跑就有真 NIC:
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=1 → test_e1000::test_detect_and_mac [PASS]。MAC 是 QEMU user-net 的默认值,读到它就证明「PCI 找卡 → BAR0 映射 → 复位 → EEPROM 取 MAC → 链路」整条路通了。
想看 PCI 按什么匹配的(vendor/device 不是 class):
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 把它收回来:
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
本章的明星坑。去看修法的现场:
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(不撞别人)
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 的规矩)
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.cppdrivers/CMakeLists.txt 应是 if(CINUX_NET) net/e1000.cpp + e1000_init.cpp else net/net_stub.cpp;main.cpp 调 net::init() 无 #ifdef(开关归 CMake,CINUX_NET off 时编 stub 的空 net::init())。和 054b 把 #ifdef CINUX_GUI 赶去 CMake 同款。
验收清单
- [ ]
run-kernel-test出真探测:e1000 found 00:04.0、MAC=52:54:00:12:34:56、test_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)。中断收包留后续。