☰
Tock 以太网支持落地路线图:2025-02-24 网络工作组会议纪要深度解读
2026/10/10 8:16:59 网站建设 项目流程
  • 操作系统
  • 嵌入式
  • 嵌入式OS

【免费下载链接】tock

A secure embedded operating system for microcontrollers

项目地址:https://gitcode.com/gh_mirrors/to/tock
点击查看免费下载

本文基于 Tock Network WG Meeting Notes(2025-02-24) 编写,并结合 Tock 内核仓库中 Ethernet HIL、EthernetTap 用户态驱动、LiteEth/VirtIO 硬件适配层及 StreamingProcessSlice 流式缓冲机制的源码,还原 2025 年 2 月 Tock 以太网能力从「实验分支」走向「正式对外发布」的关键决策过程。读者可从中掌握 Tock 以太网栈的当前支持范围、内核态到用户态的帧收发路径,以及社区在「子模块 + 构建系统」上的取舍。

会议概览

  • 日期:2025 年 2 月 24 日
  • 参与者:Branden Ghena、Leon Schuermann、Alex Radovici、Tyler Potyondy
  • 议程:
    1. 各方向进展更新(Updates)
    2. 以太网(Ethernet)
  • 核心议题:将 Tock 的以太网支持从实验状态推进到可对外宣传的正式能力,涉及 HIL 稳定化、驱动合入、用户态协议栈(LwIP)以及构建系统对第三方子模块的处理。

本次会议的结论可以概括为一句话:在 EthernetTap 驱动 PR 合入 staging 分支之后,Tock 就具备了正式对外宣传以太网支持的资格。

进展更新:RP2040 的 PIO SPI 与 WiFi 探索

会议开头,Branden Ghena 汇报了 RP2040 平台上的网络相关进展:

  • RP2040 PIO SPI 已取得突破:学生团队已经让中断(interrupts)工作起来,计划整理代码后向 Tock 提交 PR。RP2040 的 PIO(Programmable I/O)是其特色外设,可用于实现灵活的时序协议,SPI 是其中应用场景之一。
  • RP2040 WiFi 通信方式特殊:RP2040 通过单根导线与 Infineon WiFi 芯片通信,本质上是一种半双工(half-duplex)SPI 形态,实现上比较特殊,需要进一步研究。

从仓库看,RP2040 的无线外设驱动位于 capsules/extra/src/cyw4343/driver.rs,该驱动目前仅出现在 capsules 的 extra 集合中,尚未与EthernetAdapterDatapathHIL 打通,与会议中「WiFi 仍需更多工作」的判断一致。

以太网:Tock 从「实验」走向「正式支持」

现场演示:LwIP 协议栈跑通 QEMU

Leon Schuermann 在会议上做了现场演示(live demo):在 QEMU 模拟器上运行 LwIP(Lightweight IP)应用栈,以太网链路工作正常,能够通过 HTTP 提供(serve)一个基础的着陆页(landing page)。

