ax-plat-aarch64-qemu-virt
路径:
platforms/ax-plat-aarch64-qemu-virt类型:库 crate 分层:组件层 / AArch64 板级平台包 版本:0.3.1-pre.6文档依据:当前仓库源码、Cargo.toml、README.md、axconfig.toml及相关上层调用路径
ax-plat-aarch64-qemu-virt 是 QEMU ARM64 virt 机器在 axplat 体系下的具体板级实现。它不是通用外设驱动库,而是把启动入口、早期页表、物理内存布局、PSCI 启停、QEMU virt 设备地址表以及 ax-plat-aarch64-peripherals 提供的 PL011/GIC/Generic Timer/PL031 glue 组合成一个可以直接被内核链接的完整平台包。
架构设计
设计定位
该 crate 在 AArch64 平台栈中的位置可以概括为:
- 向下:依赖
ax-cpu、ax-page-table-entry和ax-plat-aarch64-peripherals,完成 CPU 模式切换、早期页表和通用外设接入。 - 向上:实现
InitIf、MemIf、PowerIf等axplat契约,并通过ax_plat::call_main()把控制权交给内核主函数。 - 向旁:通过
axconfig.toml固化 QEMUvirt板级的 RAM、MMIO、UART、GIC、RTC、PCI ECAM 等地址空间信息。
它与 ax-plat-aarch64-peripherals 的区别在于:
ax-plat-aarch64-peripherals只负责“通用外设怎么接进axplat”;- 本 crate 负责“QEMU
virt这块板子从哪启动、地址在哪里、次核怎么拉起、内核线性映射怎么建”。
模块结构
| 模块 | 作用 | 关键内容 |
|---|---|---|
lib.rs | 导出层与 glue 入口 | config 生成、平台包名校验、展开 console_if_impl! / time_if_impl! / irq_if_impl! |
boot | 早期引导与页表 | ARM64 Linux-style 镜像头、主核/次核入口、早期页表、MMU 打开、切到 call_main() |
init | InitIf 实现 | 主核/次核的早期与后期初始化顺序 |
mem | MemIf 实现 | RAM/MMIO 区间、线性映射、内核地址空间范围 |
power | PowerIf 实现 | cpu_boot()、system_off()、cpu_num() |
config | 平台常量 | axconfig.toml 经 ax_config_macros::include_configs! 生成的 plat / devices / mem 常量 |
1.3 关键数据结构与全局对象
引导期对象
BOOT_STACK:放在.bss.stack的主引导栈,大小来自配置。BOOT_PT_L0/BOOT_PT_L1:两个 4K 对齐页表页, 使用A64PTE构造最小可用页表。
这两个页表页承担的是“只够把 CPU 带到 Rust 世界”的职责,而不是最终内核页表:
- L0 指向一页 L1 表。
- 一段映射低地址设备区域。
- 一段映射 QEMU 默认 RAM 起始区。
配置对象
config 模块由 axconfig.toml 自动展开,至少包含以下几类信息:
PACKAGE、PHYS_MEMORY_BASE、PHYS_MEMORY_SIZEUART_PADDR、GICD_PADDR、GICC_PADDR、RTC_PADDRUART_IRQ、TIMER_IRQMMIO_RANGESPHYS_VIRT_OFFSETKERNEL_ASPACE_BASE、KERNEL_ASPACE_SIZEMAX_CPU_NUMPSCI_METHOD
lib.rs 用 assert_str_eq! 校验 PACKAGE 与 crate 名一致,防止误绑配置。
外设单例
虽然这些对象定义在 ax-plat-aarch64-peripherals 中,但在本平台运行时构成其实际外设后端:
- PL011 串口单例
- GIC 控制器与 IRQ 分发表
- Generic Timer 比率缓存
- PL031 墙钟偏移缓存
1.4 启动主线
boot.rs 是本 crate 最核心的部分之一,其启动链路如下:
其关键步骤包括:
- 入口镜像头采用 Linux arm64 boot image 风格,便于 QEMU 和常见引导流程识别。
- 主核启动时从
x0保存 DTB 指针,从MPIDR_EL1抽取逻辑 CPU ID。 - 先在物理地址视角下建立引导栈,再切到 EL1。
- 构造最小页表并打开 MMU,随后按
PHYS_VIRT_OFFSET将栈切换到高半区虚拟地址。 - 最终统一跳转到
ax_plat::call_main()。
在 smp 打开时,_start_secondary 为次核提供类似但更精简的路径:次核栈由上层传入,复用引导页表,打开 MMU 后调用 ax_plat::call_secondary_main()。
1.5 早期页表与地址空间策略
早期页表的设计故意保持极简,只为了完成从“裸机入口”到“可运行 Rust 初始化代码”的过渡:
- 低地址设备区被映射为 device memory。
- 从
0x4000_0000起的 RAM 区被映射为 normal memory。 - 页表层级只用到 L0/L1,不追求精细权限与完整内核地址空间。
这与最终内核页表的分工非常明确:
- 引导页表:保证启动。
- 上层页表管理:由后续内核内存子系统接管。