OpenSBI设备树

设备树

在前一启动阶段跳转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 NamePlatform 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)

参考

  1. opensbi_config

欢迎关注“安全有理”微信公众号。

安全有理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值