2. 航电网络硬件接口与设备抽象
航电计算平台上的网络驱动,本质上是在“网络协议语义”与“硬件寄存器行为”之间建立一层可移植的映射。要写出稳定、可维护的驱动,必须先看清硬件侧提供了什么:控制器如何被 CPU 访问、数据如何搬进搬出、事件如何通知软件,以及操作系统如何把这些资源抽象成统一设备模型。本节围绕典型航电网络控制器、PCIe/PCI 总线接口、寄存器空间、DMA 引擎、中断机制与操作系统设备模型展开。
一、典型航电网络控制器与总线接入
航电网络控制器通常以独立芯片或 FPGA IP 核形式存在,对外表现为一个 PCIe 端点(Endpoint)或早期 PCI 设备。以常见的航空电子全双工交换以太网(AFDX)端点控制器为例,其内部一般包含:主机接口单元、发送/接收 DMA 通道、虚拟链路(VL)调度与整形模块、冗余管理模块(Redundancy Management)、以及一组配置与状态寄存器。主机侧看到的不是“网络”,而是一块带有 BAR 空间的 PCIe 设备。
PCIe 与 PCI 的关键差异在于配置空间访问方式:PCI 使用 CF8h/CFCh I/O 端口间接访问配置空间,PCIe 则通过 ECAM(Enhanced Configuration Access Mechanism)把配置空间映射到一段内存地址,通常由 ACPI 的 MCFG 表描述。驱动枚举时读取 Vendor ID、Device ID、Class Code,再读取 BAR0~BAR5 确定寄存器与内存窗口的基地址和大小。对航电设备而言,BAR 空间往往被划分为控制寄存器区、MSI-X 表区、发送描述符环区和接收描述符环区。
二、寄存器空间的组织与访问
寄存器空间是驱动与硬件对话的“语言”。典型布局可分为四类:全局控制寄存器(复位、使能、时钟分频)、端口/VL 配置寄存器(VL ID、BAG、Lmax、冗余通道选择)、DMA 控制寄存器(环基址、环长度、门铃)、状态与计数寄存器(发送帧数、CRC 错误、冗余不匹配计数)。访问方式上,内存映射 I/O(MMIO)寄存器必须使用 readl()/writel() 这类带内存屏障的访问函数,而不能用普通指针解引用,否则编译器优化和 CPU 乱序会破坏读写顺序。
下面给出一个典型的寄存器访问与初始化片段,参数为教学示例值:
/* 假设 BAR0 已通过 pci_iomap() 映射为 base */
#define REG_CTRL 0x0000
#define REG_MAC_LO 0x0010
#define REG_MAC_HI 0x0014
#define REG_TX_RING_BASE 0x0100
#define REG_TX_RING_LEN 0x0108
#define REG_IMR 0x0200 /* 中断屏蔽寄存器 */
u32 ctrl = readl(base + REG_CTRL);
ctrl |= (1u << 0); /* bit0: 控制器使能 */
ctrl &= ~(1u << 3); /* bit3: 关闭环回测试 */
writel(ctrl, base + REG_CTRL);
writel(tx_ring_dma, base + REG_TX_RING_BASE); /* 物理地址 */
writel(TX_DESC_NUM, base + REG_TX_RING_LEN); /* 描述符个数 */
writel(0x3, base + REG_IMR); /* 使能发送完成与接收中断 */
这里 tx_ring_dma 必须是 DMA 可访问的物理地址(或 IOMMU 映射后的 IOVA),不能直接写虚拟地址。若平台启用了 IOMMU/SMMU,需先用 dma_map_single() 或 dma_alloc_coherent() 获得设备可见地址。
三、DMA 引擎与描述符环
航电网络对确定性和低抖动的需求,使 DMA 设计普遍采用描述符环而非单次搬运。发送方向:驱动在内存中准备描述符(含缓冲区物理地址、长度、VL ID、冗余标志),写门铃寄存器通知硬件;硬件按序读取描述符、取数据、组帧、经冗余通道发出,完成后写回“完成”状态位并产生中断。接收方向:驱动预先填充空缓冲区描述符,硬件收到帧后 DMA 写入缓冲区并更新描述符状态,驱动在中断或轮询中回收。
描述符环的“生产者—消费者”关系必须严格同步:驱动是发送环的生产者、接收环的消费者;硬件相反。环满时驱动应停止入队并返回 NETDEV_TX_BUSY,而不是覆盖未处理描述符。缓存一致性方面,若使用非一致性内存,需在交给硬件前执行 dma_sync_single_for_device(),回收后执行 dma_sync_single_for_cpu()。
四、中断机制与设备模型
中断是驱动感知“事件已发生”的主要手段。传统 PCI 使用 INTx 共享中断线,PCIe 推荐 MSI/MSI-X。MSI-X 允许设备申请多个独立向量,航电控制器常把“发送完成”“接收完成”“错误/冗余告警”分配到不同向量,便于按优先级处理。驱动通过 pci_alloc_irq_vectors(pdev, 1, 4, PCI_IRQ_MSIX | PCI_IRQ_MSI) 申请向量,再用 request_irq() 注册处理函数。
中断处理应遵循“上半部短、下半部重”的原则:ISR 中只读取状态寄存器、确认中断、关闭该向量中断,然后把实际收发包工作交给 NAPI 轮询或工作队列。这样可避免在中断上下文做长时间 DMA 回收,降低航电场景下的抖动。
操作系统设备模型把上述资源统一抽象:struct pci_dev 描述总线设备,struct net_device 描述网络接口,struct device 提供电源管理、DMA 映射和 sysfs 节点。驱动的 probe() 负责使能设备、映射 BAR、申请中断、注册 netdev;remove() 则逆序释放。航电平台常要求驱动支持热插拔与故障隔离,因此资源释放必须幂等,且不能依赖全局静态变量保存设备状态。
五、一个可复用的初始化检查清单
综合以上内容,驱动初始化阶段可按以下顺序自检,避免资源泄漏或访问越界:
- 确认 PCIe 链路已训练成功,读取 Link Status 寄存器;
- 使能 Bus Master 与 Memory Space,否则 DMA 无法工作;
- 映射 BAR 后先读回 Vendor/Device ID 验证映射正确;
- 分配一致性内存用于描述符环,记录设备可见地址;
- 申请 MSI-X 向量并注册中断处理函数;
- 配置 VL 表与冗余通道,再置位控制器使能;
- 注册 netdev 并启动发送/接收队列。