QEMU上RISC-V架构[U-Boot-OpenSBI-OPTEE-Linux]:环境搭建、启动流程概述、运行流程概述
1. 编译运行Kernel和OPTEE
参考文档《 OPTEE_00_01 - OP-TEE support - Home - RISE Project Confluence Wiki (atlassian.net)》。
下载和编译代码:
git clone https://gitlab.com/riseproject/riscv-optee/buildroot.git -b dev-optee-mpxy cd buildroot make qemu_riscv64_virt_optee_defconfig make
基于QEMU运行Linux和OPTEE:
./output/images/start-qemu.sh
也即执行如下命令:
qemu-system-riscv64 -M virt -cpu rv64,zkr=on \--指定CPU类型为 RISC-V 64位,并启用了zkr扩展(一个RISC-V扩展,用于支持Krentix内核)。
-dtb qemu_rv64_virt_domain.dtb \
-m 4096 -smp 2 \
-semihosting-config enable=on,target=native \--启用半主机(semihosting)功能,允许虚拟机调用宿主机(host)的系统服务。
-serial tcp:127.0.0.1:64320,server \--将虚拟机的串行端口重定向到TCP服务器,监听本地IP地址127.0.0.1的64320端口。
-bios u-boot-spl \--指定第一阶段引导加载程序(Secondary Program Loader),这里是U-Boot的SPL(小型引导加载程序)。
-device loader,file=u-boot.itb,addr=0x80200000 \--添加一个虚拟设备,加载U-Boot的ITB(Image Type Blob)文件,并将其映射到虚拟内存地址 `0x80200000`。
-device virtio-blk-device,drive=hd0 \--添加一个虚拟的VirtIO块设备,用于模拟硬盘驱动器。
-drive format=raw,file=sdcard.img,id=hd0 \--指定一个虚拟硬盘驱动器,使用原始格式的镜像文件sdcard.img。
-device virtio-net-pci,netdev=net0 \--添加一个虚拟的VirtIO网络设备。
-netdev user,id=net0,hostfwd=tcp::2200-:22 \--创建一个用户模式的网络设备,并设置端口转发,将宿主机的2200端口转发到虚拟机的22端口(通常用于SSH)。
-nographic
在另一个终端连接到Linux:
# launch another terminal and connect to normal world telnet localhost 64320
执行xtest测试:
# run 'xtest' or 'optee_example*' in the shell
左边窗口是QEMU shell,显示OPTEE日志;右边窗口Linux shell:
2 启动流程
对于2个Hart的OpenSBI+OPTEE+U-BOot启动流程如下:
RISC-V下SPL->OpenSBI-OPTEE-Linux启动流程如下:
- M-Mode:u-boot-spl加载u-boot.itb文件,启动OpenSBI。
- M-Mode->S-Mode:OpenSBI通过mret跳转到OPTEE执行,启动TOS。
- S-Mode->M-Mode:OPTEE启动完后,通过ecall返回到执行OpenSBI。
- M-Mode->S-Mode:OpenSBI通过mre启动u-boot。
- S-Mode:u-boot加载Kernel镜像,跳转到Kernel执行。
- S-Mode->U-Mode:Kernel加载rootfs,启动rootfs的init进程开启用户空间。
2.1 镜像组成
u-boot-spl作为bios,负责加载并加些u-boot.itb。
u-boot.itb根据binman.dts生成,包括:u-boot-nodtb.bin、tee.bin、fw_dynamic.bin。
/dts-v1/; / { timestamp = <0x66da6580>; description = "Configuration to load OpenSBI before U-Boot"; #address-cells = <0x02>; images { uboot { description = "U-Boot"; type = "standalone"; os = "U-Boot"; arch = "riscv"; compression = "none"; load = <0x00 0x81200000>; data = <...>; }; tee { description = "OP-TEE"; type = "tee"; arch = "riscv"; compression = "none"; os = "tee"; load = <0x00 0xf1000000>; data = [...]; }; opensbi { description = "OpenSBI fw_dynamic Firmware"; type = "firmware"; os = "opensbi"; arch = "riscv"; compression = "none"; load = <0x00 0x80100000>; entry = <0x00 0x80100000>; data = <...>; }; }; configurations { default = "conf-1"; conf-1 { description = "NAME"; firmware = "opensbi"; loadables = "uboot\0tee"; }; }; };
qemu_rv64_virt_domain.dtb是Linux kernel DTB文件。
sdcard.img包括了rootfs,以及存放于boot目录下的extlinux.conf和Image文件。
在rootfs编译完成后:
- extlinux.conf被overlay到rootfs中的boot目录中。
- 拷贝Image到rootfs的boot目录中。
然后对rootfs内容生成rootfs.ext2。rootfs.ext2经过genimage.sh根据genimage_sdcard.cfg配置生成sdcard.img。
extlinux.conf中定义了U-Boot启动Kernel的行为:
TIMEOUT 5 DEFAULT qemu_riscv64_optee LABEL qemu_riscv64_optee kernel /boot/Image append console=ttyS0,115200n8 earlycon=sbi rootwait root=/dev/vda1 rw
2.2 spl解析OpenSBI/OPTEE/U-Boot
spl中解析u-boot.itb文件:
- 从FIT中解析出、tee、U-Boot镜像参数。
- 将OPTEE、U-Boot参数附加到fdt中,所以在OpenSBI中可以获取OPTEE启动数据。
- 跳转到OpenSBI中执行。
spl_ram_load_image
spl_load_simple_fit
spl_simple_fit_read
spl_ram_load_read--使用特定设备读取手段,将FIT读取到内存中。
spl_simple_fit_parse
spl_fit_get_image_node
load_simple_fit--将FIT中数据读取到FIT中load地址。
fit_image_verify_with_data--根据需要进行验签。
gunzip/image_decomp--根据需要进行解压。
spl_fit_record_loadable--将FIT中解析的内容(arch、os、type、size、entry、load)写入到fit-images下的每个镜像中。
附加的dtb内容如下:
fit-images { tee { arch = "riscv"; os = "tee"; type = "tee"; size = <0x000922da>; load = <0x00000000 0xf1000000>; }; uboot { arch = "riscv"; os = "U-Boot"; type = "standalone"; size = <0x00099760>; load = <0x00000000 0x81200000>; }; };
spl启动OpenSBI流程参考《SPL到OpenSBI》。
2.3 OpenSBI启动OPTEE
2.3.1 OpenSBI mpxy ecall处理
Trap处理,以:
_trap_handler
TRAP_CALL_C_ROUTINE
sbi_trap_handler
sbi_ecall_handler
sbi_ecall_find_extension--根据extension_id找到对应的struct sbi_ecall_extension。
--调用struct sbi_ecall_extension->handle()函数。
--将返回值写入struct sbi_trap_context->regs的a0和a1中。
sbi_trap_set_context
将ecall_mpxy放到sbi_ecall_exts中,当S-Mode发起mpxy ecall即调用sbi_ecall_mpxy_handler:
struct sbi_ecall_extension ecall_mpxy = { .extid_start = SBI_EXT_MPXY, .extid_end = SBI_EXT_MPXY, .register_extensions = sbi_ecall_mpxy_register_extensions, .handle = sbi_ecall_mpxy_handler, };
sbi_ecall_mpxy_handler处理如下funcid:
sbi_ecall_mpxy_handler
SBI_EXT_MPXY_SET_SHMEM
sbi_mpxy_set_shmem
SBI_EXT_MPXY_READ_ATTRS
sbi_mpxy_read_attrs
SBI_EXT_MPXY_WRITE_ATTRS
sbi_mpxy_write_attrs
SBI_EXT_MPXY_SEND_MSG_WITH_RESP
sbi_mpxy_send_message
SBI_EXT_MPXY_SEND_MSG_NO_RESP
sbi_mpxy_send_message
SBI_EXT_MPXY_GET_NOTIFICATION_EVENTS
sbi_mpxy_get_notification_events
2.3.2 OpenSBI启动流程
OpenSBI平台相关操作函数集:
const struct sbi_platform_operations platform_ops = { .cold_boot_allowed = generic_cold_boot_allowed, ... .mpxy_init = fdt_mpxy_init, .vendor_ext_check = generic_vendor_ext_check, .vendor_ext_provider = generic_vendor_ext_provider, }; struct sbi_platform platform = { .opensbi_version = OPENSBI_VERSION, .platform_version = SBI_PLATFORM_VERSION(CONFIG_PLATFORM_GENERIC_MAJOR_VER, CONFIG_PLATFORM_GENERIC_MINOR_VER), ... .platform_ops_addr = (unsigned long)&platform_ops };
OpenSBI启动流程中mpxy处理包括:
- 初始化mpxy。
- 注册mpxy相关ecall。
sbi_init
init_coldboot
sbi_domain_init
sbi_domain_register--注册root Domain。
sbi_domain_finalize--
sbi_platform_domains_init
generic_domains_init--调用平台domains_init()函数。
fdt_domains_populate--根据fdt初始化Domain。
fdt_iterate_each_domain--找到opensbi-domain节点,解析其中的Domain并注册。
__fdt_parse_domain
sbi_domain_register--注册trusted-domain和untrusted-domain分别对应OPTEE和U-Boot。
--如果当前Hart是Cold Hart,那么切换scratch的next_addr/next_mode/next_arg1参数为trusted-domain参数。后面会优先启动OPTEE。
sbi_mpxy_init
sbi_platform_mpxy_init
--调用struct sbi_platform的mpxy_init函数。
fdt_mpxy_init--遍历fdt_mpxy_drivers,匹配后执行init初始化函数。
mpxy_opteed_init--调用fdt_mpxy_opteed的init函数
sbi_ecall_init--遍历sbi_ecall_exts,并调用其register_extensions()函数。
sbi_boot_print_general--显示Platform、Firmware、SBI信息。
sbi_boot_print_domains--显示所有Domain信息。
sbi_boot_print_hart--显示当前Hart信息。
sbi_hsm_hart_start_finish
sbi_hart_switch_mode
mret--返回到OPTEE。
在init_coldboot中打印的Platform、Firmware、SBI、Domain、Hart信息如下:
Platform Name : riscv-virtio,qemu--表明这是一个使用QEMU模拟的RISC-V平台,具有虚拟I/O设备。 Platform Features : medeleg--支持中间异常委托(medeleg属性)。 Platform HART Count : 2--平台有2个硬件线程(HART)。 Platform IPI Device : aclint-mswi--使用`aclint-mswi`设备处理中断处理器间的中断(IPI)。 Platform Timer Device : aclint-mtimer @ 10000000Hz--使用`aclint-mtimer`设备作为计时器,频率为10MHz。 Platform Console Device : uart8250--控制台设备使用的是`uart8250`UART控制器。 Platform HSM Device : --- Platform PMU Device : --- Platform Reboot Device : syscon-reboot--使用`syscon-reboot`设备进行系统重启。 Platform Shutdown Device : syscon-poweroff--使用`syscon-poweroff`设备进行系统关闭。 Platform Suspend Device : --- Platform CPPC Device : --- Platform RAS Device : --- Firmware Base : 0x80100000--固件基地址。 Firmware Size : 349 KB--固件大小。 Firmware RW Offset : 0x40000--固件可读写区域的偏移量。 Firmware RW Size : 93 KB--固件可读写区域的大小。 Firmware Heap Offset : 0x4e000--固件堆的偏移量。 Firmware Heap Size : 37 KB (total), 2 KB (reserved), 17 KB (used), 17 KB (free)--固件堆的总大小、保留大小、已使用大小和空闲大小。 Firmware Scratch Size : 4096 B (total), 416 B (used), 3680 B (free)--固件临时存储区的总大小、已使用大小和空闲大小。 Runtime SBI Version : 2.0--SBI(SupervisorBinaryInterface)版本。 Domain0 Name : root--域0的名称,运行OpenSBI镜像。 Domain0 Boot HART : 1--域0启动时使用的HART编号。 Domain0 HARTs : H1 0H1 ,1H1--域0中包含的HART编号。 Domain0 Region00 : 0x0000000000100000-0x0000000000100fff H1 M: H1 (IH1 ,RH1 ,WH1 ) H1 S/U: H1 (RH1 ,WH1 )--描述域0中的内存区域,包括起始地址、结束地址、权限和访问控制。 Domain0 Region01 : 0x0000000010000000-0x0000000010000fff H1 M: H1 (IH1 ,RH1 ,WH1 ) H1 S/U: H1 (RH1 ,WH1 ) Domain0 Region02 : 0x0000000002000000-0x000000000200ffff H1 M: H1 (IH1 ,RH1 ,WH1 ) H1 S/U: H1 () Domain0 Region03 : 0x0000000080140000-0x000000008015ffff H1 M: H1 (RH1 ,WH1 ) H1 S/U: H1 () Domain0 Region04 : 0x0000000080100000-0x000000008013ffff H1 M: H1 (RH1 ,XH1 ) H1 S/U: H1 () Domain0 Region05 : 0x000000000c400000-0x000000000c5fffff H1 M: H1 (IH1 ,RH1 ,WH1 ) H1 S/U: H1 (RH1 ,WH1 ) Domain0 Region06 : 0x000000000c000000-0x000000000c3fffff H1 M: H1 (IH1 ,RH1 ,WH1 ) H1 S/U: H1 (RH1 ,WH1 ) Domain0 Region07 : 0x0000000000000000-0xffffffffffffffff H1 M: H1 () H1 S/U: H1 (RH1 ,WH1 ,XH1 ) Domain0 Next Address : 0x0000000081200000--域0下一个执行地址,即U-Boot地址。 Domain0 Next Arg1 : 0x000000008129d048--域0下一个执行参数地址。 Domain0 Next Mode : H1 S-mode--域0下一个执行模式(S-mode,即Supervisor模式)。 Domain0 SysReset : yes--域0支持系统重置。 Domain0 SysSuspend : yes--域0支持系统挂起。 Domain1 Name : trusted-domain--运行OPTEE镜像。 Domain1 Boot HART : 1 Domain1 HARTs : H1 0*H1 ,1*H1 Domain1 Region00 : 0x0000000002000000-0x000000000200ffff H1 M: H1 (IH1 ,RH1 ,WH1 ) H1 S/U: H1 () Domain1 Region01 : 0x0000000080140000-0x000000008015ffff H1 M: H1 (RH1 ,WH1 ) H1 S/U: H1 () Domain1 Region02 : 0x0000000080100000-0x000000008013ffff H1 M: H1 (RH1 ,XH1 ) H1 S/U: H1 () Domain1 Region03 : 0x0000000000000000-0xffffffffffffffff H1 M: H1 (RH1 ,WH1 ,XH1 ) H1 S/U: H1 (RH1 ,WH1 ,XH1 ) Domain1 Next Address : 0x00000000f1000000--域1下一个执行地址,即OPTEE地址。 Domain1 Next Arg1 : 0x000000008129d048 Domain1 Next Mode : H1 S-mode Domain1 SysReset : no Domain1 SysSuspend : no Domain2 Name : untrusted-domain--运行U-Boot镜像。 Domain2 Boot HART : 0 Domain2 HARTs : H1 0H1 ,1H1 Domain2 Region00 : 0x0000000002000000-0x000000000200ffff H1 M: H1 (IH1 ,RH1 ,WH1 ) H1 S/U: H1 () Domain2 Region01 : 0x0000000080140000-0x000000008015ffff H1 M: H1 (RH1 ,WH1 ) H1 S/U: H1 () Domain2 Region02 : 0x0000000080100000-0x000000008013ffff H1 M: H1 (RH1 ,XH1 ) H1 S/U: H1 () Domain2 Region03 : 0x00000000f1000000-0x00000000f1ffffff H1 M: H1 () H1 S/U: H1 () Domain2 Region04 : 0x0000000000000000-0xffffffffffffffff H1 M: H1 (RH1 ,WH1 ,XH1 ) H1 S/U: H1 (RH1 ,WH1 ,XH1 ) Domain2 Next Address : 0x0000000081200000--域2下一个执行地址,即U-Boot地址。 Domain2 Next Arg1 : 0x000000008129d048 Domain2 Next Mode : H1 S-mode Domain2 SysReset : no Domain2 SysSuspend : no Boot HART ID : 1--启动时使用的HART编号。 Boot HART Domain : trusted-domain--启动时HART所在的域。 Boot HART Priv Version : v1.12--启动时HART的私有版本。 Boot HART Base ISA : rv64imafdch--启动时HART支持的基础ISA扩展集。 Boot HART ISA Extensions : sstc,zicntr,zihpm,zkr,zicboz,zicbom,sdtrig,svadu--启动时HART支持的ISA扩展。 Boot HART PMP Count : 16--启动时HART支持的物理内存保护(PMP)条目数。 Boot HART PMP Granularity : 2 bits--启动时HARTPMP的粒度。 Boot HART PMP Address Bits: 54--启动时HARTPMP支持的地址位数。 Boot HART MHPM Info : 16 (0x0007fff8)--启动时HART支持的机器性能监控器(MHPM)计数器数量和位掩码。 Boot HART Debug Triggers : 2 triggers--启动时HART支持的调试触发器数量。 Boot HART MIDELEG : 0x0000000000001666--启动时HART支持的中间异常委托位掩码。 Boot HART MEDELEG : 0x0000000000f0b509--启动时HART支持的机器异常委托位掩码。
mpxy初始化
fdt中包含如下mpxy数据:
sbi-mpxy-opteed { opensbi-domain-instance = <0x00000002>; riscv,sbi-mpxy-channel-id = <0x00000002>; compatible = "riscv,sbi-mpxy-opteed"; };
fdt_mpxy_drivers中的fdt_mpxy_opteed为:
static const struct fdt_match mpxy_opteed_match[] = { { .compatible = "riscv,sbi-mpxy-opteed", .data = NULL }, {}, }; struct fdt_mpxy fdt_mpxy_opteed = { .match_table = mpxy_opteed_match, .init = mpxy_opteed_init, };
所以匹配fdt_mpxy_drivers中的fdt_mpxy_opteed,执行mpxy_opteed_init():
mpxy_opteed_init
opteed_domain_setup
sbi_mpxy_register_channel--获取channel id后,初始化struct sbi_mpxy_channel,然后注册。
mpxy_opteed_send_message--OPTEE调用ecall通过channel发送消息,OpenSBI接收处理。
OPTEED_MSG_COMMUNICATE
sbi_ecall_tee_domain_enter
sbi_domain_context_enter--进入trusted-domain。
OPTEED_MSG_COMPLETE
sbi_ecall_tee_domain_exit
sbi_domain_context_exit--进入untrusted-domain。
2.4 OPTEE启动流程
optee启动流程:
_start reset_primary
thread_init_thread_core_local
plat_primary_init_early
console_init
core_init_mmu_map
boot_init_primary_early
boot_init_primary_late init_external_dt
discover_nsec_memory
update_external_dt
boot_primary_init_intc
init_tee_runtime
call_finalcalls
wait_secondary
thread_clr_boot_thread
thread_return_to_udomain
thread_return_to_udomain_by_mpxy
sbi_mpxy_send_message_withresp
sbi_ecall--发起extension id为SBI_EXT_MPXY,func id为SBI_EXT_MPXY_SEND_MSG_WITH_RESP,msg id为OPTEED_MSG_COMPLETE的ecall。进入OpenSBI调用sbi_ecall_tee_domain_exit()函数跳转到untrusted domain。
reset_secondary
boot_init_secondary
init_secondary_helper
2.5 OpenSBI启动U-Boot流程
OpenSBI收到OPTEE的ecall调用进入OPTEED_MSG_COMPLETE处理流程:
sbi_ecall_tee_domain_exit
sbi_domain_context_exit--找到当前Hart的struct sbi_context,然后配置下一个Domain的struct sbi_context。
switch_to_next_domain_context--保存当前CSR等信息到struct sbi_context中;根据target_dom的boot_hartid/next_arg1/next_addr/next_mode跳转。
sbi_hart_switch_mode
mret--根据设置的next_addr跳转到指定Mode,比如进入U-Boot。
2.5 U-Boot启动Kernel流程
参考《u-boot到linux》。
3 Linux安全应用和OPTEE交互流程
Linux下OPTEE安全应用和OPTEE TA交互流程如下: