OpenSBI的Domain支持

概述

OpenSBI域(Domain)是将底层硬件划分为系统级的分区,每个分区拥有自己的内存区域(RAM和MMIO设备)和HARTs(硬件线程)。OpenSBI将利用RISC-V架构的特性(如PMP、ePMP、IOPMP、SiFive Shield等)来实现域之间的安全隔离。

OpenSBI域三个重要的结构:

  • struct sbi_domain_memregion:域的内存区域
  • struct sbi_hartmask:域的HART集合
  • struct sbi_domain:域的实例

每个HART都必须分配一个OpenSBI域,OpenSBI负责填充域和映射域到对应HART。OpenSBI平台默认将所有HARTs分配给ROOT域,除非有特定的平台需求,一般并不需要手动配置域。

域的内存区域

域的内存区域使用struct_sbi_domain_memregion结构体表示:

c 复制代码
/** Representation of OpenSBI domain memory region */
struct sbi_domain_memregion {
	/**
	 * Size of memory region as power of 2
	 * It has to be minimum 3 and maximum __riscv_xlen
	 */
	unsigned long order;
	/**
	 * Base address of memory region
	 * It must be 2^order aligned address
	 */
	unsigned long base;
	/** Flags representing memory region attributes */
	unsigned long flags;
}
  • order :内存区域大小为2^order^
  • base:内存区域基地址
  • flags:内存区域标志,表示内存类型(RAM或者MMIO)和访问权限(读、写和执行等)

域实例

域实例使用struct sbi_domain结构体表示:

c 复制代码
/** Representation of OpenSBI domain */
struct sbi_domain {
	/**
	 * Logical index of this domain
	 * Note: This set by sbi_domain_finalize() in the coldboot path
	 */
	u32 index;
	/**
	 * HARTs assigned to this domain
	 * Note: This set by sbi_domain_init() and sbi_domain_finalize()
	 * in the coldboot path
	 */
	struct sbi_hartmask assigned_harts;
	/** Spinlock for accessing assigned_harts */
	spinlock_t assigned_harts_lock;
	/** Name of this domain */
	char name[64];
	/** Possible HARTs in this domain */
	const struct sbi_hartmask *possible_harts;
	/** Contexts for possible HARTs indexed by hartindex */
	struct sbi_context *hartindex_to_context_table[SBI_HARTMASK_MAX_BITS];
	/** Array of memory regions terminated by a region with order zero */
	struct sbi_domain_memregion *regions;
	/** HART id of the HART booting this domain */
	u32 boot_hartid;
	/** Arg1 (or 'a1' register) of next booting stage for this domain */
	unsigned long next_arg1;
	/** Address of next booting stage for this domain */
	unsigned long next_addr;
	/** Privilege mode of next booting stage for this domain */
	unsigned long next_mode;
	/** Is domain allowed to reset the system */
	bool system_reset_allowed;
	/** Is domain allowed to suspend the system */
	bool system_suspend_allowed;
	/** Identifies whether to include the firmware region */
	bool fw_region_inited;
};
  • index:域的逻辑索引
  • name:域的名称
  • assigned_harts:分配给此域的HART
  • possible_harts:此域中可能的HART
  • hartindex_to_context_table:对应可能的HART的上下文
  • regions :内存区域数组,以order=0的内存区域结束
  • boot_hartid:启动此域的HART ID
  • next_addr:此域下一个启动阶段的地址
  • next_arg1:下一个启动阶段的arg1参数
  • next_mode:此域下一个启动阶段的特权模式,可以是S模式或U模式
  • system_reset_allowed:此域是否允许复位系统
  • system_suspend_allowed:此域是否允许挂起系统

对于struct sbi_domain 结构体中的regions参数,为了与PMP要求对齐,有一些约束限制:

  • 必须存在一个内存区域来保护OpenSBI固件,使其不能被S模式和U模式进行访问
  • 对于两个重叠内存区域,必须是一个区域包含另外一个区域;内存区域大小不能一样;内存区域标志不能相同
  • 当进行内存访问检查时,如果存在重叠的地址范围,优先考虑最小内存区域的标志

