设备树
在前一启动阶段跳转OpenSBI时,可以将设备树的地址通过参数a1
传递过来。
OpenSBI相关的配置(opensbi_config)也可以添加到设备树节点中,OpenSBI执行时会解析和使用这些配置,并在启动结束时删除该节点,节点的示例如下:
chosen {
opensbi-config {
compatible = "opensbi,config";
cold-boot-harts = <&cpu1 &cpu2 &cpu3 &cpu4>;
system-suspend-test;
};
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
timebase-frequency = <10000000>;
cpu0: cpu@0 {
device_type = "cpu";
reg = <0x00>;
compatible = "riscv";
...
};
cpu1: cpu@1 {
device_type = "cpu";
reg = <0x01>;
compatible = "riscv";
...
};
cpu2: cpu@2 {
device_type = "cpu";
reg = <0x02>;
compatible = "riscv";
...
};
cpu3: cpu@3 {
device_type = "cpu";
reg = <0x03>;
compatible = "riscv";
...
};
cpu4: cpu@4 {
device_type = "cpu";
reg = <0x04>;
compatible = "riscv";
...
};
};
uart1: serial@10011000 {
...
};
其中关于OpenSBI配置节点的属性如下:
- compatible:强制,值须为**“opensbi,config”**
- cold-boot-harts:可选,表示哪些HART支持冷启动
- compatible:可选,如果存在,系统将等待5秒,再进入WFI
设备树导出
以QEMU启动传递给OpenSBI的设备树为例。
方法一
在启动QEMU时,使用 -M virt,dumpdtb=<file.dtb>
参数将设备树以二进制格式.dtb
导出到指定文件。
$ qemu-system-riscv64 -M virt,dumpdtb=qemu-virt.dtb -m 256M -nographic \
-bios build/platform/generic/firmware/fw_jump.bin
方法二
当执行到fw_platform_init
时,将arg1
参数地址处的内容导出。
unsigned long fw_platform_init(unsigned long arg0, unsigned long arg1,
unsigned long arg2, unsigned long arg3,
unsigned long arg4)
{
const char *model;
void *fdt = (void *)arg1;
u32 hartid, hart_count = 0;
int rc, root_offset, cpus_offset, cpu_offset, len;
root_offset = fdt_path_offset(fdt, "/");
......
}
使用GDB的dump
命令,假设arg1
的地址为0x80060000
:
(gdb) dump memory qemu-virt.dtb 0x80060000 0x80064000
导出后的dtb
文件是二进制格式,使用dtc
工具将导出的dtb
文件转换为易读的dts
格式。
$ dtc -I dtb -O dts -o qemu-virt.dts qemu-virt.dtb
设备树分析
查看qemu-virt.dts
设备树的内容如下(有删减):
/dts-v1/;
/ {
#address-cells = <0x02>;
#size-cells = <0x02>;
compatible = "riscv-virtio";
model = "riscv-virtio,qemu";
chosen {
bootargs = [00];
stdout-path = "/uart@10000000";
};
uart@10000000 {
interrupts = <0x0a>;
interrupt-parent = <0x03>;
clock-frequency = <0x384000>;
reg = <0x00 0x10000000 0x00 0x100>;
compatible = "ns16550a";
};
test@100000 {
reg = <0x00 0x100000 0x00 0x1000>;
compatible = "sifive,test1\0sifive,test0";
};
cpus {
#address-cells = <0x01>;
#size-cells = <0x00>;
timebase-frequency = <0x989680>;
cpu-map {
cluster0 {
core0 {
cpu = <0x01>;
};
};
};
cpu@0 {
phandle = <0x01>;
device_type = "cpu";
reg = <0x00>;
status = "okay";
compatible = "riscv";
riscv,isa = "rv64imafdcsu";
mmu-type = "riscv,sv48";
interrupt-controller {
#interrupt-cells = <0x01>;
interrupt-controller;
compatible = "riscv,cpu-intc";
phandle = <0x02>;
};
};
};
memory@80000000 {
device_type = "memory";
reg = <0x00 0x80000000 0x00 0x10000000>;
};
soc {
#address-cells = <0x02>;
#size-cells = <0x02>;
compatible = "simple-bus";
ranges;
interrupt-controller@c000000 {
phandle = <0x03>;
riscv,ndev = <0x35>;
reg = <0x00 0xc000000 0x00 0x4000000>;
interrupts-extended = <0x02 0x0b 0x02 0x09>;
interrupt-controller;
compatible = "riscv,plic0";
#interrupt-cells = <0x01>;
#address-cells = <0x00>;
};
clint@2000000 {
interrupts-extended = <0x02 0x03 0x02 0x07>;
reg = <0x00 0x2000000 0x00 0x10000>;
compatible = "riscv,clint0";
};
};
};
从上面内容可以得出:
- 平台设备为
riscv-virtio,qemu
- UART兼容
ns16550a
设备,基地址为0x10000000
,这个串口也是OpenSBI的打印终端 - CLINT本地中断控制器兼容
riscv,clint0
,基地址为0x02000000
- PLIC中断控制器兼容
riscv,plic0
,基地址为0x0c000000
- 内存空间基地址为
0x80000000
,其也是OpenSBI固件的链接地址 - HART个数为1
上面这些信息其实来自QEMU Virt定义的地址空间布局:
static const MemMapEntry virt_memmap[] = {
[VIRT_DEBUG] = { 0x0, 0x100 },
[VIRT_MROM] = { 0x1000, 0xf000 },
[VIRT_TEST] = { 0x100000, 0x1000 },
[VIRT_RTC] = { 0x101000, 0x1000 },
[VIRT_CLINT] = { 0x2000000, 0x10000 },
[VIRT_ACLINT_SSWI] = { 0x2F00000, 0x4000 },
[VIRT_PCIE_PIO] = { 0x3000000, 0x10000 },
[VIRT_PLATFORM_BUS] = { 0x4000000, 0x2000000 },
[VIRT_PLIC] = { 0xc000000, VIRT_PLIC_SIZE(VIRT_CPUS_MAX * 2) },
[VIRT_APLIC_M] = { 0xc000000, APLIC_SIZE(VIRT_CPUS_MAX) },
[VIRT_APLIC_S] = { 0xd000000, APLIC_SIZE(VIRT_CPUS_MAX) },
[VIRT_UART0] = { 0x10000000, 0x100 },
[VIRT_VIRTIO] = { 0x10001000, 0x1000 },
[VIRT_FW_CFG] = { 0x10100000, 0x18 },
[VIRT_FLASH] = { 0x20000000, 0x4000000 },
[VIRT_IMSIC_M] = { 0x24000000, VIRT_IMSIC_MAX_SIZE },
[VIRT_IMSIC_S] = { 0x28000000, VIRT_IMSIC_MAX_SIZE },
[VIRT_PCIE_ECAM] = { 0x30000000, 0x10000000 },
[VIRT_PCIE_MMIO] = { 0x40000000, 0x40000000 },
[VIRT_DRAM] = { 0x80000000, 0x0 },
};
代码
OpenSBI在启动时会解析设备树,代码如下:
/*
* The fw_platform_init() function is called very early on the boot HART
* OpenSBI reference firmwares so that platform specific code get chance
* to update "platform" instance before it is used.
*
* The arguments passed to fw_platform_init() function are boot time state
* of A0 to A4 register. The "arg0" will be boot HART id and "arg1" will
* be address of FDT passed by previous booting stage.
*
* The return value of fw_platform_init() function is the FDT location. If
* FDT is unchanged (or FDT is modified in-place) then fw_platform_init()
* can always return the original FDT location (i.e. 'arg1') unmodified.
*/
unsigned long fw_platform_init(unsigned long arg0, unsigned long arg1,
unsigned long arg2, unsigned long arg3,
unsigned long arg4)
{
const char *model;
void *fdt = (void *)arg1;
u32 hartid, hart_count = 0;
int rc, root_offset, cpus_offset, cpu_offset, len;
root_offset = fdt_path_offset(fdt, "/");
if (root_offset < 0)
goto fail;
fw_platform_lookup_special(fdt, root_offset);
if (generic_plat && generic_plat->fw_init)
generic_plat->fw_init(fdt, generic_plat_match);
model = fdt_getprop(fdt, root_offset, "model", &len);
if (model)
sbi_strncpy(platform.name, model, sizeof(platform.name) - 1);
if (generic_plat && generic_plat->features)
platform.features = generic_plat->features(generic_plat_match);
cpus_offset = fdt_path_offset(fdt, "/cpus");
if (cpus_offset < 0)
goto fail;
fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) {
rc = fdt_parse_hart_id(fdt, cpu_offset, &hartid);
if (rc)
continue;
if (SBI_HARTMASK_MAX_BITS <= hartid)
continue;
if (!fdt_node_is_enabled(fdt, cpu_offset))
continue;
generic_hart_index2id[hart_count++] = hartid;
}
platform.hart_count = hart_count;
platform.heap_size = fw_platform_calculate_heap_size(hart_count);
platform_has_mlevel_imsic = fdt_check_imsic_mlevel(fdt);
fw_platform_coldboot_harts_init(fdt);
/* Return original FDT pointer */
return arg1;
fail:
while (1)
wfi();
}
上面代码先解析了平台名、HART个数等,最后调用fw_platform_coldboot_harts_init
解析出冷启动的HART ID,如下:
/*
* The fw_platform_coldboot_harts_init() function is called by fw_platform_init()
* function to initialize the cold boot harts allowed by the generic platform
* according to the DT property "cold-boot-harts" in "/chosen/opensbi-config"
* DT node. If there is no "cold-boot-harts" in DT, all harts will be allowed.
*/
static void fw_platform_coldboot_harts_init(void *fdt)
{
int chosen_offset, config_offset, cpu_offset, len, err;
u32 val32;
const u32 *val;
bitmap_zero(generic_coldboot_harts, SBI_HARTMASK_MAX_BITS);
chosen_offset = fdt_path_offset(fdt, "/chosen");
if (chosen_offset < 0)
goto default_config;
config_offset = fdt_node_offset_by_compatible(fdt, chosen_offset, "opensbi,config");
if (config_offset < 0)
goto default_config;
val = fdt_getprop(fdt, config_offset, "cold-boot-harts", &len);
len = len / sizeof(u32);
if (val && len) {
for (int i = 0; i < len; i++) {
cpu_offset = fdt_node_offset_by_phandle(fdt,
fdt32_to_cpu(val[i]));
if (cpu_offset < 0)
goto default_config;
err = fdt_parse_hart_id(fdt, cpu_offset, &val32);
if (err)
goto default_config;
if (!fdt_node_is_enabled(fdt, cpu_offset))
continue;
for (int i = 0; i < platform.hart_count; i++) {
if (val32 == generic_hart_index2id[i])
bitmap_set(generic_coldboot_harts, i, 1);
}
}
}
return;
default_config:
bitmap_fill(generic_coldboot_harts, SBI_HARTMASK_MAX_BITS);
return;
}
由于上面的qemu-virt.dts
设备树并未包括OpenSBI节点,因此所有HARTs均允许冷启动。
再结合OpenSBI打印信息看下,其确实是解析了设备数,如Platform Name
、Platform HART Count
和Domain使用的地址信息等:
OpenSBI v1.5
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|
Platform Name : riscv-virtio,qemu
Platform Features : medeleg
Platform HART Count : 1
Platform IPI Device : aclint-mswi
Platform Timer Device : aclint-mtimer @ 10000000Hz
Platform Console Device : uart8250
Platform HSM Device : ---
Platform PMU Device : ---
Platform Reboot Device : ---
Platform Shutdown Device : ---
Platform Suspend Device : ---
Platform CPPC Device : ---
Firmware Base : 0x80000000
Firmware Size : 327 KB
Firmware RW Offset : 0x40000
Firmware RW Size : 71 KB
Firmware Heap Offset : 0x49000
Firmware Heap Size : 35 KB (total), 2 KB (reserved), 9 KB (used), 23 KB (free)
Firmware Scratch Size : 4096 B (total), 408 B (used), 3688 B (free)
Runtime SBI Version : 2.0
Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region01 : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region02 : 0x0000000080040000-0x000000008005ffff M: (R,W) S/U: ()
Domain0 Region03 : 0x0000000080000000-0x000000008003ffff M: (R,X) S/U: ()
Domain0 Region04 : 0x000000000c000000-0x000000000fffffff M: (I,R,W) S/U: (R,W)
Domain0 Region05 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)