概述
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
结构体表示:
/** 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:内存区域大小为2order
- base:内存区域基地址
- flags:内存区域标志,表示内存类型(RAM或者MMIO)和访问权限(读、写和执行等)
域实例
域实例使用struct sbi_domain结构体表示:
/** 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_addr、next_arg1、next_mode:这些字段从冷启动HART的暂存空间中获取,分别表示ROOT域下一个启动阶段的地址、arg1参数和特权模式
- system_reset_allowed、system_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域并进行了初始化:
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
函数添加:
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为例,关于根域的打印信息如下:
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模式读写和执行 |
上面索引为2和3的内存区域用于保护OpenSBI固件免受S模式和U模式访问,而索引5的剩余内存区域,如果想实现只有S/U模式访问,M模式不可访问的效果,需要借助RISC-V的SMEPMP机制实现。