适用于HPM的RustSBI实现

2024-09-19
浏览量:
143

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

请手动跳转


RISC-V 上的 Linux 启动流程

在 ARM 架构中,通常 Linux 的启动流程为:

而在具有 S 态的 RISC-V 架构中,通常的启动流程为:

其中 BootROM、Loader 和 SBI Runtime 运行 M-mode(机器模式)下,具体的引导程序和 Linux 等操作系统内核运行在 S-mode(监管者模式)下,而用户进程运行在 U-mode(用户模式)中。我们看到 RISC-V 的启动流程中相比 ARM 多了一个 SBI Runtime,那么什么是 SBI?


SBI (Supervisor Binary Interface)

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

请手动跳转

SDRAM 区域原子指令的支持

在调试过程中发现,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/