1.domain机制介绍

domain机制:人为的将SOC内部硬件划分为不同的权限区域,然后分别独立运行,其使用了riscv的权限管理的硬件保护单元。它提供了一种在系统中划分资源和权限的方法,以确保软件实体之间的相互隔离和安全性。

OpenSBI运行在系统的M模式,而不同Domain的上层程序只能工作在S模式或者U模式,即便是Linux Kernel也必须接收OpenSBI的Domain权限限制,domain划分带来一个好处,比如你希望SOC中的一部分core在smp模式下运行linux内核,而另一部分core工作在amp模式下,单独运行裸机程序或者RTOS会非常容易操作,将其划分到不同Domian即可,除此之外,Domian还可以划分内存地址,mmio地址,可以进行较为细致的权限划分。

Opensbi的domain机制通过以下的方式实现:

  1. Domain ID:每个 domain 都有一个唯一的标识符,称为 Domain ID。它用于区分不同的 domain。
  2. Hart Mask:OpenSBI 使用 Hart Mask 来表示哪些处理器属于特定的 domain。Hart Mask 是一个位图,每个位代表一个处理器,可以将相应的位设置为 1 表示该处理器属于某个 domain。
  3. SBI 接口:OpenSBI 提供了一组 SBI(Supervisor Binary Interface)接口,用于 domain 之间的通信和资源管理。这些接口包括中断处理、内存管理、设备访问等,可以由 domain 使用来请求和管理资源。

在目录opensbi/docdomian_support.md文档介绍了如何使用设备树来基于opensbi来划分domain。

具体解释见:基于opensbi为quard_star创建domain | TimerのBlog (yanglianoo.github.io)

2.quard_star的domain实现

dts/quard_star_sbi.dts 添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

opensbi-domains { /* 定义opensbi-domains描述节点 */
compatible = "opensbi,domain,config"; /* 节点名称 */

tmem: tmem { /* 定义内存节点 */
compatible = "opensbi,domain,memregion"; /* 节点名称 */
base = <0x0 0xb0000000>; /* 起始地址注意64位地址哦 */
order = <28>; /* 内存大小即size=2^28 */
};

tuart: tuart { /* 定义mmio节点 */
compatible = "opensbi,domain,memregion"; /* 节点名称 */
base = <0x0 0x10002000>; /* 起始地址 */
order = <8>; /* size=2^8 */
mmio; /* mmio属性 */
devices = <&uart2>; /* 关联到设备节点上 */
};

allmem: allmem { /* 定义内存节点,这个节点保护所有地址 */
compatible = "opensbi,domain,memregion";
base = <0x0 0x0>;
order = <64>;
};

tdomain: trusted-domain { /* 定义domian节点 */
compatible = "opensbi,domain,instance"; /* 节点名称 */
possible-harts = <&cpu7>; /* domian中允许使用的cpu core */
regions = <&tmem 0x7>, <&tuart 0x7>, <&allmem 0x7>;/* 各个内存/mmio区域的权限,3bit读写运行权限 0x7拥有全部权限 */
boot-hart = <&cpu7>; /* domian中用于boot的core */
next-arg1 = <0x0 0x00000000>; /* 下级程序的参数 */
next-addr = <0x0 0xb0000000>; /* 下级程序的起始地址 */
next-mode = <0x0>; /* 下级程序的允许模式 0为U模式,1为S模式 */
system-reset-allowed; /* 允许复位 */
};

udomain: untrusted-domain {
compatible = "opensbi,domain,instance";
possible-harts = <&cpu0 &cpu1 &cpu2 &cpu3 &cpu4 &cpu5 &cpu6>;
regions = <&tmem 0x0>, <&tuart 0x0>, <&allmem 0x7>;
boot-hart = <&cpu0>;
next-arg1 = <0x0 0x82200000>;
next-addr = <0x0 0x82000000>;
next-mode = <0x1>;
system-reset-allowed;
};
};

分开来解释:

