跳到主要内容

多设备实现

ax-net 采用 single smoltcp Interface + Router as Device + queue-level poll runtime。smoltcp 只看到一个 IP medium Router;每个物理设备在启动时消费一次, 拆成 queue/control/IRQ parts。独立 IRQ affinity domain 可以在不同 CPU 上并行处理 DMA,协议状态仍由一个固定 CPU executor 串行推进。

完整状态机、Linux v7.1 对照和失败回滚见队列级 NAPI 运行时

1. 源码与所有权

源码所有权
drivers/interface/rdif-ethportable NetDeviceParts、queue/IRQ/control trait、move-only DmaBuffer
drivers/net/rd-net校验 parts、建立 DMA pool、生成 PreparedNetDevice
queue_runtime/affinity domain、fixed-CPU executor、SPSC、budget、backpressure、IRQ lease
poll_runtime.rsprotocol requested/completed generation
device/ethernet.rsEthernet frame、ARP、neighbor、pending frame
router.rs多接口 route dispatch、loopback、smoltcp Device
service.rs唯一 smoltcp Interface、DHCP、控制面提交

运行期不保存可再次拆分的完整 driver handle。IRQ callback 不持有 RX/TX queue; protocol executor 不借用硬件 queue;调用者也不能直接借用 Wi-Fi SDIO control。

2. Consumable device parts

portable driver 实现唯一入口:

pub trait NetDevice: DriverGeneric + Send + 'static {
fn into_parts(self: Box<Self>) -> Result<NetDeviceParts, NetError>;
}

pub struct NetDeviceParts {
pub info: NetDeviceInfo,
pub control: Box<dyn NetControlEndpoint>,
pub wifi_control: Option<Box<dyn WifiControl>>,
pub poll_groups: Vec<NetPollGroupParts>,
}

NetPollGroupParts 表示一个不可拆分的 IRQ mask/rearm 域:

  • typed NetPollGroupId
  • 一个或多个 NetQueuePairParts
  • owner-task 使用的 NetPollIrqControl
  • 可选的 move-only NetOwnerStartup
  • 一个或多个带 NetIrqSourceId 的 move-only NetHardIrqEndpoint

只有具有独立 physical source 和独立 rearm 的硬件 queue 才能形成不同 group。当前 生产 backend 都发布 queue-0 group;接口已经允许多 group,但不虚构 virtio/fxmac 硬件多队列支持。

3. DMA token 与 queue contract

DmaBuffer 不实现 Clone/Copy。它从 pool 分配后只能处于以下一个位置:

RX pool/spares -> hardware RX -> RxCompletion
-> submit replacement -> RX-ready SPSC -> ProtocolRxFrame
-> EthernetDevice -> Router -> smoltcp RxToken::consume
-> RxRecycler -> recycle SPSC/overflow -> queue-local spares

TX pool -> TX-free SPSC -> protocol fills DMA frame -> TX-ready SPSC
-> submit_with_options -> batch flush -> hardware TX
-> completion reclaim -> TX-free SPSC

ITxQueue::submit()IRxQueue::submit()/recycle() 失败时,typed SubmitError 必须归还原 token。runtime 对 Retry/LinkDown 保留 token;若本轮没有 RX reclaim 等可观察进展,则完成本轮 poll、rearm IRQ 并等待未来硬件或 task 事件,不能 在 IRQ 保持关闭时立即自调度。只有 reclaim 已释放 descriptor 时,RX refill 才立即重试。 其它错误可以归还 free ring 或使 group 失败,但不能丢失 DMA ownership。

TxSubmitOptions 携带逐包 checksum 和通知方式。TxNotify::Deferred 允许驱动延迟 写 doorbell;queue executor 在完成发送批次以及提交提前返回前调用 flush(),已发布 的 descriptor 不会因为 RX backpressure 或下一次 submit 返回 Retry 而滞留。 默认 queue 只支持软件 checksum;不支持的 offload 请求必须返回原 DMA token。

