ax-plat 技术文档
路径:
components/axplat_crates/axplat类型:库 crate 分层:组件层 / 平台抽象 版本:0.3.1-pre.6文档依据:当前仓库源码、Cargo.toml与components/axplat_crates/axplat/README.md
ax-plat 不是某个具体板级平台的驱动集合,而是 ArceOS 平台层对上层内核暴露的统一契约包。它把“平台初始化、物理内存描述、控制台、时间、中断、电源、每核上下文”等能力拆成稳定接口;具体 ax-plat-* 平台包通过 ax-crate-interface 机制填充实现,内核只面向 ax-plat 编程,而不直接依赖某个 UART、GIC、APIC 或 PSCI/SBI 实现。
1. 架构设计分析
1.1 设计目标
ax-plat 的核心目标是把“平台相关代码”从“内核主体逻辑”中剥离出来,并提供一组足够小、但覆盖启动期关键路径的接口:
- 启动期:把平台引导代码与内核入口解耦,统一收敛到
ax_plat::call_main()与#[ax_plat::main]。 - 运行期:让时钟、串口、中断、电源管理等能力通过统一 trait 访问。
- 构建期:让同一套内核代码可以通过切换
ax-plat-*平台包复用到 QEMU、物理板卡和教学实验平台。 - 演进期:通过 trait 边界保持上层稳定,使新增平台主要表现为“实现接口”而非“修改内核”。
这意味着 ax-plat 本身几乎不直接操作硬件寄存器;它真正提供的是“稳定调用面”和“平台实现接入框架”。
1.2 模块划分
| 模块 | 作用 | 关键内容 |
|---|---|---|
lib.rs | crate 根与导出层 | main / secondary_main 宏导出、call_main()、call_secondary_main()、impl_plat_interface、assert_str_eq! |
init | 平台初始化契约 | InitIf,约束早期/后期、主核/次核初始化顺序 |
console | 统一控制台接口 | ConsoleIf、console_print!、CONSOLE_LOCK |
time | 时间与定时器接口 | TimeIf、monotonic_time()、wall_time()、busy_wait() |
mem | 物理内存描述与辅助算法 | MemIf、MemRegionFlags、PhysMemRegion、区间差集与重叠检查 |
power | 电源与 CPU 启停接口 | PowerIf,涵盖 system_off()、cpu_boot()、cpu_num() |
irq | 中断控制抽象 | IrqIf、IrqHandler、IpiTarget、HandlerTable |
ax-percpu | 每核辅助状态 | CPU_ID、IS_BSP 以及主核/次核 ax-percpu 初始化 |
这种划分有两个明显特点:
ax-plat把真正“必须跨平台统一”的部分收敛到几个小 trait,不试图抽象完整驱动栈。ax-percpu、mem中包含少量公共算法与状态容器,因此它不只是接口定义包,也承担平台层公共工具箱的职责。
1.3 关键数据结构与接口契约
InitIf
InitIf 是平台 bring-up 的第一条主线,明确区分:
init_early(cpu_id, arg):早期初始化,要求在异常向量、早期串口、时间源等最小环境建立后返回。init_later(cpu_id, arg):后期初始化,留给中断控制器、定时器使能、其它必要外设。init_early_secondary()/init_later_secondary():在smp打开时补充次核路径。
这让平台包与内核对初始化阶段的理解保持一致,避免把所有初始化逻辑塞进一个不可分解的入口函数。
MemIf
MemIf 决定了上层如何理解平台物理内存布局:
phys_ram_ranges()提供可用 RAM 区间。reserved_phys_ram_ranges()提供需要保留但仍需映射的区间。mmio_ranges()提供设备寄存器窗口。phys_to_virt()/virt_to_phys()建立线性映射语义。kernel_aspace()提供内核地址空间基址与大小。
配套的数据结构包括:
MemRegionFlags:读/写/执行、设备、不可缓存、保留、可分配等属性位。PhysMemRegion:带名字、标志和地址区间的物理内存描述。Aligned4K<T>:页对齐包装器,常被具体平台包用于页表、引导栈等静态对象。
check_sorted_ranges_overlap() 与 ranges_difference() 是 mem 模块最有价值的公共算法:它们让平台包和上层内存在构造区间表时能显式验证“不重叠”和“扣除保留区”的前提。
IrqIf
IrqIf 不仅抽象“开关中断”,还把中断分发策略收敛为统一入口:
set_enable():平台实现负责映射到 GIC/APIC/PLIC 等控制器。register()/unregister():约束注册成功时自动开中断、撤销时自动关中断。handle():由公共中断入口调用,平台实现负责找出真实 IRQ 并完成 ACK/EOI。send_ipi():统一 IPI 发送语义。
IpiTarget 明确了 IPI 目标选择:当前核、单核或除当前核外全部 CPU。
ConsoleIf、TimeIf、PowerIf、ax-percpu
ConsoleIf用write_bytes()/read_bytes()提供最小串口抽象,必要时通过irq_num()暴露输入中断号。TimeIf同时保留“硬件 tick”与“纳秒时间”两个视角,并额外定义epochoffset_nanos(),把单调时钟与墙钟语义拼接起来。PowerIf将系统关机、CPU 启动和 CPU 数量查询放进统一接口,覆盖 PSCI/SBI/APIC SIPI 等不同平台实现。ax-percpu模块用ax_percpu::def_percpu定义CPU_ID和IS_BSP,为平台初始化和上层调度器提供一致的当前核信息。
1.4 核心机制:接口定义、宏导出与动态分发
ax-plat 的关键机制不是 Rust trait 对象,而是 ax-crate-interface 生成的“接口调用面”:
- 各模块用
#[def_plat_interface]定义平台 trait。 - 具体平台包用
#[impl_plat_interface]为本地零大小类型实现该 trait。 - 上层调用
ax_plat::init::init_early()、ax_plat::time::current_ticks()这类函数时,实际会被ax-crate-interface路由到具体平台实现。
这套设计的优势是:
- 上层调用接口时不需要持有具体平台对象。
- 不引入堆分配、trait object 或运行时注册表。
- 平台包可以在链接期静态接入,符合
no_std启动期约束。
lib.rs 中的 call_main() / call_secondary_main() 则把 boot stub 与内核主函数连接起来。平台包在完成最低限度的汇编或 Rust 引导后,跳入这两个函数;真正的内核主函数通过 #[ax_plat::main] 或 #[ax_plat::secondary_main] 标注,由 ax-plat-macros 生成固定符号导出。
1.5 辅助设计与边界
assert_str_eq! 是 ax-plat 很容易被忽视但非常关键的编译期防错机制。平台包常用它校验 axconfig 中的 PACKAGE 字符串必须等于 crate 名,从而避免“配置文件写错,但仍勉强编译通过”的错误组合。
同时,ax-plat 也明确保持边界克制:
- 不定义完整设备驱动模型。
- 不承担内核页表策略、调度器、线程管理等高层职责。
- 不规定日志系统、文件系统、网络栈如何实现。
它只保证“平台 bring-up 与基本硬件能力”以一致方式向上暴露。
2. 核心功能说明
2.1 主要功能
- 统一导出内核入口装饰器:
#[ax_plat::main]与#[ax_plat::secondary_main]。 - 提供平台初始化阶段划分:早期、后期、主核、次核。
- 提供跨平台控制台输出和最小输入能力。
- 提供单调时间、墙钟、忙等与定时器接口。
- 提供 RAM/MMIO/保留区查询及地址转换接口。
- 提供 IRQ 注册、撤销、分发和 IPI 发送接口。
- 提供系统关机、CPU 启动和 CPU 数量查询接口。
- 提供每核 ID 与 BSP 标识辅助状态。
2.2 典型 API 使用方式
对于内核作者,ax-plat 的使用方式是“只写统一接口,不碰具体平台类型”:
#[ax_plat::main]
fn kernel_main(cpu_id: usize, arg: usize) -> ! {
ax_plat::percpu::init_primary(cpu_id);
ax_plat::init::init_early(cpu_id, arg);
ax_plat::console_println!("hello from cpu {}", cpu_id);
ax_plat::init::init_later(cpu_id, arg);
let now = ax_plat::time::monotonic_time_nanos();
let ram = ax_plat::mem::total_ram_size();
ax_plat::console_println!("time={}ns ram={} bytes", now, ram);
ax_plat::power::system_off();
}
对于平台实现者,关键是提供各 trait 的实现,而不是改动内核:
use ax_plat::impl_plat_interface;
struct InitIfImpl;
#[impl_plat_interface]
impl ax_plat::init::InitIf for InitIfImpl {
fn init_early(cpu_id: usize, arg: usize) { /* ... */ }
fn init_later(cpu_id: usize, arg: usize) { /* ... */ }
}
2.3 使用场景
- ArceOS 在不同架构与板级平台之间复用同一套上层模块。
ax-hal需要一个稳定的平台调用面,而不希望把平台判断散落到各模块。- 示例内核和实验内核希望用最少样板代码完成 bring-up。
- 第三方平台包希望以“新增 crate”的方式接入,而不是修改核心内核仓库。