根域

在OpenSBI中,ROOT域是一个特殊的默认域,其自动分配给所有的HART。OpenSBI在启动早期会配置这个域,如下:

  • index:ROOT域的逻辑索引始终为零,表示它是系统中的第一个最基本的域
  • name:ROOT域的名称固定为"root"
  • assigned_harts:在启动时,所有有效的HART都被默认分配给ROOT域。然而,后续这个分配可能会发生变化,但ROOT域始终保留作为系统的基础
  • possible_harts:ROOT域可能的HART集合,这意味着理论上任何HART都可以被分配给ROOT域(尽管实际分配可能有所不同)
  • hartindex_to_context_table:这是一个上下文表,映射了ROOT域中每个可能HART的上下文信息
  • regions :ROOT域通常有两个内存区域:
    • 一个是用于保护OpenSBI固件免受S模式和U模式访问的内存区域
    • 一个是覆盖整个内存地址空间的内存区域(大小等于__riscv_xlen定义的位宽),允许S模式和U模式访问所有内存
  • boot_hartid:启动此ROOT域的冷启动HART
  • next_addrnext_arg1next_mode:这些字段从冷启动HART的暂存空间中获取,分别表示ROOT域下一个启动阶段的地址、arg1参数和特权模式
  • system_reset_allowedsystem_suspend_allowed:ROOT域允许复位和挂起系统,因为它是系统的基础管理域

域的影响

系统被划分为多个域后,会带来一些影响:

  • HART的域上下文:在任何时刻,一个HART都只能在一个OpenSBI域上下文中运行。这意味着HART的特权级别、内存访问权限和其他相关资源都受其所属域的限制。
  • SBI IPI和RFENCE调用:从HART A发出的SBI IPI和RFENCE调用仅限于HART A域中的HART。这有助于实现域间隔离,防止未经授权的通信。
  • SBI HSM调用:如果HART A尝试通过SBI HSM(硬件状态管理)调用来更改或读取HART B的状态,那么这些调用仅当HART A和HART B被分配到同一个域时才有效。这确保了只有属于同一管理域的HART才能相互管理和交互。
  • 内存访问权限:在S模式或U模式下运行的HART只能访问其所属域定义的内存区域。这通过物理内存保护(PMP)等机制来实现,确保了不同域之间的内存隔离和安全性。

代码

在OpenSBI的启动代码中,建立了一个ROOT域并进行了初始化:

