Reactos 第 10 章 网络操作 — 10.3.1 NIC驱动

第 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.chardware.csend.cinterrupt.ceeprom.cmedia.cphy.cinit.c 等。


概述

NIC Miniport 驱动的核心职责:

  1. 探测与配置:通过 PCI BAR 找到 I/O 端口、IRQ
  2. 初始化硬件:复位 NIC、设置 MAC 地址、配置收发描述符
  3. 数据发送:构造 TX 描述符,启动 DMA
  4. 数据接收:解析 RX 描述符,把包转给 NDIS
  5. 中断处理:响应 NIC 中断,处理完成事件
  6. 错误恢复:超时、总线错误、链接中断

什么是 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(如 NdisMIndicateReceivePacketNdisMSendComplete)向上层通知事件。

硬件交互模型

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 使用 KeAcquireSpinLockKeReleaseSpinLock 保护关键数据结构。

资源管理 :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))完成:

  1. 读取 PCI 配置空间
  2. 映射 I/O 寄存器
  3. 分配共享内存(DMA 描述符表)
  4. 分配中断
  5. 读取 MAC 地址(从 EEPROM)
  6. 复位 NIC
  7. 配置 TX/RX 描述符
  8. 启用中断
  9. 设置 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;

发送流程:

  1. 驱动填描述符,置 OWN = 1
  2. NIC 检测 OWN = 1,开始 DMA
  3. 完成后 NIC 置 OWN = 0,触发中断
  4. 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 总结

关键要点

  1. Miniport 驱动是硬件适配层:直接操作寄存器、DMA
  2. NdisMRegisterMiniport 注册一组 NDIS 回调
  3. 初始化流程:PCI 资源 → 中断 → 复位 → 描述符 → 中断使能
  4. 发送路径:填 TX 描述符,置 OWN,启动 DMA
  5. 接收路径 :NIC DMA → RX 描述符 → 中断 → NdisMIndicateReceivePacket
  6. dc21x4 实例:经典 NDIS 5.x 驱动,CSR 寄存器、TX/RX 描述符
  7. 其他实例: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_ULONGWRITE_PORT_ULONG 宏访问硬件寄存器。

DMA(直接内存访问)机制

高性能网卡使用 DMA 直接在系统内存和 NIC 硬件之间传输数据,无需 CPU 参与数据复制。ReactOS 的 Miniport 驱动使用 NDIS 提供的 DMA API:

  1. 一致性内存分配NdisMAllocateSharedMemory 分配物理连续的内存,用于存放 TX/RX 描述符表。NIC 和 CPU 都可以访问这块内存。

  2. 映射缓冲区 :当发送数据时,Miniport 需要将系统内存中的数据包缓冲区映射为 NIC 可以访问的物理地址。这通过 NdisMMapIoSpace 或 NDIS 的 DMA 映射函数完成。

  3. 描述符环(Descriptor Ring) :TX 和 RX 描述符通常组织为环形缓冲区。Miniport 维护一个索引(NextTxDescCurrentRxDesc),指示下一个可用的描述符位置。当索引到达环末尾时,回绕到开头。

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;

发送流程的详细步骤:

  1. CPU 填充描述符的 Buffer1 字段为数据包的物理地址
  2. CPU 设置 Count 字段为数据长度
  3. CPU 最后设置 Status 字段的 OWN 位为 1,将控制权交给 NIC
  4. NIC 检测到 OWN=1,通过 DMA 读取数据到 NIC 内部 FIFO
  5. NIC 发送数据到物理介质
  6. 发送完成后,NIC 清除 OWN 位,在 Status 中写入发送结果
  7. NIC 触发中断通知 CPU
  8. Miniport ISR 检查描述符的 OWN 位,发现为 0,调用 NdisMSendComplete

RX 描述符的结构类似,但 OWN 位的含义相反:OWN=1 表示 NIC 可以写入数据。

e1000 与 dc21x4 的架构差异

Intel PRO/1000(e1000)是千兆网卡,其架构比 dc21x4 复杂得多:

  1. 描述符格式:e1000 使用 16 字节的"高级描述符",包含更多控制字段(如 VLAN 标签、校验和卸载标志)。

  2. 多队列支持:e1000 支持多个 TX/RX 队列,每个队列可以绑定到不同的 CPU 核心。ReactOS 的 e1000 驱动使用单队列模式。

  3. 校验和卸载:e1000 可以在硬件中计算 IP/TCP/UDP 校验和,减轻 CPU 负担。ReactOS 的 e1000 驱动支持基本的校验和卸载。

  4. 中断机制:e1000 支持 MSI-X(消息信号中断),每个队列一个中断向量。ReactOS 使用传统的引脚中断。

  5. 寄存器访问:e1000 使用内存映射寄存器(MMIO),通过一个 64KB 的内存窗口访问所有寄存器。寄存器通过索引-数据对访问:先写寄存器索引到 CTRL 寄存器,再从 DATA 寄存器读取数据。

rtl8139 的简化设计

Realtek 8139 是一个高度集成的百兆网卡,设计目标是低成本:

  1. 接收缓冲:rtl8139 使用一个连续的 64KB 环形缓冲区接收数据,而不是描述符链。Miniport 驱动维护一个读指针,跟踪已处理的位置。

  2. 发送:rtl8139 提供 4 个独立的 TX 寄存器组,每个包含地址和状态。这意味着最多只能有 4 个未完成的发送操作。

  3. 寄存器访问:rtl8139 使用 I/O 端口寄存器,比 e1000 的 MMIO 简单但速度较慢。

  4. PHY 集成:rtl8139 将 PHY(物理层收发器)集成在芯片内部,不需要外部 PHY 芯片。

中断处理的优化

Miniport 驱动的中断处理是性能关键路径。ReactOS 的中断处理分为两个阶段:

  1. ISR(中断服务例程):运行在 DIRQL(设备中断请求级别),必须快速执行。ISR 读取中断状态寄存器,确认中断,然后请求 DPC。

  2. 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 通过 QueryInformationHandlerSetInformationHandler 回调处理 OID 请求。ReactOS 的 dc21x4 驱动在 requests.c 中实现了这些处理函数。

电源管理

NDIS 5.x 引入了电源管理支持。Miniport 驱动可以注册 MiniportSetPowerD0MiniportSetPowerD3 回调,在系统进入休眠时停止 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
相关推荐
zhengfei6111 小时前
小白级手册——全面剖析红队信息收集思考
网络·安全·web安全
颜*鸣&空1 小时前
通信原理学习
网络
初圣魔门首席弟子2 小时前
Node.js 详细介绍(知识库版)
windows·qt·node.js·知识库
我是一颗柠檬2 小时前
【计算机网络全面教学】网络设备与故障排查,从集线器到Wireshark抓包实战Day7(2026年)
网络·计算机网络·wireshark
我是小bā吖2 小时前
Claude Code 模型接入阿里云 AI 网关并统计不同使用者的模型用量
网络·人工智能·阿里云
xsc-xyc2 小时前
用 Tailscale + Syncthing 实现手机、电脑与 NAS 的跨网络文件同步
linux·网络·网络安全·智能手机·电脑
sdm0704272 小时前
多路转接-select
网络·c++·select·多路转接
VidDown3 小时前
视频帧率技术详解:从 24fps 到 120fps,帧率如何影响你的观看体验?
网络·网络协议·编辑器·音视频·视频编解码·视频
TechWayfarer3 小时前
苏超赛事网站安全防护:WAF、DDoS与仿冒页面如何联动治理
网络·python·安全·flask·ddos