第五节 基于设备树获取内存信息

下面就可以基于 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 的各地址范围。