HPMicro 的 MCU 一直以高性能著稱,之前也一直有想在 HPM 的 MCU 上運行 Linux 的想法。直到看見 Linux 6.10 中支持了 RISC-V 架構在 S-mode 中運行 nommu 內核*,才下定決心開始在 HPM6360 上折騰 nommu Linux。
劃線部分鏈接為:
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
+關注
關注
146文章
17347瀏覽量
352708 -
Linux
+關注
關注
87文章
11350瀏覽量
210446 -
HPM
+關注
關注
1文章
38瀏覽量
7789
發布評論請先 登錄
相關推薦
評論