第 10 章 网络操作 --- 10.3.1 NIC驱动
本节以 dc21x4.sys(DEC 21x4x 系列百兆网卡驱动)为例剖析 NIC Miniport 驱动的实现。 Miniport 驱动是网络栈的"硬件适配层",直接操作网卡寄存器、DMA 控制器、处理中断。ReactOS 的 dc21x4 实现在 drivers/network/dd/dc21x4/(file:///d:/reactos/drivers/network/dd/dc21x4/),包含 dc21x4.c、hardware.c、send.c、interrupt.c、eeprom.c、media.c、phy.c、init.c 等。
概述
NIC Miniport 驱动的核心职责:
- 探测与配置:通过 PCI BAR 找到 I/O 端口、IRQ
- 初始化硬件:复位 NIC、设置 MAC 地址、配置收发描述符
- 数据发送:构造 TX 描述符,启动 DMA
- 数据接收:解析 RX 描述符,把包转给 NDIS
- 中断处理:响应 NIC 中断,处理完成事件
- 错误恢复:超时、总线错误、链接中断
什么是 Miniport 驱动
Miniport 驱动是 NDIS(Network Driver Interface Specification)架构中最底层的驱动模块,它直接面向具体的网卡硬件。在 Windows/ReactOS 的网络栈中,NDIS 充当"中间层"角色:上层是协议驱动(如 TCP/IP、NDISWAN),下层是 Miniport 驱动。Miniport 驱动的唯一职责就是把 NDIS 的抽象请求翻译成特定硬件能理解的操作------写寄存器、操作 DMA、处理中断。
ReactOS 目前包含多个 Miniport 驱动实现:dc21x4.sys(DEC 21x4x 百兆网卡)、e1000.sys(Intel PRO/1000 千兆网卡)、rtl8139.sys(Realtek 8139 百兆网卡)、ne2k_pci.sys(NE2000 兼容卡)和 pcnetnic.sys(AMD PCnet)。这些驱动共享相同的 NDIS 回调接口,但内部实现因硬件差异而截然不同。
NDIS 架构中的位置
在 ReactOS 网络栈中,数据包从应用层到网卡的完整路径为:
应用程序 → Winsock → AFD → TCP/IP → NDIS 协议驱动 → Miniport 驱动 → 网卡硬件
Miniport 驱动通过 NdisMRegisterMiniport 向 NDIS 库注册一组回调函数。NDIS 在需要时调用这些回调:初始化时调用 InitializeHandler,发送数据时调用 SendHandler,查询状态时调用 QueryInformationHandler。反过来,当网卡收到数据或产生中断时,Miniport 驱动通过 NDIS 提供的 API(如 NdisMIndicateReceivePacket、NdisMSendComplete)向上层通知事件。
硬件交互模型
Miniport 驱动与硬件的交互遵循"寄存器操作 + DMA"模型:
-
寄存器操作 :通过
READ_PORT_ULONG/WRITE_PORT_ULONG访问 I/O 端口映射的寄存器,或通过内存映射(MMIO)访问内存地址空间的寄存器。寄存器用于控制 NIC 的行为(启动/停止、配置模式)和读取状态(中断原因、发送完成状态)。 -
DMA(直接内存访问) :网卡通过 DMA 控制器直接读写系统内存,无需 CPU 参与数据复制。Miniport 驱动使用
NdisMAllocateSharedMemory分配物理连续的共享内存,用于存放 TX/RX 描述符表。描述符是 CPU 和 NIC 之间的"合同":CPU 填写描述符告诉 NIC 数据在哪里、多长,NIC 完成操作后更新描述符的状态位。 -
中断机制:当 NIC 完成发送、收到数据或发生错误时,通过硬件中断线通知 CPU。Miniport 的 ISR(中断服务例程)运行在高优先级的 DIRQL 级别,必须快速执行:确认中断、调度 DPC(延迟过程调用),由 DPC 在较低级别完成实际的数据处理。
驱动开发的核心挑战
编写 Miniport 驱动面临几个核心挑战:
并发与同步 :NIC 硬件和 CPU 同时访问描述符内存,必须通过原子操作和内存屏障保证一致性。例如,CPU 设置 OWN 位后,NIC 可能立即开始 DMA,如果内存写入顺序不对,NIC 可能读到不完整的数据。ReactOS 使用 KeAcquireSpinLock 和 KeReleaseSpinLock 保护关键数据结构。
资源管理 :Miniport 驱动在初始化时分配大量资源(DMA 内存、中断、包池、缓冲池),在卸载时必须正确释放。任何泄漏都会导致系统资源耗尽。ReactOS 的 DC21X4Halt 函数严格按照分配顺序的逆序释放资源。
硬件差异:不同网卡的寄存器布局、描述符格式、中断机制各不相同。Miniport 驱动必须通过 PCI 设备 ID 识别硬件版本,并选择正确的操作模式。例如,dc21x4 有多个硬件版本(21040、21041、21140、21143),每个版本的 PHY 配置方式不同。
错误恢复 :网络硬件可能因各种原因进入异常状态:总线错误、链路断开、描述符环损坏。Miniport 驱动必须实现 ResetHandler 回调,在 NDIS 检测到异常时复位硬件并恢复操作。
10 问为什么
Q1:为什么 Miniport 驱动必须通过 NDIS 回调注册,而不是直接暴露设备对象?
因为 NDIS 是网络驱动的统一抽象层。如果每个网卡驱动直接暴露设备对象,上层协议(TCP/IP、NDISWAN)就必须为每种网卡编写适配代码。NDIS 通过标准化回调接口(Initialize、Send、Halt 等),让上层协议只与 NDIS 交互,完全不知道底层硬件差异。这实现了"一次编写协议,适配所有网卡"的目标。
Q2:为什么 TX 描述符需要 OWN 位,而不是用简单的队列指针?
因为 CPU 和 NIC 是两个独立的执行单元,没有共享的锁机制。OWN 位是一个"令牌传递"协议:OWN=0 表示 CPU 拥有描述符,可以填写数据;OWN=1 表示 NIC 拥有描述符,可以 DMA 读取。如果只用队列指针,CPU 和 NIC 可能同时修改同一个描述符,导致数据损坏。OWN 位通过原子位操作实现了无锁的生产者-消费者模型。
Q3:为什么 DMA 描述符必须分配在物理连续的内存中?
因为 NIC 硬件通过 DMA 控制器直接访问物理地址,不经过 CPU 的 MMU(内存管理单元)。NIC 看到的地址是物理地址,不是虚拟地址。如果描述符分散在多个物理页上,NIC 无法通过一个基地址找到所有描述符。NdisMAllocateSharedMemory 保证分配的内存既是物理连续的,又在 CPU 和 NIC 之间共享(CPU 用虚拟地址访问,NIC 用物理地址访问)。
Q4:为什么 ISR 必须快速执行,不能在里面处理数据包?
因为 ISR 运行在 DIRQL(设备中断请求级别),这个级别高于所有线程调度。ISR 执行期间,CPU 不能切换线程、不能处理其他中断(同级或更低级别)。如果 ISR 花太长时间处理数据包,会导致:(1)其他设备的中断延迟,系统响应变慢;(2)同一 NIC 的后续中断被丢失(如果 NIC 的中断被屏蔽太久)。所以 ISR 只做"确认中断 + 请求 DPC",实际处理在 DPC 中完成。
Q5:为什么 dc21x4 的 CSR 寄存器间隔 8 字节而不是 4 字节?
因为 DEC 21x4x 的 CSR 寄存器是 32 位宽(4 字节),但硬件设计上使用 64 位对齐(8 字节间隔)。这是 DEC 的 Tulip 芯片家族的历史设计选择,可能为了简化地址解码逻辑,或者为未来扩展预留空间。驱动程序必须按照 8 字节间隔访问寄存器,即使实际数据只有 4 字节。
Q6:为什么 Miniport 驱动需要同时支持 I/O 端口和内存映射两种寄存器访问方式?
因为不同的网卡硬件使用不同的总线接口。老旧的网卡(如 NE2000、dc21x4 的某些模式)使用 I/O 端口空间,通过 in/out 指令访问。现代的网卡(如 e1000)使用内存映射 I/O(MMIO),寄存器被映射到物理内存地址,通过普通的内存读写指令访问。I/O 端口空间有限(x86 只有 64KB),而 MMIO 可以使用更大的地址空间,支持更多寄存器。Miniport 驱动必须根据 PCI BAR 的类型选择正确的访问方式。
Q7:为什么接收路径使用 NdisMIndicateReceivePacket 而不是直接返回数据包?
因为 NDIS 架构是事件驱动的,不是请求-响应模式。Miniport 驱动不能"等待"上层来取数据,而是主动"通知"上层有新数据到达。NdisMIndicateReceivePacket 是一个异步通知:Miniport 告诉 NDIS"这里有数据包",NDIS 再通知协议驱动(如 TCP/IP)。协议驱动可以选择立即处理、排队、或者拒绝。这种设计允许 NIC 以线速接收数据,而不被上层处理速度阻塞。
Q8:为什么 dc21x4 有多个硬件版本(21040、21041、21140、21143),驱动需要分别处理?
因为每个版本的内部架构不同。21040 是最早的版本,外部 PHY;21041 增加了内部 PHY 和 GPIO;21140 支持 100Mbps,引入了新的 CSR 寄存器和 NWay 自动协商;21143 增加了电源管理和改进的 PHY。虽然基本寄存器布局兼容,但 PHY 配置、媒体检测、自动协商的实现完全不同。驱动必须通过 PCI 设备 ID 识别版本,选择正确的初始化序列。
Q9:为什么 Miniport 驱动需要 CheckForHang 回调?
因为硬件可能进入"死锁"状态:NIC 不再产生中断,但也没有报告错误。这种情况可能由电磁干扰、总线错误、固件 bug 引起。NDIS 定期(默认每 2 秒)调用 CheckForHang,让 Miniport 检查硬件状态。如果检测到异常,Miniport 可以返回 TRUE,NDIS 会调用 ResetHandler 复位硬件。这是一种"看门狗"机制,确保网络在硬件故障后能自动恢复。
Q10:为什么 e1000 使用描述符环(descriptor ring),而 rtl8139 使用线性缓冲?
因为性能需求不同。e1000 是千兆网卡,线速 1Gbps 意味着每秒 148 万个 64 字节包。描述符环允许 NIC 和 CPU 并行工作:NIC 写 RX 描述符,CPU 读另一个,两者通过环指针同步。这种设计支持零拷贝和 scatter-gather DMA。rtl8139 是百兆网卡,数据量小一个数量级,使用简单的线性缓冲区(64KB 环形缓冲)更容易实现,但效率较低(需要 CPU 复制数据)。这是成本和性能的权衡。
本节内容概览
- 10.3.1.0 框架图
- 10.3.1.1 Miniport 驱动模型
- 10.3.1.2
dc21x4.c主入口 - 10.3.1.3
MiniportInitialize硬件初始化 - 10.3.1.4
MiniportHalt卸载 - 10.3.1.5
MiniportSend数据发送 - 10.3.1.6 中断与接收
- 10.3.1.7 其他 Miniport 驱动实例
- 10.3.1.8 总结与代码索引
学习目标
- 理解 Miniport 驱动的完整工作流
- 掌握 NIC 硬件初始化(PCI BAR、IRQ、MAC)
- 知道 TX/RX 描述符的工作原理
- 区分 dc21x4、e1000、rtl8139 的差异
涉及的内核子系统
| 子系统 | 头文件/源文件 | 核心作用 |
|---|---|---|
| dc21x4 主 | dc21x4.c(file:///d:/reactos/drivers/network/dd/dc21x4/dc21x4.c) | DriverEntry、NDIS 回调 |
| dc21x4 硬件 | hardware.c(file:///d:/reactos/drivers/network/dd/dc21x4/hardware.c) | 寄存器操作 |
| dc21x4 头 | dc21x4.h(file:///d:/reactos/drivers/network/dd/dc21x4/dc21x4.h) | 数据结构 |
| dc21x4 HW 头 | dc21x4hw.h(file:///d:/reactos/drivers/network/dd/dc21x4/dc21x4hw.h) | 寄存器定义 |
| dc21x4 发送 | send.c(file:///d:/reactos/drivers/network/dd/dc21x4/send.c) | TX 路径 |
| dc21x4 中断 | interrupt.c(file:///d:/reactos/drivers/network/dd/dc21x4/interrupt.c) | ISR |
| dc21x4 初始化 | init.c(file:///d:/reactos/drivers/network/dd/dc21x4/init.c) | 启动序列 |
| dc21x4 EEPROM | eeprom.c(file:///d:/reactos/drivers/network/dd/dc21x4/eeprom.c) | MAC 读取 |
| dc21x4 Media | media\*.c(file:///d:/reactos/drivers/network/dd/dc21x4/) | 媒体类型 |
| e1000 | drivers/network/dd/e1000/(file:///d:/reactos/drivers/network/dd/e1000/) | Intel 千兆 |
| rtl8139 | drivers/network/dd/rtl8139/(file:///d:/reactos/drivers/network/dd/rtl8139/) | Realtek 百兆 |
10.3.1.0 框架图
+-------------------------------------+
| NDIS 库 (ndis.sys) |
+-------------------------------------+
| Miniport 回调
v
+-------------------------------------+
| dc21x4.sys / e1000.sys / rtl8139 | 本节主题
| - MiniportInitialize |
| - MiniportSend |
| - MiniportISR / MiniportHandleInterrupt |
+-------------------------------------+
| 寄存器 I/O
v
+-------------------------------------+
| NIC 硬件 (DEC 21x4x / Intel 8254x) |
| - CSR 寄存器 |
| - TX/RX 描述符 DMA |
| - PHY |
+-------------------------------------+
|
v
+-------------------------------------+
| 网络介质 |
+-------------------------------------+
10.3.1.1 Miniport 驱动模型
注册回调
Miniport 通过 NdisMRegisterMiniport 注册一组回调:
c
NDIS_STATUS MyDriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
NDIS_MINIPORT_CHARACTERISTICS Chars = {0};
Chars.MajorNdisVersion = 5;
Chars.MinorNdisVersion = 0;
// 必须的回调
Chars.InitializeHandler = MyInit; // 初始化
Chars.HaltHandler = MyHalt; // 卸载
Chars.SendHandler = MySend; // 发送
// 经常需要的
Chars.TransferDataHandler = MyTransferData; // 接收
Chars.ResetHandler = MyReset; // 复位
Chars.QueryInformationHandler = MyQuery; // OID 查询
Chars.SetInformationHandler = MySet; // OID 设置
// 可选
Chars.CheckForHangHandler = MyCheckForHang;
Chars.DisableInterruptHandler = MyDisableInterrupt;
Chars.EnableInterruptHandler = MyEnableInterrupt;
NdisMRegisterMiniport(&Status, DriverObject, &Chars, sizeof(Chars));
return Status;
}
重要回调一览
| 回调 | 何时调用 | 必需 |
|---|---|---|
Initialize |
PnP 启动时 | 是 |
Halt |
卸载时 | 是 |
Send |
上层发送包 | 是 |
TransferData |
包接收时 | 是 |
Reset |
总线异常时 | 是 |
Query/SetInformation |
OID 查询/设置 | 是 |
ISR |
硬件中断 | 是 |
HandleInterrupt |
延迟处理(DPC) | 是 |
CheckForHang |
NDIS 监视器 | 否 |
10.3.1.2 dc21x4.c 主入口
dc21x4.c(file:///d:/reactos/drivers/network/dd/dc21x4/dc21x4.c) 包含 NDIS 回调实现。
DriverEntry
c
NTSTATUS NTAPI
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
NDIS_MINIPORT_CHARACTERISTICS Chars = {0};
NDIS_STATUS Status;
Chars.MajorNdisVersion = 5;
Chars.MinorNdisVersion = 0;
Chars.InitializeHandler = DC21X4Initialize;
Chars.HaltHandler = DC21X4Halt;
Chars.SendHandler = DC21X4Send;
Chars.TransferDataHandler = DC21X4TransferData;
Chars.ResetHandler = DC21X4Reset;
Chars.QueryInformationHandler = DC21X4QueryInformation;
Chars.SetInformationHandler = DC21X4SetInformation;
Chars.DisableInterruptHandler = DC21X4DisableInterrupt;
Chars.EnableInterruptHandler = DC21X4EnableInterrupt;
Chars.CheckForHangHandler = DC21X4CheckForHang;
NdisMRegisterMiniport(&Status, DriverObject, &Chars, sizeof(Chars));
return Status;
}
10.3.1.3 MiniportInitialize 硬件初始化
DC21X4Initialize(在 init.c(file:///d:/reactos/drivers/network/dd/dc21x4/init.c))完成:
- 读取 PCI 配置空间
- 映射 I/O 寄存器
- 分配共享内存(DMA 描述符表)
- 分配中断
- 读取 MAC 地址(从 EEPROM)
- 复位 NIC
- 配置 TX/RX 描述符
- 启用中断
- 设置 NdisAdapterHandle
详细步骤
c
NDIS_STATUS DC21X4Initialize(PNDIS_STATUS OpenErrorStatus, UINT SelectedMediumIndex,
PNDIS_MEDIUM MediumArray, UINT MediumArraySize,
NDIS_HANDLE AdapterContext,
NDIS_HANDLE WrapperConfigurationContext)
{
PDC21X4_ADAPTER Adapter = (PDC21X4_ADAPTER)AdapterContext;
NDIS_STATUS Status;
// 1. 读取 PCI 资源
NdisMGetBusData(Adapter->MiniportAdapterHandle, PCIConfiguration, 0, &PciConfig, sizeof(PciConfig));
Adapter->IoBase = (PULONG)NdisMMapIoSpace(...); // 映射 BAR0
Adapter->MemBase = NdisMMapIoSpace(...); // 映射 BAR1
// 2. 分配 DMA 一致性内存(描述符表)
NdisMAllocateSharedMemory(Adapter->MiniportAdapterHandle, NonCached, sizeof(TX_DESC)*NUM_TX_DESC, FALSE, &TxDesc);
// 3. 注册中断
NdisMRegisterInterrupt(&Status, &Adapter->Interrupt, Adapter->MiniportAdapterHandle,
PciConfig.u.type0.InterruptLine, PciConfig.u.type0.InterruptLine,
TRUE, FALSE, NdisInterruptLevelSensitive);
// 4. 读取 MAC 地址
DC21X4ReadEEpromMAC(Adapter, Adapter->PermanentAddress);
// 5. 复位 NIC
DC21X4Reset(Adapter);
// 6. 配置描述符
DC21X4SetupTxRxDescriptors(Adapter);
// 7. 设置 MAC 地址寄存器
DC21X4SetMACAddress(Adapter, Adapter->CurrentAddress);
// 8. 配置 PHY
DC21X4ConfigurePhy(Adapter);
// 9. 启用接收/发送
DC21X4EnableTxRx(Adapter);
// 10. 启用中断
DC21X4EnableInterrupt(Adapter);
return NDIS_STATUS_SUCCESS;
}
关键数据结构
c
typedef struct _DC21X4_ADAPTER {
NDIS_ADAPTER_INFORMATION AdapterInfo;
NDIS_HANDLE MiniportAdapterHandle;
// 寄存器
PULONG IoBase; // I/O 寄存器(CSR)
PVOID MemBase; // 内存寄存器
// 描述符
PTX_DESC TxDesc; // TX 描述符数组
PRX_DESC RxDesc; // RX 描述符数组
// MAC
UCHAR PermanentAddress[6];
UCHAR CurrentAddress[6];
// 中断
NDIS_MINIPORT_INTERRUPT Interrupt;
// 状态
ULONG Flags;
// 包/缓冲池
NDIS_HANDLE PacketPool;
NDIS_HANDLE BufferPool;
} DC21X4_ADAPTER;
CSR 寄存器
DC21X4 内部有一组 CSR(Control and Status Register):
c
#define CSR0 0x00 // 总线模式
#define CSR1 0x08 // TX 状态
#define CSR2 0x10 // 中断屏蔽
#define CSR3 0x18 // 中断状态
#define CSR4 0x20 // 接收地址 + 中断确认
#define CSR5 0x28 // 当前 TX 描述符
#define CSR6 0x30 // 命令
#define CSR7 0x38 // 当前 RX 描述符
// ...
10.3.1.4 MiniportHalt 卸载
c
VOID DC21X4Halt(NDIS_HANDLE MiniportAdapterContext) {
PDC21X4_ADAPTER Adapter = MiniportAdapterContext;
// 1. 禁用中断
NdisMSynchronizeWithInterrupt(&Adapter->Interrupt, DC21X4DisableInterruptSync, Adapter);
// 2. 停止 NIC(TX/RX OFF)
DC21X4StopTxRx(Adapter);
// 3. 注销中断
NdisMDeregisterInterrupt(&Adapter->Interrupt);
// 4. 释放 DMA 内存
NdisMFreeSharedMemory(Adapter->MiniportAdapterHandle, sizeof(TX_DESC)*NUM_TX_DESC, FALSE, TxDesc);
// 5. 取消 I/O 映射
NdisMUnmapIoSpace(Adapter->MiniportAdapterHandle, Adapter->IoBase, ...);
// 6. 释放适配器结构
ExFreePool(Adapter);
}
10.3.1.5 MiniportSend 数据发送
send.c(file:///d:/reactos/drivers/network/dd/dc21x4/send.c):
c
NDIS_STATUS DC21X4Send(NDIS_HANDLE MiniportAdapterContext, PNDIS_PACKET Packet, UINT Flags) {
PDC21X4_ADAPTER Adapter = MiniportAdapterContext;
NDIS_STATUS Status;
PNDIS_BUFFER Buffer;
PVOID VirtualAddress;
UINT Length, TotalLength;
// 1. 获取包中的缓冲
NdisQueryPacket(Packet, NULL, NULL, &Buffer, &TotalLength);
// 2. 选择空闲 TX 描述符
ULONG DescIndex = Adapter->NextTxDesc;
PTX_DESC Desc = &Adapter->TxDesc[DescIndex];
if (Desc->Status & TX_OWN) {
// 没有空闲描述符,返回 RESOURCE
return NDIS_STATUS_RESOURCES;
}
// 3. 关联缓冲到描述符
// 4. 设置描述符状态 OWN = 1(NIC 拥有)
Desc->Status = TX_OWN | TX_FIRST | TX_LAST | DescIndex;
Desc->Count = TotalLength;
Desc->Buffer1 = NdisGetPhysicalAddress(Buffer);
// 5. 启动发送
NdisMSend(Adapter->MiniportAdapterHandle, Packet);
// 或:写 CSR1 启动发送
WRITE_PORT_ULONG((PULONG)((PUCHAR)Adapter->IoBase + CSR1), 0);
// 6. 更新下一个描述符
Adapter->NextTxDesc = (DescIndex + 1) % NUM_TX_DESC;
return NDIS_STATUS_PENDING;
}
TX 描述符
c
typedef struct _TX_DESC {
ULONG Status; // 状态(OWN bit, status bits)
ULONG Count; // 数据长度
PHYSICAL_ADDRESS Buffer1; // 物理地址
ULONG Status2; // 扩展状态
} TX_DESC;
发送流程:
- 驱动填描述符,置
OWN = 1 - NIC 检测 OWN = 1,开始 DMA
- 完成后 NIC 置
OWN = 0,触发中断 - ISR 调用
NdisMSendComplete通知 NDIS
10.3.1.6 中断与接收
中断流程
interrupt.c(file:///d:/reactos/drivers/network/dd/dc21x4/interrupt.c):
c
VOID DC21X4DisableInterrupt(NDIS_HANDLE MiniportInterruptContext) {
PDC21X4_ADAPTER Adapter = MiniportInterruptContext;
// 清 CSR7 中断屏蔽
WRITE_PORT_ULONG((PULONG)((PUCHAR)Adapter->IoBase + CSR7), 0);
}
VOID DC21X4EnableInterrupt(NDIS_HANDLE MiniportInterruptContext) {
PDC21X4_ADAPTER Adapter = MiniportInterruptContext;
// 设置 CSR7 中断屏蔽
WRITE_PORT_ULONG((PULONG)((PUCHAR)Adapter->IoBase + CSR7), 0x1FC0); // 各种中断使能
}
VOID DC21X4InterruptHandler(NDIS_HANDLE MiniportInterruptContext) {
PDC21X4_ADAPTER Adapter = MiniportInterruptContext;
ULONG IntStatus;
// 读中断状态
IntStatus = READ_PORT_ULONG((PULONG)((PUCHAR)Adapter->IoBase + CSR3));
if (IntStatus & INT_TX_COMPLETE) {
// TX 完成
DC21X4HandleTxComplete(Adapter);
}
if (IntStatus & INT_RX_COMPLETE) {
// RX 完成
DC21X4HandleRxComplete(Adapter);
}
// 确认中断
WRITE_PORT_ULONG((PULONG)((PUCHAR)Adapter->IoBase + CSR3), IntStatus);
}
接收处理
c
VOID DC21X4HandleRxComplete(PDC21X4_ADAPTER Adapter) {
for (ULONG i = 0; i < NUM_RX_DESC; i++) {
PRX_DESC Desc = &Adapter->RxDesc[Adapter->CurrentRxDesc];
if (Desc->Status & RX_OWN) {
// NIC 还在处理这个描述符
break;
}
if (Desc->Status & RX_OK) {
// 接收成功
// 1. 分配 NDIS_PACKET
PNDIS_PACKET Packet;
NdisAllocatePacket(&Status, &Packet, Adapter->PacketPool);
// 2. 关联缓冲
NdisChainBufferAtFront(Packet, Desc->Buffer);
// 3. 通知 NDIS
NdisMIndicateReceivePacket(Adapter->MiniportAdapterHandle, &Packet, 1);
// 4. 重置描述符,准备下次接收
Desc->Status = RX_OWN;
}
Adapter->CurrentRxDesc = (Adapter->CurrentRxDesc + 1) % NUM_RX_DESC;
}
}
10.3.1.7 其他 Miniport 驱动实例
e1000.sys(Intel PRO/1000)
Intel 千兆网卡驱动。Intel 维护开源版本(e1000-8.x),ReactOS 直接使用 Intel 的源码。
关键特性:
- PCIe 总线
- 描述符环(descriptor ring)
- LSO(Large Send Offload)
- RSS(Receive Side Scaling)
- MSI 中断
rtl8139.sys(Realtek 8139)
Realtek 8139 百兆网卡驱动。Realtek 较老的方案:
- I/O 端口寄存器
- Cap+Cmd 寄存器
- 4 个 TX 描述符
- RX 环(64KB 接收缓冲)
- 简单但老旧
ne2k_pci.sys、pcnetnic.sys
- ne2k_pci:NE2000 PCI 兼容卡
- pcnetnic:AMD PCnet 驱动
10.3.1.8 总结
关键要点:
- Miniport 驱动是硬件适配层:直接操作寄存器、DMA
NdisMRegisterMiniport注册一组 NDIS 回调- 初始化流程:PCI 资源 → 中断 → 复位 → 描述符 → 中断使能
- 发送路径:填 TX 描述符,置 OWN,启动 DMA
- 接收路径 :NIC DMA → RX 描述符 → 中断 →
NdisMIndicateReceivePacket - dc21x4 实例:经典 NDIS 5.x 驱动,CSR 寄存器、TX/RX 描述符
- 其他实例:e1000(Intel)、rtl8139(Realtek)
NIC Miniport 驱动的深度技术分析
ReactOS 中的 NIC Miniport 驱动是网络栈与物理硬件之间的桥梁。本节深入分析 Miniport 驱动的设计模式、硬件交互机制以及不同网卡驱动的实现差异。
Miniport 驱动的 PCI 资源管理
Miniport 驱动初始化的第一步是通过 PCI 配置空间获取硬件资源。在 ReactOS 的 dc21x4 驱动中,DC21X4Initialize 函数通过 NdisMGetBusData 读取 PCI 配置空间,获取基地址寄存器(BAR)的值。BAR 指定了 NIC 的 I/O 端口地址和内存映射地址。
c
// PCI 配置空间读取
NdisMGetBusData(Adapter->MiniportAdapterHandle,
PCIConfiguration, 0,
&PciConfig, sizeof(PciConfig));
PCI 配置空间包含 6 个 BAR(BAR0-BAR5),每个 BAR 描述一种资源类型:
- I/O 空间 BAR:最低位为 1,表示资源在 I/O 地址空间
- 内存空间 BAR:最低位为 0,表示资源在内存地址空间
Miniport 驱动使用 NdisMMapIoSpace 将 I/O 端口或内存映射到内核虚拟地址空间,然后通过 READ_PORT_ULONG 和 WRITE_PORT_ULONG 宏访问硬件寄存器。
DMA(直接内存访问)机制
高性能网卡使用 DMA 直接在系统内存和 NIC 硬件之间传输数据,无需 CPU 参与数据复制。ReactOS 的 Miniport 驱动使用 NDIS 提供的 DMA API:
-
一致性内存分配 :
NdisMAllocateSharedMemory分配物理连续的内存,用于存放 TX/RX 描述符表。NIC 和 CPU 都可以访问这块内存。 -
映射缓冲区 :当发送数据时,Miniport 需要将系统内存中的数据包缓冲区映射为 NIC 可以访问的物理地址。这通过
NdisMMapIoSpace或 NDIS 的 DMA 映射函数完成。 -
描述符环(Descriptor Ring) :TX 和 RX 描述符通常组织为环形缓冲区。Miniport 维护一个索引(
NextTxDesc、CurrentRxDesc),指示下一个可用的描述符位置。当索引到达环末尾时,回绕到开头。
dc21x4 的 CSR 寄存器体系
DEC 21x4x 系列网卡使用 CSR(Control and Status Register)寄存器进行控制。CSR 寄存器通过 I/O 端口访问,每个寄存器 32 位宽,间隔 8 字节:
CSR0 (0x00) - 总线模式寄存器:配置 DMA 字节序、描述符大小
CSR1 (0x08) - TX 请求寄存器:写任意值启动发送
CSR2 (0x10) - 中断屏蔽寄存器:控制哪些中断源被启用
CSR3 (0x18) - 中断状态寄存器:读/写清除中断状态
CSR5 (0x28) - 状态寄存器:当前 TX 描述符地址、发送状态
CSR6 (0x30) - 命令寄存器:启动/停止发送和接收
CSR7 (0x38) - 中断状态寄存器(只读)
CSR8 (0x40) - 缺失帧计数器
CSR 寄存器的设计体现了早期网络硬件的特点:寄存器数量少,功能通过位域复用。例如,CSR6 同时包含发送使能、接收使能、全双工模式、背压控制等多个功能位。
TX/RX 描述符的详细结构
dc21x4 的 TX 描述符是一个 16 字节的结构:
c
typedef struct _TX_DESC {
ULONG Status; // 位 31: OWN(NIC 拥有),位 30: 设置位 1
ULONG Count; // 位 30-16: 缓冲1大小,位 14-0: 缓冲2大小
PHYSICAL_ADDRESS Buffer1; // 第一个数据缓冲区物理地址
PHYSICAL_ADDRESS Buffer2; // 第二个数据缓冲区(用于链式描述符)
} TX_DESC;
发送流程的详细步骤:
- CPU 填充描述符的 Buffer1 字段为数据包的物理地址
- CPU 设置 Count 字段为数据长度
- CPU 最后设置 Status 字段的 OWN 位为 1,将控制权交给 NIC
- NIC 检测到 OWN=1,通过 DMA 读取数据到 NIC 内部 FIFO
- NIC 发送数据到物理介质
- 发送完成后,NIC 清除 OWN 位,在 Status 中写入发送结果
- NIC 触发中断通知 CPU
- Miniport ISR 检查描述符的 OWN 位,发现为 0,调用
NdisMSendComplete
RX 描述符的结构类似,但 OWN 位的含义相反:OWN=1 表示 NIC 可以写入数据。
e1000 与 dc21x4 的架构差异
Intel PRO/1000(e1000)是千兆网卡,其架构比 dc21x4 复杂得多:
-
描述符格式:e1000 使用 16 字节的"高级描述符",包含更多控制字段(如 VLAN 标签、校验和卸载标志)。
-
多队列支持:e1000 支持多个 TX/RX 队列,每个队列可以绑定到不同的 CPU 核心。ReactOS 的 e1000 驱动使用单队列模式。
-
校验和卸载:e1000 可以在硬件中计算 IP/TCP/UDP 校验和,减轻 CPU 负担。ReactOS 的 e1000 驱动支持基本的校验和卸载。
-
中断机制:e1000 支持 MSI-X(消息信号中断),每个队列一个中断向量。ReactOS 使用传统的引脚中断。
-
寄存器访问:e1000 使用内存映射寄存器(MMIO),通过一个 64KB 的内存窗口访问所有寄存器。寄存器通过索引-数据对访问:先写寄存器索引到 CTRL 寄存器,再从 DATA 寄存器读取数据。
rtl8139 的简化设计
Realtek 8139 是一个高度集成的百兆网卡,设计目标是低成本:
-
接收缓冲:rtl8139 使用一个连续的 64KB 环形缓冲区接收数据,而不是描述符链。Miniport 驱动维护一个读指针,跟踪已处理的位置。
-
发送:rtl8139 提供 4 个独立的 TX 寄存器组,每个包含地址和状态。这意味着最多只能有 4 个未完成的发送操作。
-
寄存器访问:rtl8139 使用 I/O 端口寄存器,比 e1000 的 MMIO 简单但速度较慢。
-
PHY 集成:rtl8139 将 PHY(物理层收发器)集成在芯片内部,不需要外部 PHY 芯片。
中断处理的优化
Miniport 驱动的中断处理是性能关键路径。ReactOS 的中断处理分为两个阶段:
-
ISR(中断服务例程):运行在 DIRQL(设备中断请求级别),必须快速执行。ISR 读取中断状态寄存器,确认中断,然后请求 DPC。
-
DPC(延迟过程调用):运行在较低级别,可以执行更复杂的操作。DPC 处理接收到的数据包、完成发送操作、处理错误状态。
c
// ISR 伪代码
BOOLEAN MyISR(KINTERRUPT *Interrupt, PVOID Context) {
ULONG status = READ_PORT_ULONG(IntStatusReg);
if (status == 0) return FALSE; // 不是我们的中断
WRITE_PORT_ULONG(IntAckReg, status); // 确认中断
NdisMIndicateStatus(Adapter, ...); // 通知 NDIS
IoRequestDpc(DeviceObject, Irp, NULL); // 请求 DPC
return TRUE;
}
// DPC 伪代码
VOID MyDPC(KDPC *Dpc, PVOID Context) {
if (txComplete) NdisMSendComplete(...);
if (rxComplete) HandleReceive(...);
if (error) HandleError(...);
}
OID(Object Identifier)查询和设置
NDIS 使用 OID 机制查询和设置 Miniport 的状态信息。常见的 OID 包括:
OID_GEN_MAC_OPTIONS:MAC 层选项(如是否支持多播)OID_GEN_CURRENT_LOOKAHEAD:前看缓冲区大小OID_802_3_CURRENT_ADDRESS:当前 MAC 地址OID_GEN_LINK_SPEED:链路速度OID_GEN_MEDIA_CONNECT_STATUS:介质连接状态
Miniport 通过 QueryInformationHandler 和 SetInformationHandler 回调处理 OID 请求。ReactOS 的 dc21x4 驱动在 requests.c 中实现了这些处理函数。
电源管理
NDIS 5.x 引入了电源管理支持。Miniport 驱动可以注册 MiniportSetPowerD0 和 MiniportSetPowerD3 回调,在系统进入休眠时停止 NIC,在恢复时重新启动。ReactOS 的 dc21x4 驱动在 power.c 中实现了基本的电源管理。
本章代码索引
| 文件 | 内容 |
|---|---|
| dc21x4.c(file:///d:/reactos/drivers/network/dd/dc21x4/dc21x4.c) | NDIS 回调 |
| hardware.c(file:///d:/reactos/drivers/network/dd/dc21x4/hardware.c) | 寄存器操作 |
| dc21x4.h(file:///d:/reactos/drivers/network/dd/dc21x4/dc21x4.h) | 数据结构 |
| dc21x4hw.h(file:///d:/reactos/drivers/network/dd/dc21x4/dc21x4hw.h) | CSR 寄存器 |
| send.c(file:///d:/reactos/drivers/network/dd/dc21x4/send.c) | TX 路径 |
| interrupt.c(file:///d:/reactos/drivers/network/dd/dc21x4/interrupt.c) | ISR |
| init.c(file:///d:/reactos/drivers/network/dd/dc21x4/init.c) | 启动序列 |
| eeprom.c(file:///d:/reactos/drivers/network/dd/dc21x4/eeprom.c) | MAC 读取 |
| media\*.c(file:///d:/reactos/drivers/network/dd/dc21x4/) | 媒体类型 |
| e1000/(file:///d:/reactos/drivers/network/dd/e1000/) | Intel PRO/1000 |
| rtl8139/(file:///d:/reactos/drivers/network/dd/rtl8139/) | Realtek 8139 |