RTL8125 在 descriptor 发布与 doorbell 之间使用 Release 顺序。驱动保留显式 TxSubmitOptions::checksum 请求及 checksum-v2 支持;超出 descriptor 表示范围的请求返回原 token, 调用方可以完成软件计算后重试。能力分别表示 IPv4 TCP、IPv4 UDP、IPv6 TCP、IPv6 UDP, 可通过 union() 组合,通过 intersection() 取共同能力;逐包请求还规定了完整 IP packet、L2 起点的 transport offset 和零 checksum 初值,由调用方保证这些条件,不使用 partial-checksum seed。Deferred 是提示,驱动可以提前通知;flush() 保证此前接受的提交已通知硬件,不等待发送完成。

Router 目前始终保留 smoltcp 的软件 TCP/UDP 发送计算。smoltcp 0.13.1 的 TCP 和 raw 发送在 TxToken 边界都使用默认 PacketMeta,不能据此区分普通协议报文和调用者 要求保留的 raw payload。IPv4 UDP checksum 为零合法地表示没有生成 checksum,见 RFC 768。Ethernet 和 loopback 因而只复制 传输层字节,不再根据零值请求卸载,也不重算 raw packet 的 checksum。待协议生成端 能显式传递逐包计算意图后,才能重新启用这条自动卸载路径。

router_keeps_software_checksums_even_with_offload_capable_devices 验证即使出口支持 卸载,TCP/UDP 的 rx()tx() 仍为 true;动态加入无卸载设备也不会留下过期的 smoltcp checksum capability。驱动支持 IPv6 checksum 不代表物理 Ethernet IPv6 协议已完整接入。

广播与 IPv6 组播 fanout 在首次发送时记录非 loopback 出口。某个出口返回 Again 时,原包继续留在 Router TX 队首;后续只重试尚未接受该包的出口,已经成功或返回 永久错误的出口不重复发送。一次重试没有任何进展时等待队列事件,不立即空转。 所有出口处理完成后才出队,再为下一包重新记录出口;额外状态只保存设备索引。

Ethernet 发送将 IPv4 组播地址的低 23 位映射到 01:00:5e,将 IPv6 组播地址的低 32 位映射到 33:33,分别遵循 RFC 1112 §6.4RFC 2464 §7。这两类发送直接使用 映射后的目的 MAC,不经过 ARP;有限广播和子网广播继续使用 ff:ff:ff:ff:ff:ff

非一致 DMA 平台的 CPU/device sync 在 DmaBuffer 的 read/write 与 driver submit/reclaim 边界完成。跨 CPU 只 move token,不共享可变 payload reference。

4. IRQ affinity domain

平台(axruntime)在构造 NetworkDeviceInput 时已把每个 driver source 解析为 ResolvedNetIrqSource { source_id, irq };builder 内部把每个 endpoint 的 source ID 映射到唯一 physical IrqId,同一 source ID 解析出多个 IrqId 或存在未被任何 endpoint 引用的 source 都会使初始化失败。共享同一 IRQ 的 group 通过并查集合并为一个 affinity domain,domain 按稳定创建顺序轮转分配在线 CPU。

硬性不变量:

IRQ callback CPU == group poll CPU == owner_cpu

注册顺序:

  1. 构造全部 group state、DMA pool、SPSC 和 per-CPU executor。
  2. executor 设置 AxCpuMask::one_shot(owner_cpu),yield 后回报 affinity-ready。
  3. registrar 以 NonReentrant + AutoEnable::No + Fixed(owner_cpu) 注册 disabled action,随后 enable 全部 registration。
  4. owner worker 执行 one-shot startup;AIC 固件/FDRV 初始化只允许发生在这里(IRQ 已注册并 enable,队列尚未发布)。
  5. owner worker 预填 TX pool、执行 initial refill 并完成第一次 rearm_and_check()
  6. startup Wi-Fi transaction 成功并刷新 MAC 后才发布 service。

shared action affinity 冲突、fixed route 不支持、worker pin 失败或 registration 返回的 CPU 不一致都会使整个物理网络初始化失败。没有 Any affinity、远程 IRQ continuation、 无 IRQ 路径或周期 poll fallback。

5. Hard IRQ 与 group 状态机

hard endpoint 只能做 bounded mask/ack/status snapshot,返回:

  • Spurious:共享 IRQ 不属于本 endpoint;
  • Schedule(snapshot):有真实 queue work;
  • ProbeDeferred:例如 virtio transport gate 正被 task owner 使用,需要同 CPU 稍后探测。