1
2
3
4
5
6
7
8
9
10
tdomain: trusted-domain { 								/* 定义domian节点 */
compatible = "opensbi,domain,instance"; /* 节点名称 */
possible-harts = <&cpu7>; /* domian中允许使用的cpu core */
regions = <&tmem 0x7>, <&tuart 0x7>, <&allmem 0x7>; /* 各个内存/mmio区域的权限,3bit读写运行权限 0x7拥有全部权限 */
boot-hart = <&cpu7>; /* domian中用于boot的core */
next-arg1 = <0x0 0x00000000>; /* 下级程序的参数 */
next-addr = <0x0 0xb0000000>; /* 下级程序的起始地址 */
next-mode = <0x0>; /* 下级程序的允许模式 0为U模式,1为S模式 */
system-reset-allowed; /* 允许复位 */
};

定义tdomiantrust domain 这个使用了cpu7,下级程序的起始地址0xb0000000,模式为U模式,可以运行freertos的实时操作系统等

1
2
3
4
5
6
7
8
9
10
udomain: untrusted-domain {
compatible = "opensbi,domain,instance";
possible-harts = <&cpu0 &cpu1 &cpu2 &cpu3 &cpu4 &cpu5 &cpu6>;
regions = <&tmem 0x0>, <&tuart 0x0>, <&allmem 0x7>; # 0x0 表示无权限
boot-hart = <&cpu0>;
next-arg1 = <0x0 0x82200000>;
next-addr = <0x0 0x82000000>;
next-mode = <0x1>; /* 下级程序的允许模式 0为U模式,1为S模式 */
system-reset-allowed;
};

定义udomain,不受信任的域,用于运行Linux系统等

这种模式的划分:

  • openSBI运行在系统的M模式,而不同Domain的上层程序只能工作在S模式或者U模式,即便是Linux Kernel也必须接收OpenSBIDomain权限限制,domain划分带来一个好处,比如你希望SOC中的一部分coresmp模式下运行linux内核,而另一部分core工作在amp模式下,单独运行裸机程序或者RTOS会非常容易操作,将其划分到不同Domian即可。
  • image-20240610153823283

3.domain测试代码

新建trust_domain文件夹,以及在文件夹内新建两个文件link.ldsstartup.s

trust_domain/link.lds

注意这起始的地址是0xb0000000,与上面 tdomian保持一直,因为信任域设置就从这个地址开始了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
OUTPUT_ARCH( "riscv" )

ENTRY( _start )

MEMORY
{
ddr (rxai!w) : ORIGIN = 0xb0000000, LENGTH = 256M
}

SECTIONS
{
.text :
{
KEEP(*(.text))
} >ddr
}

trust_domain/startup.s

这里串口输出的是uart2,也就是上面定义的tuart,输出地址为0x10002000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
	.section .text
.globl _start
.type _start,@function

_start:
li t0, 0x100
slli t0, t0, 20 # 左移20位
li t1, 0x200
slli t1, t1, 4 # 左移 4位
add t0, t0, t1
li t1, 'H'
sb t1, 0(t0)
li t1, 'e'
sb t1, 0(t0)
li t1, 'l'
sb t1, 0(t0)
li t1, 'l'
sb t1, 0(t0)
li t1, 'o'
sb t1, 0(t0)
li t1, ' '
sb t1, 0(t0)
li t1, 'Q'
sb t1, 0(t0)
li t1, 'u'
sb t1, 0(t0)
li t1, 'a'
sb t1, 0(t0)
li t1, 'r'
sb t1, 0(t0)
li t1, 'd'
sb t1, 0(t0)
li t1, ' '
sb t1, 0(t0)
li t1, 'S'
sb t1, 0(t0)
li t1, 't'
sb t1, 0(t0)
li t1, 'a'
sb t1, 0(t0)
li t1, 'r'
sb t1, 0(t0)
li t1, ' '
sb t1, 0(t0)
li t1, 'b'
sb t1, 0(t0)
li t1, 'o'
sb t1, 0(t0)
li t1, 'a'
sb t1, 0(t0)
li t1, 'r'
sb t1, 0(t0)
li t1, 'd'
sb t1, 0(t0)
li t1, '!'
sb t1, 0(t0)
li t1, '\r'
sb t1, 0(t0)
li t1, '\n'
sb t1, 0(t0)
_loop:
j _loop

