第五节 基于设备树获取内存信息
下面就可以基于 axdtb 组件来解析硬件平台的设备树信息,我们首先要知道的是物理内存的上限和各个 mmio 范围。基于这两个信息,在下一章中,我们的内核将重建地址空间并建立正式的内存动态分配机制。
首先来实现解析函数 parse_dtb
,从设备树中解析物理内存上限和各个 mmio 范围。
// axruntime/src/lib.rs
extern crate alloc;
use core::str;
use alloc::string::String;
use alloc::vec::Vec;
use axdtb::SliceRead;
use axconfig::phys_to_virt;
struct DtbInfo {
memory_addr: usize,
memory_size: usize,
mmio_regions: Vec<(usize, usize)>,
}
fn parse_dtb(dtb_pa: usize) -> axdtb::DeviceTreeResult<DtbInfo> {
let dtb_va = phys_to_virt(dtb_pa);
let mut memory_addr = 0;
let mut memory_size = 0;
let mut mmio_regions = Vec::new();
let mut cb = |_name: String, addr_cells: usize, size_cells: usize, props: Vec<(String, Vec<u8>)>| {
let mut is_memory = false;
let mut is_mmio = false;
let mut reg = None;
for prop in props {
match prop.0.as_str() {
"device_type" => {
is_memory = str::from_utf8(&(prop.1))
.map_or_else(|_| false, |v| v == "memory\0");
},
"compatible" => {
is_mmio = str::from_utf8(&(prop.1))
.map_or_else(|_| false, |v| v == "virtio,mmio\0");
},
"reg" => {
reg = Some(prop.1);
},
_ => (),
}
}
if is_memory {
assert!(addr_cells == 2);
assert!(size_cells == 2);
if let Some(ref reg) = reg {
memory_addr = reg.as_slice().read_be_u64(0).unwrap() as usize;
memory_size = reg.as_slice().read_be_u64(8).unwrap() as usize;
}
}
if is_mmio {
assert!(addr_cells == 2);
assert!(size_cells == 2);
if let Some(ref reg) = reg {
let addr = reg.as_slice().read_be_u64(0).unwrap() as usize;
let size = reg.as_slice().read_be_u64(8).unwrap() as usize;
mmio_regions.push((addr, size));
}
}
};
let dt = axdtb::DeviceTree::init(dtb_va.into())?;
dt.parse(dt.off_struct, 0, 0, &mut cb)?;
Ok(DtbInfo {memory_addr, memory_size, mmio_regions})
}
第 10~14 行:定义返回解释结果的结构体,包括物理内存的开始地址和长度以及 virtio_mmio 地址范围列表。
第 27~42 行:设备树中,物理内存节点的 device_type 是 "memory";而 virtio_mmio 的 compatible 是 "virtio,mmio"。过滤出这两种节点,并取出它们的地址范围信息,此类信息记录在 reg 属性中。
第 43~50 行:读出物理内存的地址范围信息。
第 51~59 行:读出 VirtIO_MMIO 的地址范围信息。
第 62~63 行:从设备树的 off_struct 指向的位置开始解析。
在 axruntime 中调用 axdtb 解析平台信息,位置就在初始化日志组件 axlog 之后:
// axruntime/src/lib.rs
#[no_mangle]
pub extern "C" fn rust_main(hartid: usize, dtb: usize) -> ! {
... ...
axlog::init();
axlog::set_max_level(option_env!("LOG").unwrap_or("")); // no effect if set `log-level-*` features
info!("Logging is enabled.");
info!("Primary CPU {} started, dtb = {:#x}.", hartid, dtb);
// Parse fdt for early memory info
let dtb_info = match parse_dtb(dtb.into()) {
Ok(info) => info,
Err(err) => panic!("Bad dtb {:?}", err),
};
info!("Memory: {:#x}, size: {:#x}", dtb_info.memory_addr, dtb_info.memory_size);
info!("Virtio_mmio[{}]:", dtb_info.mmio_regions.len());
for r in &dtb_info.mmio_regions {
info!("\t{:#x}, size: {:#x}", r.0, r.1);
}
... ...
}
// axruntime/Cargo.toml
[dependencies]
axdtb = { path = "../axdtb" }
axconfig = { path = "../axconfig" }
第 11~15 行:调用上面的 parse_dtb
开始扫描整个设备树,期间会触发我们注册的回调闭包。每发现一个节点,cb 就会被调用一次,从中过滤出我们需要的两组信息:一是物理内存的开始地址和总的大小,二是 virtio_mmio 范围列表。
第 17~21 行:展示从设备树中解析获得的物理内存的开始地址和长度以及 virtio_mmio 地址范围列表。
上面过程中需要调用 phys_to_virt 把 dtb 块的物理地址转换为虚拟地址,所以最后扩展一下 axconfig 的实现,增加这个地址转换函数和它对应的反向转换函数。
#[inline]
pub const fn phys_to_virt(pa: usize) -> usize {
pa.wrapping_add(PHYS_VIRT_OFFSET)
}
#[inline]
pub const fn virt_to_phys(va: usize) -> usize {
va.wrapping_sub(PHYS_VIRT_OFFSET)
}
验证一下我们增加的新功能,执行 make run LOG=info
。
ArceOS is starting ... [ 0.099759 axruntime:31] Logging is enabled. [ 0.107211 axruntime:32] Primary CPU 0 started, dtb = 0x87000000. [ 0.120634 axruntime:40] Memory: 0x80000000, size: 0x8000000 [ 0.121983 axruntime:41] Virtio_mmio[8]: [ 0.123077 axruntime:43] 0x10008000, size: 0x1000 [ 0.124260 axruntime:43] 0x10007000, size: 0x1000 [ 0.125612 axruntime:43] 0x10006000, size: 0x1000 [ 0.126650 axruntime:43] 0x10005000, size: 0x1000 [ 0.128103 axruntime:43] 0x10004000, size: 0x1000 [ 0.130050 axruntime:43] 0x10003000, size: 0x1000 [ 0.132008 axruntime:43] 0x10002000, size: 0x1000 [ 0.134621 axruntime:43] 0x10001000, size: 0x1000
Now: 0.135674 Hello, ArceOS![from String] Elapsed: 0.001336 [ 0.138626 axhal::riscv64::lang_items:6] panicked at axruntime/src/lib.rs:47:5: ArceOS exit ...
验证成功!可以看到取得了物理内存的开始地址及长度,还有 virtio_mmio 的各地址范围。