IRQ 不能分配、访问 DMA payload、调用 smoltcp、调用任意 waker或在 transport gate 上 自旋。

每个 group 的原子状态为:

IDLE -> SCHEDULED -> POLLING -> IDLE
| ^
+ MISSED
any state -> DISABLED

首次 publish 才通知 executor;scheduled/polling 中的新事件只置 MISSED。poll owner 结束时用 CAS:观察到 MISSED 则回到 SCHEDULED;否则先变 IDLE,再执行硬件 rearm_and_check()。rearm 窗口发现 pending 会立即重新 schedule。

DISABLED 是吸收态。poll completion 使用 CAS,不能把 concurrent disable 重新写成 scheduled。

6. Budget 与 backpressure

一个 group poll 按顺序处理:

  1. 上轮滞留的 pending_tx_free 回推 TX-free ring;
  2. TX completion reclaim;
  3. TX submission 与批次 flush()
  4. 上轮滞留的 pending_rx 发布到 RX-ready ring;
  5. 已消费 RX token 回收到 queue-local spare cache;
  6. RX reclaim、replacement refill 与完成项发布。

各类使用 64 项子预算,每 CPU executor round 使用 256 项总预算。任一子预算用尽、CPU round 用尽、MISSED 或硬件仍有工作时,group 保持 IRQ 关闭并重新排队。

四条 SPSC ring 都预分配且有界:

  • RX-ready 满:queue owner 保留 pending_rx
  • replacement submit retry:在有界 pending_rx_refill 中同时保留 completion 和 replacement;继续 reclaim,避免软件队列因 completion ring 满而无法推进;
  • RX replacement 分配失败或达到 token 预算:丢弃当前 packet、累计 RX drop,并重投原 token;若 submit 返回 Retry,仍保留 token 等待 completion 进展或 rearm 后的硬件事件;
  • TX-free 满:保留 pending_tx_free
  • TX submit retry:保留 pending_tx

blocked group 不 busy-wait,也不等待 timer。protocol owner 消费/释放空间时调用 group-local task schedule;只有目标 group 被精准激活。

7. Protocol frame port 与 Router

QueueFramePort 位于 protocol owner 一侧。receive_owned() 从 RX-ready ring 取 completion,返回带回收器的 ProtocolRxFrame。它保留 token 到 smoltcp 消费结束; ARP 和未知二层帧在原 token 回收前处理。回收 ring 满时由 RxRecycler 暂存 token, queue owner 再将其转入 spare cache,不提前释放 DMA mapping。硬件 ring 外最多取得 max(RX capacity, 64) 个 replacement token;达到上限后只复用回收 token 或丢包, 不会因协议消费者长期持有 packet 而无限增加 DMA 内存。

TX 优先从 spare/free ring 取 token,在 DMA buffer 中写 Ethernet header、IP packet 及 ETH_ZLEN padding。NoQueue 在忙时返回 AgainFifo 只在积压时保留有界 inline frame 和原提交选项,按序重试。各 RX 接口都会推进待发送 FIFO,避免仅使用 owned RX 的生产路径遗漏发送重试。Router peek 待发 packet,仅在接受提交后 dequeue。 非 DMA 端口沿用 ProtocolEthernetFrame 的兼容复制接口。

DeviceDeviceRxPacketDeviceRxPoll 均为 crate 内部接口;DMA owned packet 只服务本 crate 的 queue runtime。外部 frame port 使用公开的兼容接收接口,当前没有 承诺通用 L3 owned backing API。Router 优先选择 owned RX,其余接收入口保留为兼容适配。

EthernetDevice 在 frame port 上完成:

  • Ethernet header 解析/封装;
  • IPv4 与 ARP 过滤;
  • neighbor cache 与 300 秒 TTL;
  • ARP retry 与有界 pending packet;
  • L2 byte/packet/error/drop 统计。

Router 对 smoltcp 暴露 Medium::Ip。RX packet 携带 ingress InterfaceId,用于 DHCP/TCP SYN snoop 与 cmsg metadata;TX 根据目标、source address、最长前缀和 metric 选择 frame port。loopback 直接注入 protocol RX buffer,不经过物理 queue。

8. 唯一 protocol executor

