axplat-dyn 技术文档
路径:
platform/axplat-dyn类型:库 crate 分层:平台层 / 动态平台桥接层 版本:0.3.0-preview.3文档依据:当前仓库源码、Cargo.toml、build.rs、link.ld及os/arceos/modules/axhal/ax-driver的接入路径
axplat-dyn 不是 cargo-axplat 生成的那类“用 axconfig.toml 固化板级常量”的常规 axplat-* 平台包。它更像一层桥接适配器:把 somehal 已经建立好的启动入口、FDT 地址、内存图、时钟、IRQ、电源与 SMP 元数据转译成 axplat 的统一契约;同时再补上一条 ax-driver 动态设备模型所需的设备探测与 DMA glue。这里的 dyn 真正表示“平台事实来自运行时抽象层和探测结果”,而不是“把平台包当作运行时可装卸模块加载”。
1. 架构设计分析
1.1 设计定位
axplat-dyn 在当前仓库里的位置可以概括为:
- 向下:依赖
somehal提供的入口宏、内存映射、控制台、时钟、中断、电源和 CPU 元数据。 - 向上:实现
InitIf、ConsoleIf、MemIf、TimeIf、PowerIf以及可选IrqIf,并通过ax_plat::call_main()/call_secondary_main()把控制权交给内核入口。 - 向旁:公开
drivers模块,为ax-driver的dyn设备模型提供基于rdrive的设备探测与块设备封装。 - 向链接层:通过
build.rs生成axplat.x,把linkme段、异常表和若干 ArceOS 约定符号补进最终镜像。
这决定了它与普通板级包的一个根本差异:
- 普通
axplat-*平台包主要把编译期axconfig.toml变成板级常量,再围绕这些常量实现axplat接口。 axplat-dyn则把somehal暴露的运行时事实直接转成axplat接口,不以axconfig.toml为主线。
1.2 模块划分
| 模块 | 作用 | 关键内容 |
|---|---|---|
lib.rs | crate 根与装配层 | 裸机目标限定、模块导入、无 irq 时的空中断入口 |
boot | 启动 glue | #[somehal::entry(Kernel)]、#[somehal::secondary_entry]、Kernel 的 MmioOp 实现 |
init | InitIf 实现 | trap 初始化、计时器打开、post_paging()、后期 IRQ 打开 |
console | ConsoleIf 实现 | 控制台读写、\n 到 \r\n 的串口兼容转换 |
mem | MemIf 实现 | 从 somehal::mem::memory_map() 生成 RAM/保留区/MMIO 视图,导出 _percpu_base_ptr |
generic_timer | TimeIf 实现 | tick/nanos 转换、定时器 IRQ 编号、one-shot 定时器 |
irq | IrqIf 实现 | HandlerTable<1024>、启停 IRQ、注册/撤销、公共分发入口 |
power | PowerIf 实现 | cpu_boot()、system_off()、cpu_num() |
drivers | 动态设备探测 glue | probe_all_devices()、动态块设备注册表、DMA 适配、PCIe/VirtIO block 探测 |
1.3 平台实现装配方式
axplat-dyn 的实现不是单点完成的,而是由四层 glue 组合出来:
| 装配层 | 依赖来源 | 本 crate 的落点 | 作用 |
|---|---|---|---|
| 启动 glue | somehal | boot.rs | 把主核/次核入口收敛到 ax_plat::call_main() / call_secondary_main() |
| 平台契约 glue | axplat | init.rs、console.rs、mem.rs、generic_timer.rs、irq.rs、power.rs | 用 #[impl_plat_interface] 把 somehal 能力接到 axplat trait |
| 设备 glue | rdrive、rd-block、ax-driver-virtio | drivers/* | 把 FDT/PCIe/VirtIO 探测结果转成 ax-driver 可消费的动态块设备 |
| 链接 glue | build.rs、link.ld | crate 根目录 | 生成 axplat.x,插入所需段和符号,适配 ArceOS 链接约定 |
这套装配方式说明它承担的是“桥接与接线”职责,而不是“重新定义平台抽象”。
1.4 启动与初始化主线
boot.rs 给出的启动链很短,但责任很明确:
#[somehal::entry(Kernel)]标记的main()作为主核入口。- 入口尝试从
somehal::fdt_addr_phys()取得 FDT 物理地址,作为arg传给内核。 - 主核统一跳到
ax_plat::call_main(0, arg)。 - 若启用
smp,#[somehal::secondary_entry]标记的次核入口会继续调用ax_plat::call_secondary_main(cpu_id)。
init.rs 则把 somehal 已经具备的基础能力重新编排成 axplat 生命周期:
init_early()/init_early_secondary():初始化 trap,并打开基础计时器。init_later():在分页建立更完整后调用somehal::post_paging(),随后使能定时器 IRQ。init_later_secondary():次核路径上只补做定时器 IRQ 使能。
这里有一个重要前提:axplat-dyn 默认假设“更早的架构级 bring-up 已经由 somehal 完成”,它不自己构造早期页表,也不自己处理最底层的 CPU 模式切换。
1.5 内存、时间、中断与电源 glue
内存视图
mem.rs 直接遍历 somehal::mem::memory_map(),按 MemoryType 过滤出三类区间:
Free->phys_ram_ranges()Reserved/KImage/PerCpuData->reserved_phys_ram_ranges()Mmio->mmio_ranges()
这些结果被缓存到 spin::Once<Vec<...>> 中,说明该 crate 采用“首次查询时冻结运行时内存图”的策略,而不是每次重新探测。
地址转换和地址空间范围也完全委托给 somehal:
phys_to_virt()/virt_to_phys()直接转发到somehal::memkernel_aspace()来自somehal::mem::kernel_space()
此外,_percpu_base_ptr() 通过 somehal::smp::percpu_data_ptr() 向 ax-percpu crate 提供每核数据基址。这也解释了为什么 ax-hal::mem 在 plat-dyn 模式下不再额外注入一套传统平台包的内核保留区逻辑:此路径默认信任 somehal 给出的内存事实已经包含 KImage 和 PerCpuData。
时间、中断与电源
generic_timer.rs 把 somehal::timer 接成 ax_plat::time::TimeIf:
current_ticks()/ticks_to_nanos()/nanos_to_ticks():直接围绕ticks()和freq()换算。irq_num():取somehal::irq::systick_irq()。set_oneshot_timer():把绝对纳秒截止时间换成剩余 tick,再调用set_next_event_in_ticks()。epochoffset_nanos():当前固定返回0,说明这里只提供单调计时语义,不额外注入墙钟偏移。
irq.rs 把 HandlerTable<1024> 和 somehal 的中断入口拼在一起:
register()/unregister()负责维护处理函数表并同步开关 IRQ。handle()只负责报告当前正在处理的原始 IRQ 编号。- 真正的公共中断入口是
#[somehal::irq_handler] fn somehal_handle_irq(...),它再调用IRQ_HANDLER_TABLE.handle(...)进行分发。
也就是说,这里的 IrqIf 只接上了“注册表 + 分发桥”,底层 ACK/EOI 语义仍然由 somehal 负责。
power.rs 则相对直接:
cpu_boot()调用somehal::power::cpu_on(cpu_id)拉起次核。system_off()调用somehal::power::shutdown()。cpu_num()通过somehal::smp::cpu_meta_list()统计 CPU 数。
这里还暴露出一个细节:cpu_boot() 当前忽略了 stack_top_paddr 参数,说明次核启动栈安排不是由 axplat-dyn 独立决定,而是纳入了 somehal 的启动协议。
1.6 动态设备探测路径
drivers 模块是 axplat-dyn 与普通平台包最不一样的部分之一。它不是简单地“列出 MMIO 区间”,而是主动承担一部分动态设备发现职责:
probe_all_devices()先清空本地块设备注册表。- 调用
rdrive::probe_all(true)触发探测。 drivers/pci.rs通过module_driver!注册通用 PCIe ECAM 控制器探测器。drivers/blk/virtio.rs通过module_driver!注册virtio,mmioblock 设备探测器。- 探测出的
rd_block::Block被包成实现ax_driver_block::BlockDriverOps的Block,最终进入BLOCK_DEVICES。 os/arceos/modules/axdriver/src/dyn_drivers/mod.rs再通过take_block_devices()把它们取走,转成ax-driver的动态设备集合。
这一层的关键价值在于:
axplat-dyn不只是“平台初始化 glue”,还是ax-driver动态设备模型的探测前端。- 当前有效覆盖面主要是块设备;网络、显示等类别并没有在本 crate 中提供同等级的动态探测路径。
VirtIOblock 路径里的enable_irq()/disable_irq()仍是todo!(),说明它更偏向当前可用的基础探测和阻塞 I/O 路径,而非完整中断驱动栈。
1.7 与 axplat、ax-plat-macros 和工具链的边界
axplat-dyn 的边界必须明确区分:
- 与
axplat的边界:axplat定义的是稳定平台契约和入口调用面;axplat-dyn只是其中一个实现者,并不改变接口定义。 - 与
ax-plat-macros的边界:本 crate 不直接依赖ax-plat-macros,只通过axplat重新导出的#[impl_plat_interface]和入口宏参与体系。 - 与
somehal的边界:真正的“平台事实来源”在somehal,包括入口、内存图、时钟、IRQ、电源与 CPU 元数据;axplat-dyn负责转译,而不是重新探测 CPU 模式或自己管理整套启动环境。 - 与
cargo-axplat/ax-config-gen的边界:当前源码中保留了一段被注释掉的config模块草稿,但现行实现并没有启用axconfig.toml -> AX_CONFIG_PATH -> include_configs!这条常规平台包主线,因此它不属于典型axplat-*配置化平台生态。
2. 核心功能说明
2.1 主要能力
- 作为
somehal到axplat的桥接层,提供统一的启动、内存、时间、中断和电源接口实现。 - 通过
build.rs + link.ld生成适配当前内核镜像的axplat.x链接脚本扩展。 - 让
ax-hal可通过plat-dynfeature 接入这一动态平台路径。 - 让
ax-driver可通过dynfeature 复用其设备探测与动态块设备封装。 - 通过
hv、uspace、smp、irqfeature 把能力向somehal和axplat两侧传播。
2.2 feature 行为
| Feature | 作用 |
|---|---|
smp | 透传到 ax-plat/smp,启用次核入口、次核初始化和 cpu_boot() 路径 |
irq | 透传到 ax-plat/irq,编译 irq.rs 并启用 timer IRQ 相关接口 |
uspace | 透传到 somehal/uspace,说明该路径允许 somehal 切换到含用户态支持的构建 |
hv | 透传到 somehal/hv 与 ax-cpu/arm-el2,为 hypervisor 场景准备 CPU 模式支持 |
需要注意,默认 feature 就是 ["smp", "irq"],这意味着该 crate 被设计成优先服务多核且可中断的平台路径,而不是最小单核裸机包。
2.3 典型使用场景
- 需要把
somehal管理的运行时平台事实快速挂接到 ArceOSaxplat/ax-hal栈。 - 需要配合
ax-driver的dyn模型,在运行时探测并收集多实例块设备。 - 需要实验性地复用一套更“运行时驱动”的平台 bring-up 路径,而不是重新写一个静态
axplat-*板级包。
3. 依赖关系图谱
3.1 直接依赖
| 依赖 | 作用 |
|---|---|
axplat | 提供平台契约与 call_main() / call_secondary_main() |
somehal | 提供真实平台事实与入口宏,是本 crate 的核心下层 |
ax-cpu | 在 hv 等场景提供 CPU 模式支持 |
axklib | 提供 iomap() 等内核内存映射辅助 |
ax-alloc | 为 VirtIO DMA 路径提供页分配 |
rdrive、rd-block | 提供运行时设备探测与块设备抽象 |
axdriver_block、ax-driver-virtio、ax-driver-base | 将探测结果转接为 ArceOS 驱动接口 |
dma-api | 为设备 DMA 提供抽象接口 |
heapless、spin | 用于固定容量缓存与锁/一次初始化结构 |
3.2 主要消费者
os/arceos/modules/axhal:通过plat-dynfeature 选择该平台路径。os/arceos/modules/axdriver:通过dynfeature 调用其动态设备探测入口。- 进一步依赖上述模块的 ArceOS 系统镜像,以及复用同一模块栈的其它内核工程。
3.3 依赖关系示意
4. 开发指南
4.1 何时应使用这条路径
适合使用 axplat-dyn 的情况是:
- 你已经有
somehal这层更底部的平台抽象,希望把它接进axplat/ax-hal。 - 你需要的是“运行时探测 + 动态设备模型”,而不是“固定板级参数 + 静态平台包”。
不适合直接套用它的情况是:
- 你要新做一个常规
axplat-*板级包,并希望走axconfig.toml配置化主线。 - 你需要一个完整的、已覆盖多类别设备的动态驱动模型。本 crate 当前主要只补齐了块设备路径。
4.2 接入主线
- 在上层内核构建中启用
ax-hal的plat-dynfeature;若需要动态设备探测,再启用ax-driver的dyn相关 feature。 - 确保目标是裸机环境,而不是
unix/windows宿主机构建路径。 - 让
somehal提供入口、FDT、内存图、控制台、时钟、中断和电源实现。 - 由
boot.rs把控制流统一转到ax_plat::call_main(),随后上层只通过axplat接口使用平台能力。 - 若需要动态块设备,在适当阶段调用
ax-driver::init_drivers(),其内部会落到axplat_dyn::drivers::probe_all_devices()。
4.3 维护注意事项
send_ipi()仍是todo!(),因此不要把它误判为一条已经完成的 SMP IPI 路径。VirtIOblock 的 IRQ enable/disable/handle 仍未完成,中断驱动块 I/O 不是当前实现重点。build.rs生成的axplat.x会把__SMP固定替换成16;若下层假设变化,需要同步检查链接脚本和启动约定。- 当前 crate 根部有
#![cfg(not(any(windows, unix)))],说明主机侧cargo test/cargo check不是它的主要验证面。 - 源码中虽保留了被注释掉的
config模块草稿,但现行代码并不实际消费AX_CONFIG_PATH或axconfig.toml。
5. 测试策略
5.1 当前有效验证面
- 裸机目标上的完整启动冒烟:确认
somehal入口能贯通到ax_plat::call_main()。 - 内存图验证:检查
somehal::mem::memory_map()过滤出的 RAM、保留区和 MMIO 是否与上层预期一致。 - 计时器与 IRQ 验证:确认
init_later()后 timer IRQ 真正可触发。 - 动态设备验证:确认
ax-driver的dyn路径能从本 crate 获取块设备。
5.2 推荐测试分层
- 启动测试:验证主核、次核入口都能正确进入
axplat入口函数。 - 契约测试:对
InitIf、MemIf、TimeIf、PowerIf的桥接语义做最小集成回归。 - 设备测试:在含
virtio,mmio或 PCIe ECAM 的环境下验证probe_all_devices()至少能发现块设备。 - 多核测试:在
smp打开时验证cpu_boot()、_percpu_base_ptr()和 CPU 计数一致性。
5.3 重点风险
- 一旦
somehal的内存图语义变化,axplat-dyn的 RAM/保留区/MMIO 划分会整体漂移。 - 该 crate 同时承担“平台契约 glue”和“设备探测 glue”两类职责,回归面比普通平台包更宽。
- 当前动态设备路径主要覆盖块设备,若上层以为
dyn模式天然涵盖所有设备类型,容易产生错误预期。