.end

还需要修改 build.sh

将t_fw,写入到了fw.bin的0x400000偏移的地方. — flash

1
2
3
4
5
6
7
8
9
10
11
12
13
# 编译 tdomain
if [ ! -d "$SHELL_FOLDER/output/trusted_domain" ]; then
mkdir $SHELL_FOLDER/output/trusted_domain
fi
cd $SHELL_FOLDER/trust_domain
$CROSS_PREFIX-gcc -L/opt/riscv/riscv64-unknown-elf/lib -L/opt/riscv/lib -x assembler-with-cpp -c startup.s -o $SHELL_FOLDER/output/trusted_domain/startup.o
$CROSS_PREFIX-gcc -L/opt/riscv/riscv64-unknown-elf/lib -L/opt/riscv/lib -nostartfiles -T./link.lds -Wl,-Map=$SHELL_FOLDER/output/trusted_domain/trusted_fw.map -Wl,--gc-sections $SHELL_FOLDER/output/trusted_domain/startup.o -o $SHELL_FOLDER/output/trusted_domain/trusted_fw.elf
$CROSS_PREFIX-objcopy -O binary -S $SHELL_FOLDER/output/trusted_domain/trusted_fw.elf $SHELL_FOLDER/output/trusted_domain/trusted_fw.bin
$CROSS_PREFIX-objdump --source --demangle --disassemble --reloc --wide $SHELL_FOLDER/output/trusted_domain/trusted_fw.elf > $SHELL_FOLDER/output/trusted_domain/trusted_fw.lst

...
# 写入 tdomain,地址偏移 4k * 1k = 0x400000, 因此 t_fw的地址偏移 0x400000
dd of=fw.bin bs=1k conv=notrunc seek=4k if=$SHELL_FOLDER/output/trusted_domain/trusted_fw.bin

boot/start.s

这里还要将位与flash中0x400000中的 trust_fw.bin加载到DRAM中。 还是采用了 load_data函数

[0x20400000:0x20800000] –> 加载到[0x80200000:0x80600000]

1
2
3
4
5
6
7
8
9
//load trusted_fw.bin
//[0x20400000:0x20800000] --> [0x80200000:0x80600000]
li a0, 0x204
slli a0, a0, 20 //a0 = 0x20400000
li a1, 0xb00
slli a1, a1, 20 //a1 = 0xb0000000
li a2, 0xb04
slli a2, a2, 20 //a2 = 0xb0400000
load_data a0,a1,a2

run.sh

DEFAULT_VC来指定了qemu显示的分辨率,还需要新增三个-serial选项让qemu输出三个串口终端

1
2
3
4
5
6
7
8
9
10
11
12
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
DEFAULT_VC="1080x1920"

$SHELL_FOLDER/output/qemu/bin/qemu-system-riscv64 \
-M quard-star \
-m 1G \
-smp 8 \
-bios none \
-drive if=pflash,bus=0,unit=0,format=raw,file=$SHELL_FOLDER/output/fw/fw.bin \
-d in_asm -D qemu.log \
--serial vc:$DEFAULT_VC --serial vc:$DEFAULT_VC --serial vc:$DEFAULT_VC --monitor vc:$DEFAULT_VC --parallel none \
#-nographic --parallel none \

串口1 输出opensbi

image-20240610160946767

串口2 输出:

image-20240610161015912

4.内存布局

加入了trust_domain.bin后,内存布局如下,首先是flash0x400000上增加了t_domain,然后需要加载到 DRAM0xb0000000

image-20240610161319772