queue executor 只在 frame/token 到达时调用 request_poll()ProtocolPollRuntime 增加 requested generation 并唤醒固定 CPU net-protocol task;只有该任务可以持有 Service/SocketSet 并调用 smoltcp poll。

同步 egress flush 同样只 request 并等待自己的 completed generation,调用线程不会 成为第二个 poll owner。request 与 completion 竞争时,worker 清除 scheduled 后再次 比较 generation,确保新请求至少再运行一轮。

9. Wi-Fi/SDIO specialization

AIC probe 只识别 chip variant 并把 SDHCI controller source move 到 hard endpoint, 不执行固件/FDRV I/O。owner startup 在固定 CPU 创建 bus;top half 只识别并 mask CARD_INT,owner CPU 负责 FIFO drain、TX、firmware command completion、AP event 与 rearm。SDHCI task rearm 在 unmask 后立即读 CARD_INT status,若 level 已挂起则重新 mask 并返回 pending。

Wi-Fi startup/reconfigure 使用有界 owner control queue。executor quiesce group 后才 执行 SDIO/MMIO control,完成后 rearm,再让 protocol owner更新 STA DHCP 或 SoftAP static/DHCP-server state。

当前 variant policy:

  • AIC8800D80:V3 queue encoding、software IRQ bit 清源、Function 1 command/data FIFO;
  • AIC8800DC:V1 queue/status,Function 1 data FIFO、Function 2 firmware mailbox;
  • AIC8801、AIC8800D80X2、AIC8800DW 和未知变体:缺少经验证的 profile 或固件, probe fail-closed。

任何 variant 都没有 OOB callback、独立 RX/TX task 或 kicker。

10. Driver group mapping

Driver当前 grouphard endpointtask owner/rearm
virtio-netqueue 0 RX/TX仅真实 QUEUE_INTERRUPT;gate 竞争为 ProbeDeferredcallback disable + 非消费 peek_used double-check;reclaim 才消费 completion
E1000queue 0 RX/TXICR snapshot/maskdescriptor drain 后恢复 IMS 并复查
RTL8125queue 0 RX/TXstatus gate/ack/maskdeferred refill、completion、pending status 复查
FXMACqueue 0status snapshot;gate 竞争 deferredowner drain/rearm
Loongson GMACqueue 0DMA status snapshot/maskRX restart、ACK/rearm
AIC/SDHCIqueue 0nested CARD_INT mask/statusFIFO/command/TX + controller/chip rearm/shutdown

11. Lifecycle 与回滚

builder 的失败路径按发布顺序反向执行:拒绝新 Wi-Fi request、disable/synchronize IRQ lease。同步成功时通知 executor stop,由 owner CPU quiesce 并执行 driver shutdown(), 然后 join;同步失败时发送 QUARANTINE,保留完整 callback、executor、control 与 backing graph,不再并发触碰驱动硬件。 只有 shutdown 证明硬件已不能访问 backing 时才 drop queue token 与 device parts;无法 证明时隔离完整 executor ownership graph。NetworkQueueRuntime::Drop 保持同一顺序。 service 只有在 builder 完整成功后进入全局 OnceLock,因此不存在半发布接口。

物理设备要求完整 IRQ、fixed affinity、mask/rearm 和 worker pin。无物理 NIC 时可以 启动 loopback-only;发现物理设备但能力不完整时必须失败,不能把 loopback 成功当作 物理网络成功。

12. 可观测不变量

每个 group 暴露测试态 NetQueueStats:IRQ、schedule、MISSED、poll batch、budget exhaustion、spurious、probe deferred、rearm race、owner CPU、last IRQ CPU、last poll CPU 与 remote wake。

验收断言:

  • last_irq_cpu == last_poll_cpu == owner_cpu
  • irq_to_poll_remote_wake == 0
  • 一个 IRQ 不改变无关 group 的 schedule/wake;
  • idle queue executor 没有周期唤醒;
  • burst 在 IRQ mask 期间按预算合并,drain/rearm 后才重开;
  • stop 后 group 拒绝新 schedule,DMA token 不重复、不泄漏。

CPU hotplug、运行时新增/删除 NIC、RPS/RFS/GRO、busy-poll、协议状态分片和真实 virtio/fxmac 多硬件队列不在当前范围。