该演示对应的用户在用户态(userspace)侧的成果是 LwIP Ethernet Libtock-C App(libtock-c 的 PR #494)。这意味着完整的 TCP/IP 协议栈运行在用户态,而 Tock 内核只负责把裸以太网帧(raw IEEE 802.3 frames)在内核与用户态之间搬运。

当前以太网硬件支持范围

针对 Branden 的提问「我们目前有哪些硬件支持以太网?」,Leon 给出了清单:

平台说明
QEMU通过 VirtIO 网络设备仿真,配合qemu_rv32_virt/qemu_rv64_virt板级支持
LiteX基于 LiteEth MAC 的软核以太网(FPGA SoC)
部分 STM32F4带片上以太网 MAC 的型号
任意带 USB 的板子通过USB-CDC ECM(Ethernet Control Model)虚拟网卡,理论上任何有 USB 的板子都能用,且能与任何主流桌面操作系统对接

这些声明在仓库源码中都有对应实现:

  • LiteX LiteEth:chips/litex/src/liteeth.rs 实现了EthernetAdapterDatapathHIL,并通过ETHMAC_SRAM_WRITER_*(接收)与ETHMAC_SRAM_READER_*(发送)寄存器组驱动 LiteEth MAC;boards/litex/arty/src/main.rs 与 boards/litex/sim/src/main.rs 已将其接入板级。
  • VirtIO 网络卡:chips/virtio/src/devices/virtio_net.rs 实现了 VirtIO Net 设备;boards/qemu_rv32_virt/src/lib.rs 在探测到 VirtIO NetworkCard 时实例化EthernetTapDriver并桥接到VirtIONet,探测不到则禁用(对应 README 中 "VirtIO NetworkCard device not found, disabling EthernetTapDriver" 的提示,见 boards/qemu_rv32_virt/README.md)。

合入路线:staging → master

会议的合入策略非常明确,按以下顺序推进:

  1. 先把现有 PR 合入staging 分支;
  2. 合并EthernetTap PR(即 capsules/extra/src/ethernet_tap.rs 对应的用户态 TAP 驱动);
  3. 从 staging 向 master 提 PR。

Leon 特别指出,staging → master 的那个 PR 将同时包含三件套:

  • HIL(硬件抽象层接口,即EthernetAdapterDatapath);
  • HIL 的一个硬件实现(如 LiteEth、VirtIONet);
  • HIL 的一个使用者(即 EthernetTap 用户态驱动)。

这正是 Tock 社区典型的「接口 + 实现 + 使用者」三位一体合入模式,保证 HIL 不是空头支票——既有实现验证,又有真实消费方。

后续事项:文档化支持矩阵

Branden 提出一个重要诉求:要把「哪些平台支持、哪些不支持」明确记录下来,用一个表格说明各芯片对各功能(feature)的支持情况。这对应 Tock 文档体系中的板级 README(例如 boards/qemu_rv32_virt/README.md 明确列出 VirtIO 网络卡存在/缺失时的行为),避免用户踩坑。

LiteX 驱动需要随 HIL 更新到最新接口,但这项工作可以在合入 master 之后再做。

远期计划:SmolTCP 与 libtock-c 用户态栈

会议明确了两条后续主线:

  • 内核态 TCP/IP 栈:更新 Ionut 的 SmolTCP 方案,作为 Tock 内核内的网络栈方向;
  • 用户态网络栈:优先完成 libtock-c 用户态侧的工作,即streaming process slice API与LwIP 栈 + 应用两个 PR。

这两个 PR 都已经接近完成,其中streaming process slice API 应先行合入。

深入源码:Tock 以太网的内核实现路径

原始以太网帧 HIL:EthernetAdapterDatapath

kernel/src/hil/ethernet.rs 定义了 Tock 内核的以太网抽象层,其设计要点如下:

  • 只覆盖裸数据通路(raw datapath):传输 IEEE 802.3 以太网帧,要求帧已包含带源/目的地址的以太网头,不含 FCS(帧校验序列)尾部。
  • 接口声明:该 HIL 目前不稳定,会被后续贡献扩展为更完整的内核内网络栈;但已足以把以太网 MAC / 适配器桥接到用户态,由用户态运行网络栈。

核心 traitEthernetAdapterDatapath提供四个方法:

  • set_client:注册EthernetAdapterDatapathClient回调;
  • enable_receive/disable_receive:启用/禁用帧接收;HIL 明确要求 MAC 不得在启用前或禁用后触发received_frame;
  • transmit_frame(frame_buffer, len, transmission_identifier):发送一帧。帧必须位于缓冲区偏移 0 处、含以太网头、不含 FCS;长度以u16表示(最大帧尺寸上限为u16::MAX字节,覆盖含各种 Jumbo Frame 选项在内的所有常见 MTU)。

异步完成通过客户端 traitEthernetAdapterDatapathClient的两个回调体现:

  • transmit_frame_done(err, frame_buffer, len, transmission_identifier, timestamp):发送完成或异步出错,错误码包括FAIL(内部错误)、BUSY(MAC 忙)、OFF(未初始化)、NODEVICE(无活动链路);timestamp为可选发送时间戳(面向 IEEE 1588 PTP 等场景预留);
  • received_frame(frame, timestamp):收到一帧,同样携带可选接收时间戳。

EthernetTap:面向用户态的 TAP 式网卡驱动

capsules/extra/src/ethernet_tap.rs 实现了会议中反复提及的EthernetTap 驱动——一个「用户态应用 TAP 式网络驱动」,模拟 Linux/FreeBSD 的 TAP 虚拟网络设备语义,让用户态应用直接操作裸以太网帧。这是本次会议合入路线图中的关键 PR。

其核心机制包括:

  • 多应用复用:驱动在所有应用之间复用底层网络接口。所有应用都allow一个接收缓冲区,每帧入站帧都会被放入所有应用的接收缓冲区(类似 VEPA 模式下的 MacVTap:应用都能收发链路帧,但一般无法彼此直接通信,除非对端支持 hairpin 模式;若需应用间互通,可在内核实例化 hairpinning 桥或为每个进程提供独立 TAP)。

  • 基于StreamingProcessSlice的无损流式接收:驱动把收到的帧连同每帧驱动头一起写入StreamingProcessSlice。每帧头格式为:

    • Bytes 0–1(u16,本机字节序):flags,bit 0 表示「接收时间戳有效」;
    • Bytes 2–3(u16,本机字节序):帧长度(不含本头);
    • Bytes 4–11(u64,本机字节序):接收时间戳;
    • Bytes 12 起:帧内容(含以太网头、不含 FCS)。

    该头让用户态无需解析帧内容即可高效定位帧边界。驱动仅在成功追加数据后调度 upcall;应用须遵循StreamingProcessSlice语义,通过原子allow换缓冲实现无损接收。

  • 发送路径:同时最多传输一帧。把待发帧拷贝进内核内存是同步操作,实际发送可能异步,因此发送开始后通过 upcall 通知应用,用户态不得在前一次发送未被 upcall 确认前再次发送。MAX_MTU硬编码为1518字节(802.1q VLAN 标签 + 1500 字节负载 MTU,不含 4 字节 FCS)。

  • 驱动号:capsules_core::driver::NUM::EthernetTap。

其系统调用接口完整定义如下(源码impl SyscallDriver for EthernetTapDriver中command实现可查证):

Command 系统调用

命令号功能返回
0检测驱动是否安装成功,或ENOSUPPORT
1查询进程本地 RX 统计success_u32_u32_u32:(rx_frames, rx_bytes, rx_frames_dropped),计数在u32::MAX处回绕
2查询进程本地 TX 统计success_u32_u32:(tx_frames, tx_bytes)
3发送allow_ro::TX_FRAME缓冲区中的帧参数 1 为最多发送字节数(自偏移 0 起,受MAX_MTU限制,最多u16::MAX),参数 2 为帧发送标识符(原样带回 upcall 2);失败返回BUSY(有帧正在发送)或SIZE(缓冲区过小或超 MTU)

Subscribe 系统调用

  • upcall0:暂不支持(驱动被其他进程释放时的通知);
  • upcall1:接收帧写入流式切片后触发;
  • upcall2:帧发送完成时触发。参数 1 为 statuscode(0 或 ErrorCode);参数 2 为 flags 与长度打包的u32(bits 16–30 为 flags,bit 16 表示 TX 时间戳有效;bits 0–15 为已发送字节数);参数 3 为帧发送标识符。

Allow 系统调用

  • rw-allow0:接收帧用的StreamingProcessSlice;
  • rw-allow1:发送帧元数据缓冲区(至少 8 字节,bytes 0–7 为发送时间戳,big endian);
  • ro-allow0:待发送帧缓冲区(仍需通过 command 3 触发发送)。

StreamingProcessSlice:无损内核→用户态流式传输的基石

kernel/src/utilities/streaming_process_slice.rs 实现了会议中提到的streaming process slice抽象。它解决的核心问题是:ADC 采样、网络栈等场景需要内核向进程持续、无损地输送不受进程速率控制的数据流,且不想依赖内核侧缓冲。

其协议要点(版本 0):

  1. 进程分配两个缓冲区;
  2. 按格式初始化第一个缓冲区:version字段置 0,清空exceeded标志,可自行设置/清除halt标志,保留位必须为 0,offset置 0;
  3. 进程将缓冲区allow给内核驱动;
  4. 内核从data字段 +offset处写入数据,每次写入后递增offset。只有完整写入一个 chunk 才递增offset;若空间不足则置位exceeded标志(bit 0)。若halt与exceeded同时置位,内核停止写入;若exceeded置位而halt清除,内核仍尝试写入更小的后续 chunk(可能丢弃部分包);
  5. 内核调度 upcall 通知进程(进程不得依赖 upcall 数量,须以缓冲区头中的offset与 flags 为准);
  6. 进程准备第二个缓冲区,通过allow原子换入;
  7. 进程处理第一个缓冲区中的 chunk,同时内核向新缓冲区持续写入——这就是双缓冲乒乓(ping-pong)实现无损流式传输。

缓冲区头布局(8 字节):

0 2 4 6 8 +-----------+-----------+-----------------------+----------... | version | flags | write offset (32 bit) | data +-----------+-----------+-----------------------+----------... | 000...000 | x{14},H,E | <native endian u32> |
  • version:u16,目前仅支持版本0;
  • flags:u16 位域,E(bit 0)为exceeded,H(bit 1)为halt,其余 14 位保留(进程必须清零,内核遇到未知保留位会拒绝操作);
  • offset:u32,本机字节序的写入偏移。

对应实现细节可在 kernel/src/utilities/streaming_process_slice.rs(append_chunk)与 kernel/src/utilities/streaming_process_slice.rs(append_chunk_from_iter)中查看,且文件底部附有完整的单元测试(test_empty_process_slice、test_header_only_process_slice)验证头部解析、exceeded置位与空 chunk 行为。

会议中提到 API「接受两个缓冲区」的讨论,正是指这一双缓冲协议:用户如果愿意,仍可使用单一连续缓冲区,但双缓冲是推荐形态。EthernetTap 的received_frame实现正是通过StreamingProcessSlice::append_chunk_from_iter(frame_header.iter().chain(frame.iter()))把「驱动头 + 帧」整体追加进进程缓冲,并只在首次追加成功时调度 upcall。

构建系统之争:git 子模块的「自动更新」与「文件清单」取舍

会议花了相当篇幅讨论一个工程化痛点:Tock 的 Makefile 体系会自动初始化/更新 git 子模块(包括 LwIP 这类第三方代码库),但子模块目录在 Make 首次运行时可能还是空目录,导致「不能依赖子模块目录中任何文件在 Make 首跑时就存在」。

Leon 遇到的具体问题是:

  • 无法阻止 Uncrustify(代码格式化工具)对 LwIP 子模块尝试格式化;
  • 无法在 Makefile 初始化阶段引入 LwIP 内的 Filelists.mk(该文件以变量形式列出 LwIP 的全部 C 源文件)——因为子模块此时可能尚未就绪;
  • Brad 建议增加Makefile.setup来初始化和更新子模块,Tock 现有 Makefile 体系会在发现该文件时「自动」处理。

会上提出的候选方案包括:

  1. 两阶段构建系统:先发现 setup 问题、再重新调用同一条 make 命令(Leon 认为「很 messy」);
  2. 自己 vendoring(内置)文件清单:直接把 LwIP 的Filelists.mk内容硬编码进仓库(Branden 倾向此方案:「我认为最简单的方式就是把文件清单自己 vendor 进来」);
  3. 独立编译库:用一个独立 make 步骤先编译库(不依赖 Tock),再在构建 Tock 时引入(Tyler 提到 OpenThread 曾用「单独编译库」的做法,且 OpenThread 子模块固定在某 release,make时自动初始化并拉取)。

Tyler 还补充了一个现实经验:给网络栈新增功能(比如 MQTT 示例)往往「很难 just work」——栈应可扩展,但新增一个子示例可能破坏已有移植,因为新特性总是需要在多个层面(库、移植、示例)同时工作。

最终 Branden 的结论是:vendoring 文件清单仍是最省事的方案,代价是子模块更新时可能因未同步文件清单而出现未定义符号(undefined symbols)之类的问题——这是需要接受的成本。同时应在 README 里说明Filelists.mk是怎么来的、以及如何随版本更新,降低维护门槛。

结论与行动清单

本次会议收敛出的可执行结论如下:

  1. 合入顺序:先合入 streaming process slice API 相关 PR(双缓冲接口),再合入 LwIP 栈 + 应用 PR;
  2. staging 分支:合入 EthernetTap PR 后,staging → master 的 PR 将携带「HIL + HIL 硬件实现 + HIL 使用者」三件套,标志着 Tock 以太网支持正式对外宣传;
  3. 文档化:需要建立「哪些芯片支持哪些网络功能」的支持矩阵表,明确标注工作/不工作的部分;
  4. LiteX 驱动:随后在 master 上把 LiteX 驱动更新到最新 HIL 接口;
  5. 远期:推进内核态 SmolTCP 方案,以及 libtock-c 用户态栈的更多 PR。

对开发者而言,若想在 Tock 上跑通以太网,当前可操作的路径是:使用带 LiteEth 的 LiteX 板或带 VirtIO 网卡的 QEMU 目标(boards/qemu_rv32_virt),在内核侧依赖 kernel/src/hil/ethernet.rs 定义的 HIL 与 capsules/extra/src/ethernet_tap.rs 驱动,在用户态通过 libtock-c 的 LwIP 应用(PR #494 对应成果)完成 TCP/IP 栈接入;USB-CDC ECM 则为任意带 USB 的板子提供了虚拟以太网通道。

  • 操作系统
  • 嵌入式
  • 嵌入式OS

【免费下载链接】tock

A secure embedded operating system for microcontrollers

项目地址:https://gitcode.com/gh_mirrors/to/tock
点击查看免费下载

相关推荐

上一篇:EdXposed框架终极指南:深入解析YAHFA与SandHook核心架构
下一篇:10个Options Framework Theme常见问题终极解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询