适用于HPM的RustSBI实现
HPMicro 的 MCU 一直以高性能著称,之前也一直有想在 HPM 的 MCU 上运行 Linux 的想法。直到看见 Linux 6.10 中支持了 RISC-V 架构在 S-mode 中运行 nommu 内核*,才下定决心开始在 HPM6360 上折腾 nommu Linux。
划线部分链接为:
https://lore.kernel.org/r/20240227003630.3634533-5-samuel.holland@sifive.com
请手动跳转
在 ARM 架构中,通常 Linux 的启动流程为:
而在具有 S 态的 RISC-V 架构中,通常的启动流程为:
其中 BootROM、Loader 和 SBI Runtime 运行 M-mode(机器模式)下,具体的引导程序和 Linux 等操作系统内核运行在 S-mode(监管者模式)下,而用户进程运行在 U-mode(用户模式)中。我们看到 RISC-V 的启动流程中相比 ARM 多了一个 SBI Runtime,那么什么是 SBI?
RISC-V 架构中,存在着定义于操作系统之下的运行环境(Runtime)。这个运行环境不仅将引导启动 RISC-V 下的操作系统, 还将常驻后台,为操作系统提供一系列二进制接口,以便其获取和操作硬件信息。RISC-V 给出了此类环境和二进制接口的规范,称为“监管者二进制接口”,即 “SBI”。
SBI 有多种实现,如 Berkeley Boot Loader (BBL)、OpenSBI。而本次项目中使用的 SBI 实现为 RustSBI。
RustSBI 项目源于2020年清华操作系统夏令营,旨在使用 Rust 语言编写 RISC-V 指令集中的 SBI 实现,支撑上层系统软件比如操作系统的运行。在国际 SBI 实现列表中获得 编号四。具有以下功能:
· 多功能且可拓展的操作系统运行时
· 为物理机、虚拟机、模拟器提供支持和兼容性
· 支持 RISC-V SBI 规范 v2.0
· 使用 Rust 编写,使用稳定版本的 Rust 工具链构建
由于 HPM 系列芯片的启动设备较为单一(XPI),且 XPI 的初始化工作已经在 BootROM 中完成并映射到地址空间中,同时为了加快启动速度,本项目最终没有移植 U-Boot;而是基于 RustSBI 库,编写操作系统的 Bootloader,实现 SDRAM 初始化、固件加载、设备树传递和内核跳转等功能,以及为操作系统提供监管者二进制接口(SBI)。
最终在 HPM6360 芯片上的启动流程如下:
Zicntr 指令集拓展支持
Linux 内核中,使用了 TIME 和 TIMEH 这两个 CSR 寄存器来获取系统时钟用于调度。但 HPM6360 使用的 Andes D45 核心并没有实现这两个 CSR 寄存器,在执行 csrrs rd, time, rs1 或 csrrs rd, timeh, rs1 指令时会产生非法指令异常(Illegal Instruction)。这就需要软件在异常处理函数中模拟这两条指令的执行。
static inline cycles_t get_cycles(void)
{
return csr_read(CSR_TIME);
}
#define get_cycles get_cycles
static inline u32 get_cycles_hi(void)
{
return csr_read(CSR_TIMEH);
}
#define get_cycles_hi get_cycles_hi
#endif /* !CONFIG_RISCV_M_MODE */
#ifdef CONFIG_64BIT
static inline u64 get_cycles64(void)
{
return get_cycles();
}
#else /* CONFIG_64BIT */
static inline u64 get_cycles64(void)
{
u32 hi, lo;
do {
hi = get_cycles_hi();
lo = get_cycles();
} while (hi != get_cycles_hi());
return ((u64)hi << 32) | lo;
}
#endif /* CONFIG_64BIT */
arch/riscv/include/asm/timex.h:51
这里我使用了 riscv-decode 这个库对机器码进行解码。
1、触发非法指令异常后从 mtval 寄存器中读取到非法指令;
2、将其作为参数调用 riscv_decode::decode() 函数进行解码,获取到 CSR 以及 rd 的值,判断是否为 TIME:0xc01 和 TIMEH:0xc81 这两个 CSR 寄存器;
3、如果是,则将 MCHTMR 外设中 MTIME 寄存器的值保存至 rd 寄存器中。并恢复现场并从异常指令的下一条指令开始执行;
4、如果不是,则将异常委托给 Linux 内核处理。模拟异常发生时的硬件行为,填充对应寄存器并更新 mstatus:MPP ,使异常返回后的特权级别为 S-mode,最后将 mepc 寄存器覆盖为 stvec 的值,异常返回后将从 Linux 内核的异常处理函数入口开始执行。具体实现细节请参考:
https://github.com/rustsbi/rustsbi-hpm/blob/194d9cc7899fef320ac9e4b8e2c57ffca3eafe34/src/trap.rs#L51-L67
请手动跳转
在调试过程中发现,HPM6360 无法在 SDRAM 的地址范围中执行原子指令,会产生存储/原子指令访问错误异常(Store/AMO Access Fault)。而 Linux 内核中有部分操作调用了原子指令(例如加锁等同步操作),在 CPU 不支持原子指令的情况下 Linux 内核无法运行。这里同样可以基于异常对原子指令进行模拟。
1、AMO 指令
在执行 AMO 指令时,会陷入存储/原子指令访问错误异常。同样的,我们需要从 mepc 指向的指令地址获取出错的原子指令,并进行模拟。
2、lr / sc
对于 lr (Load Reserved) 和 sc (Store Conditional) 指令的情况则有一些特殊。 lr 指令执行时会触发加载指令访问错误异常(Load Access Fault),但 sc 指令在执行时不会触发任何异常,rd 寄存器的结果直接返回 1 ,则就导致无法直接模拟该指令的执行。
这里我选择的解决办法是:在执行到 lr 指令时,先对 lr 指令进行模拟,然后查找后续内存地址中的 sc 指令,并将其替换为非法指令(实际使用的是 csrrw zero, time, zero)。这样在执行到原先 sc 指令的位置时就会触发非法指令异常。在异常处理函数中,判断是否是原先替换指令的地址,如果是,则还原被替换的 sc 指令,并模拟指令执行,随后异常退出至下一条指令继续执行;如果不是,则将异常委托给内核处理。
/dts-v1/;
/ {
#address-cells = <0x01>;
#size-cells = <0x01>;
compatible = "hpmicro,hpm6360";
model = "HPMicro HPM6360 Evaluate Kit";
aliases {
serial0 = &uart0;
};
chosen {
bootargs = "earlycon=sbi console=hvc0 ignore_loglevel rootwait root=/dev/mtdblock0";
stdout-path = "hvc0";
};
memory@40000000 {
device_type = "memory";
reg = <0x40000000 0x02000000>;
};
cpus {
#address-cells = <0x01>;
#size-cells = <0x00>;
timebase-frequency = <1000000>;
cpu@0 {
phandle = <0x01>;
device_type = "cpu";
reg = <0x00>;
status = "okay";
compatible = "riscv";
riscv,isa = "rv32imafdcp";
riscv,isa-base = "rv32i";
riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "zicsr",
"zifencei", "zihpm";
mmu-type = "riscv,none";
interrupt-controller {
#interrupt-cells = <0x01>;
interrupt-controller;
compatible = "riscv,cpu-intc";
};
};
};
soc {
#address-cells = <0x01>;
#size-cells = <0x01>;
compatible = "simple-bus";
ranges;
rom@80400000 {
compatible = "mtd-rom";
reg = <0x80400000 0xC00000>;
bank-width = <1>;
};
uart0: uart0@f0040000 {
compatible = "hpmicro,hpm6360-uart";
reg = <0xf0040000 0x40>;
clock-frequency = <24000000>;
status = "okay";
};
};
};
目前已经成功实现 Linux 6.10 内核启动引导,并传递设备树给内核。
coremark 跑分结果:
~ # coremark
2K performance run parameters for coremark.
CoreMark Size : 666
Total ticks : 13913
Total time (secs): 13.913000
Iterations/Sec : 1437.504492
Iterations : 20000
Compiler version : GCC13.3.0
Compiler flags : -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -O2 -g0 -fPIC -Wl,-elf2flt=-r -static -lrt
Memory location : Please put data memory location here
(e.g. code in flash, data on heap etc)
seedcrc : 0xe9f5
[0]crclist : 0xe714
[0]crcmatrix : 0x1fd7
[0]crcstate : 0x8e3a
[0]crcfinal : 0x382f
Correct operation validated. See readme.txt for run and reporting rules.
CoreMark 1.0 : 1437.504492 / GCC13.3.0 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -O2 -g0 -fPIC -Wl,-elf2flt=-r -static -lrt / Heap
ramspeed 测试结果,可以看出缓内的读取速度是非常快的,超出缓存范围后读取速度受限于 SDARM 的速度:
~ # ramspeed -b 2 -g 1 -m 1 -r
RAMspeed (GENERIC) v2.6.0 by Rhett M. Hollander and Paul V. Bolotoff, 2002-09
1Gb per pass mode
INTEGER & READING 1 Kb block: 1460.57 Mb/s
INTEGER & READING 2 Kb block: 1487.33 Mb/s
INTEGER & READING 4 Kb block: 1498.92 Mb/s
INTEGER & READING 8 Kb block: 1505.16 Mb/s
INTEGER & READING 16 Kb block: 1507.90 Mb/s
INTEGER & READING 32 Kb block: 1301.73 Mb/s
INTEGER & READING 64 Kb block: 116.71 Mb/s
INTEGER & READING 128 Kb block: 116.79 Mb/s
INTEGER & READING 256 Kb block: 116.81 Mb/s
INTEGER & READING 512 Kb block: 116.82 Mb/s
INTEGER & READING 1024 Kb block: 116.74 Mb/s
最后附上仓库地址,同时也提供了 pre-built 的系统镜像,欢迎各位开发者下载体验。
1、rustsbi-hpm:
https://github.com/rustsbi/rustsbi-hpm
2、linux:
https://github.com/hpm-rs/linux
3、buildroot:
https://github.com/hpm-rs/buildroot
HPMICRO
鸣谢
感谢华中科技大学洛佳同学 (https://github.com/luojia65)在本项目开发过程中提供的建议和支持!同时他也是 RustSBI 的作者。
感谢华中科技大学王振辰同学(https://github.com/Plucky923)完善了 riscv-decode 库对 RVA 指令集解码的支持!(https://github.com/fintelia/riscv-decode/pull/6)
参考
https://riscv.org/wp-content/uploads/2019/06/13.30-RISCV_OpenSBI_Deep_Dive_v5.pdf
https://github.com/rustsbi/rustsbi
https://github.com/fintelia/riscv-decode
以上内容来自先楫开发者的原创分享。
我们始终相信开发者共创的力量。先楫社区坚持开源共享、互惠互利,贴近每一个开发者,一步一个脚印,一点一滴积累,为成为更好的我们而不断努力。
心之所向,锐意进取,星辰大海,恣意成长。
MCU生态建设需要您的贡献与支持!欢迎广大爱好者和开发者踊跃投稿,供稿请联系sha.li@hpmicro.com。
“先楫半导体”(HPMicro)是一家致力于高性能嵌入式解决方案的半导体公司,总部位于上海,产品覆盖微控制器、微处理器和周边芯片,以及配套的开发工具和生态系统。公司成立于2020年6月,总部坐落于上海市浦东软件园区,并在天津、苏州、深圳和杭州均设立分公司。核心团队来自世界知名半导体公司管理团队,具有15年以上,超过20个SoC的丰富的研发及管理经验。先楫半导体以产品质量为本,所有产品均通过严格的可靠性测试。目前已经量产的高性能通用MCU产品包含HPM6700/6400、HPM6300、HPM6200、HPM5300、HPM6800及HPM6E00系列,性能领先国际同类产品并通过AEC-Q100认证。公司已完成ISO9001质量管理认证和ISO 26262/IEC61508功能安全管理体系双认证,全力服务中国乃至全球的工业、汽车和能源市场。更多信息,敬请访问 https://hpmicro.com/