c 复制代码
int sbi_domain_init(struct sbi_scratch *scratch, u32 cold_hartid)
{
	u32 i;
	int rc;
	struct sbi_hartmask *root_hmask;
	struct sbi_domain_memregion *root_memregs;
	const struct sbi_platform *plat = sbi_platform_ptr(scratch);

	if (scratch->fw_rw_offset == 0 ||
	    (scratch->fw_rw_offset & (scratch->fw_rw_offset - 1)) != 0) {
		sbi_printf("%s: fw_rw_offset is not a power of 2 (0x%lx)\n",
			   __func__, scratch->fw_rw_offset);
		return SBI_EINVAL;
	}

	if ((scratch->fw_start & (scratch->fw_rw_offset - 1)) != 0) {
		sbi_printf("%s: fw_start and fw_rw_offset not aligned\n",
			   __func__);
		return SBI_EINVAL;
	}

	domain_hart_ptr_offset = sbi_scratch_alloc_type_offset(void *);
	if (!domain_hart_ptr_offset)
		return SBI_ENOMEM;

	root_memregs = sbi_calloc(sizeof(*root_memregs), ROOT_REGION_MAX + 1);
	if (!root_memregs) {
		sbi_printf("%s: no memory for root regions\n", __func__);
		rc = SBI_ENOMEM;
		goto fail_free_domain_hart_ptr_offset;
	}
	root.regions = root_memregs;

	root_hmask = sbi_zalloc(sizeof(*root_hmask));
	if (!root_hmask) {
		sbi_printf("%s: no memory for root hartmask\n", __func__);
		rc = SBI_ENOMEM;
		goto fail_free_root_memregs;
	}
	root.possible_harts = root_hmask;

	/* Root domain firmware memory region */
	sbi_domain_memregion_init(scratch->fw_start, scratch->fw_rw_offset,
				  (SBI_DOMAIN_MEMREGION_M_READABLE |
				   SBI_DOMAIN_MEMREGION_M_EXECUTABLE),
				  &root_memregs[root_memregs_count++]);

	sbi_domain_memregion_init((scratch->fw_start + scratch->fw_rw_offset),
				  (scratch->fw_size - scratch->fw_rw_offset),
				  (SBI_DOMAIN_MEMREGION_M_READABLE |
				   SBI_DOMAIN_MEMREGION_M_WRITABLE),
				  &root_memregs[root_memregs_count++]);

	root.fw_region_inited = true;

	/*
	 * Allow SU RWX on rest of the memory region. Since pmp entries
	 * have implicit priority on index, previous entries will
	 * deny access to SU on M-mode region. Also, M-mode will not
	 * have access to SU region while previous entries will allow
	 * access to M-mode regions.
	 */
	sbi_domain_memregion_init(0, ~0UL,
				  (SBI_DOMAIN_MEMREGION_SU_READABLE |
				   SBI_DOMAIN_MEMREGION_SU_WRITABLE |
				   SBI_DOMAIN_MEMREGION_SU_EXECUTABLE),
				  &root_memregs[root_memregs_count++]);

	/* Root domain memory region end */
	root_memregs[root_memregs_count].order = 0;

	/* Root domain boot HART id is same as coldboot HART id */
	root.boot_hartid = cold_hartid;

	/* Root domain next booting stage details */
	root.next_arg1 = scratch->next_arg1;
	root.next_addr = scratch->next_addr;
	root.next_mode = scratch->next_mode;

	/* Root domain possible and assigned HARTs */
	for (i = 0; i < plat->hart_count; i++)
		sbi_hartmask_set_hartindex(i, root_hmask);

	/* Finally register the root domain */
	rc = sbi_domain_register(&root, root_hmask);
	if (rc)
		goto fail_free_root_hmask;

	return 0;

fail_free_root_hmask:
	sbi_free(root_hmask);
fail_free_root_memregs:
	sbi_free(root_memregs);
fail_free_domain_hart_ptr_offset:
	sbi_scratch_free_offset(domain_hart_ptr_offset);
	return rc;
}

上面的代码中建立了三个针对RAM空间的内存区域:OpenSBI代码段、数据段以及其他剩余内存区域。其余的内存区域是针对MMIO设备,是在设备初始化中添加到ROOT域中,如UART设备通过调用sbi_domain_root_add_memrange函数添加:

c 复制代码
int uart8250_init(unsigned long base, u32 in_freq, u32 baudrate, u32 reg_shift,
		  u32 reg_width, u32 reg_offset)
{
	u16 bdiv = 0;

	uart8250_base      = (volatile char *)base + reg_offset;
	uart8250_reg_shift = reg_shift;
	uart8250_reg_width = reg_width;
	uart8250_in_freq   = in_freq;
	uart8250_baudrate  = baudrate;

	if (uart8250_baudrate) {
		bdiv = (uart8250_in_freq + 8 * uart8250_baudrate) /
		       (16 * uart8250_baudrate);
	}

	/* Disable all interrupts */
	set_reg(UART_IER_OFFSET, 0x00);
	/* Enable DLAB */
	set_reg(UART_LCR_OFFSET, 0x80);

	if (bdiv) {
		/* Set divisor low byte */
		set_reg(UART_DLL_OFFSET, bdiv & 0xff);
		/* Set divisor high byte */
		set_reg(UART_DLM_OFFSET, (bdiv >> 8) & 0xff);
	}

	/* 8 bits, no parity, one stop bit */
	set_reg(UART_LCR_OFFSET, 0x03);
	/* Enable FIFO */
	set_reg(UART_FCR_OFFSET, 0x01);
	/* No modem control DTR RTS */
	set_reg(UART_MCR_OFFSET, 0x00);
	/* Clear line status */
	get_reg(UART_LSR_OFFSET);
	/* Read receive buffer */
	get_reg(UART_RBR_OFFSET);
	/* Set scratchpad */
	set_reg(UART_SCR_OFFSET, 0x00);

	sbi_console_set_device(&uart8250_console);

	return sbi_domain_root_add_memrange(base, PAGE_SIZE, PAGE_SIZE,
					    (SBI_DOMAIN_MEMREGION_MMIO |
					    SBI_DOMAIN_MEMREGION_SHARED_SURW_MRW));
}

以Qemu启动OpenSBI为例,关于根域的打印信息如下:

text 复制代码
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)
Domain0 Next Address      : 0x0000000080200000
Domain0 Next Arg1         : 0x0000000082200000
Domain0 Next Mode         : S-mode
Domain0 SysReset          : yes
Domain0 SysSuspend        : yes

可以看出ROOT域共建立了六个内存区域:

区域号 地址 标志 说明
0 0x10000000~0x10000FFF SBI_DOMAIN_MEMREGION_MMIO | SBI_DOMAIN_MEMREGION_SHARED_SURW_MRW UART设备,MMIO类型,M/S/U模式均可读写
1 0x02000000~0x0200FFFF SBI_DOMAIN_MEMREGION_MMIO | SBI_DOMAIN_MEMREGION_M_READABLE | SBI_DOMAIN_MEMREGION_M_WRITABLE CLINT设备,MMIO类型,只M模式可读写
2 0x80040000~0x8005FFFF SBI_DOMAIN_MEMREGION_M_READABLE | SBI_DOMAIN_MEMREGION_M_EXECUTABLE OpenSBI数据段,只M模式可读写
3 0x80000000~0x8003FFFF SBI_DOMAIN_MEMREGION_M_READABLE | SBI_DOMAIN_MEMREGION_M_WRITABLE OpenSBI代码段,只M模式可读和可执行
4 0x0C000000~0x0FFFFFFF SBI_DOMAIN_MEMREGION_MMIO | SBI_DOMAIN_MEMREGION_SHARED_SURW_MRW PLIC设备,MMIO类型,M/S/U模式均可读写
5 0x0~0xFFFFFFFFFFFFFFFF SBI_DOMAIN_MEMREGION_SU_READABLE | SBI_DOMAIN_MEMREGION_SU_WRITABLE | SBI_DOMAIN_MEMREGION_SU_EXECUTABLE 其他剩余内存区域,只允许S/U模式读写和执行

上面索引为23 的内存区域用于保护OpenSBI固件免受S模式和U模式访问,而索引5 的剩余内存区域,如果想实现只有S/U模式访问,M模式不可访问的效果,需要借助RISC-V的SMEPMP机制实现。

参考

  1. domain_support

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

相关推荐
BigDark的笔记15 天前
【鸿蒙】0x02-LiteOS-M基于Qemu RISC-V运行
华为·harmonyos·risc-v
早上真好18 天前
【项目推荐】CakeMu-RV:一个开放的 RISC-V 处理器模拟器学习项目
嵌入式硬件·mcu·学习·计算机外设·risc-v
sinovoip18 天前
Banana Pi BPI-RV2 RISC-V路由开发板采用矽昌通信SF2H8898芯片
risc-v
CV金科19 天前
freertos的基础(二)内存管理:堆和栈
stm32·开源·arm·freertos·risc-v
BroccoliKing21 天前
An FPGA-based SoC System——RISC-V On PYNQ项目复现
arm开发·单片机·mcu·fpga开发·dsp开发·risc-v
百里杨24 天前
X86(Local APIC+I/O APIC)与RISC-V(IMSIC+APLIC)对比
risc-v·x86·local apic·ioapic·imsic·aplic
嵌入式Linux,24 天前
一块钱的RISC-V 32位芯片
risc-v
世事如云有卷舒1 个月前
RISC-V学习笔记
笔记·学习·risc-v
oahrzvq1 个月前
【CPU】RISC-V 与 x86 操作数字段的区别
系统架构·risc-v
MounRiver_Studio1 个月前
基于VSCode软件框架的RISC-V IDE MRS2正式上线发布
ide·vscode·mcu·risc-v