市面上采用RISC-V架構的CPU很多,且沒有如X86那樣高度細節的標准,故采用說明文檔詳細的SiFive Freedom U540-C000芯片來做介紹(下面統一稱為FU540)。
FU540支持多種啟動方式,且由MSEL針腳控制。
在了解啟動流程之前,首先需要明確RISC-V的三種啟動模式
- M-mode(Machine Mode)
- S-mode(Supervisor Mode)
- U-mode(User Mode)
在系統加電啟動后會處於M-mode,有關啟動模式將在下文詳細講解。
通常,RISC-V啟動順序流程包含以下幾個階段:
RISC-V上游引導流程類似。ROM是ZSBL。FSBL加載器是SoC專用的。將由Coreboot和/或U-Boot SPL替代。運行時是OpenSBI。它提供運行時服務。U-Boot是OpenSBI中的有效負載。

- Zeroth Stage Boot Loader(ZSBL),安裝在板載的ROM中,處於M-mode
- First Stage Boot Loader(FSBL),brings up PPLs and DDR, 處於M-mode
- Berkeley Boot Loader(BBL),adds emulation for soft instructions,處於M-mode
- User Payload,包含軟件,如Linux,處於S-mode或U-mode
ZSBL和FSBL均依照MSEL(Mode Select)的設置(這些引腳要接到零或電源,表示高電平或低電平)加載下一階段的bootloader,具體參照附錄A。
1.1 復位向量(第一條指令)
Z待加電后,所有核心都跳轉到0x1004,在這個位置的內存包含如下內容:

Reset vector ROM
所有核心將依照復位地址和MSEL跳轉,詳細信息如下:

1.2 ZSBL(第0階段Bootloader)
處於M-mode的ZSBL保存在maskROM 中地址為0x1_0000的位置,它負責從GPT中加載更為復雜的FSBL(尋找編號為5B193300-FC78-40CD-8002-E86C45580B47的GPT分區)。通過先加載GPT的頭文件,然后一塊一塊(塊大小為512bytes)的順序地掃描GPT。加載過程結束后,FSBL被加載進地址為0x0800_0000的L2 LIM緩存中,隨后,將執行FSBL階段。
ZSBL通過MSEL陣腳的值來決定從哪里尋找FSBL所在的分區,詳細信息參照附錄B。
1.3 FSBL(第1階段Bootloader)
處於M-mode的FSBL從L2 LIM地址為0x0800_0000的緩存上執行,它負責為在DDR上運行系統做准備,可大概分為如下的這些任務:
- 通過配置芯片上的PLL將核心頻率變為1GHz
- 配置DDR PLL,PHY和DDR控制器
- 將GEM GXL TX PLL設置為123MHz並重置它
- 如果是外部PHY,重置它
- 從編號為2E54B353-1271-4842-806F-E436D6AF6985的GTP分區下載BBL
- 掃描OTP獲取的芯片序列號
- 將DTB(硬件設備樹)復制到DDR,填寫FSBL版本,內存大小和MAC地址
- 啟用16個中15道L2緩存線路(這樣做將移除幾乎所有的L2 LIM緩存)
- 跳轉到DDR上地址為0x8000_0000的位置
同樣,FSBL也參照MSEL的設置決定去哪里尋找BBL所在的分區,詳細信息參照附錄D。
1.4 BBL(第2階段的Bootloader)
處於M-mode的BBL從DDR上地址為0x8000_0000的位置執行。它負責為如SBI(Supervisor Binary Interface)等RISC-V需要的,但沒有被芯片本身實現的指令。在進行寫操作的時候,BBL通常包含一個嵌入的Linux內核負載,當SBI被初始化后,它將跳轉的Linux內核。
當ZSBL與FSBL都從QSPI(Quad SPI)加載下一階段的boot-loader。然而,其中使用的協議並不確定,具體由MSEL決定。
1.5 用戶負載(預示boot完成)
在這一階段,boot基本完成,待執行如轉載操作系統(S-mode)等任務后,運行用戶空間軟件程序(U-mode)。
1.6 什么是U-Boot
U-Boot屬於一種bootloader,簡單來說,其作用就是從flash中讀出內核,隨后加載在內存中,最終初始化並啟動操作系統內核。
具體來說,可以分為下述幾個方面:
1)U-Boot主要作用是用來啟動操作系統內核。體現在uboot最后一句代碼就是啟動內核。
2)U-Boot還要負責部署操作系統內核。體現在uboot最后的傳參。
3)U-Boot中還有操作Flash等板子上硬件的驅動。例如串口要打印,ping網絡成功,擦除、燒寫flash是否成功等。
4)U-Boot還得提供一個命令行界面供人來操作。很簡單,至少你能看到。
1.7 什么是SBI和OpenSBI(Open Supervisor Binary Interface)
SBI是一種接口規范,提供了RISC-V標准的S-mode OS與M-mode的SEE(Supervisor Execution Environment)的接口,一般來說操作系統不會直接接觸硬件,而是通過調用SBI的接口。
Open正如其名,是一個開源的SBI具體實現,通過遵守開源協議,任何用戶都可以修改和使用。
有了OpenSBI和U-Boot加持,大致啟動流程變為下圖:

OpenSBI的初始化流程如下:
(1)底層初始化:
- 判斷hart(Hardware Thread) id
- 代碼重定位(判斷_load_start與_start函數是否一致)
- 清除除保存設備樹地址的寄存器的值
- 清除bss段
- 設置棧指針(預留棧空間)
- 讀取設備樹中的設備信息
- 設備樹重定位,為U-Boot提供信息
- 至此,底層初始化結束,執行sbi_init,進行正式的初始化程序
(2)設備初始化:
首先判斷是通過S-mode還是M-mode啟動
- sbi_domain_init 初始化動態加載的鏡像的模塊
2.sbi_platform_early_init 平台的早期初始化
3.sbi_console_init 控制台初始化,從這里開始,就可以使用串口輸出了。
4.sbi_platform_irqchip_init irq中斷初始化
5.sbi_ipi_init 核間中斷初始化
6.sbi_tlb_init mmu的tlb表的初始化
7.sbi_timer_init timer初始化
8.sbi_hsm_prepare_next_jump 准備下一級的boot
(3)二級boot跳轉,如加載U-Boot或者Linux內核
1.8 詳細解讀RISC-V的啟動模式
M-mode(Machine Mode)
M-mode是最底層的模式,也是每一個標准 RISC-V 處理器必須要實現的模式,它擁有最高權限,這意味着他將使用物理地址直接運行在硬件上。當cpu加電后,將處於M-mode。
機器模式具備攔截和處理異常的能力,並且可以訪問所有其他模式下的控制狀態寄存器CSR (Control Status Register)。
下表是對 RISC-V 機器模式下的控制狀態寄存器的匯總:
| 寄存器 |
功能簡要說明 |
| Machine ISA Register (misa) |
用於指示當前處理器所支持的指令集模塊。 |
| Machine Vendor ID Register (mvendorid) |
用於指示供應商 ID 的寄存器 |
| Machine Architecture ID Register (marchid) |
處理器核架構編號 |
| Machine Implementation ID Register (mimpid) |
提供了處理器實現版本的唯一編碼 |
| Hart ID Register (mhartid) |
運行當前代碼的硬件線程(hart)的 ID |
| Machine Status Registers (mstatus and mstatush) |
機器模式下的狀態寄存器 |
| Machine Trap-Vector Base-Address Register (mtvec) |
配置發生異常后的入口地址 |
| Machine Trap Delegation Registers (medeleg and mideleg) |
機器異常委托寄存器和機器中斷委托寄存器 |
| Machine Interrupt Registers (mip and mie) |
控制中斷使能與查詢中斷的狀態的寄存器 |
| Hardware Performance Monitor |
硬件性能監控的CSR寄存器,用於監控CPU的內部運行情況。 |
| Machine Counter-Enable Register (mcounteren) |
僅控制計數器的可訪問性。讀或寫這個寄存器的行為不會影響底層計數器。 |
| Machine Counter-Inhibit CSR (mcountinhibit) |
控制計數器是否遞增 |
| Machine Scratch Register (mscratch) |
用於機器模式下臨時性的保存某些數據。 |
| Machine Exception Program Counter (mepc) |
用於保存進入異常前的指令的 PC 值。 |
| Machine Cause Register (mcause) |
用於保存進入異常的原因。 |
| Machine Trap Value Register (mtval) |
用於保存進入異常時的地址或者指令 |
S-mode(Supervisor Mode)
Supervisor Mode(S-mode),如下圖的RISC-V Privilege Levels中所示,是Machine Mode(M-mode)上兩層的模式。這意味着操作系統調用OpenSBI的抽象來完成非常底端的操作。這樣會使在開發板上開發一個操作系統變得容易一些。在此模式下,運行如操作系統內核的軟件,並通過OpenSBI接口調用底層設備。

RISC-V Privilege Levels
上圖我們看到介於S-mode與M-mode之間是保留的,這里正是為SBI接口留有的位置,通常來說,是下圖的OpenSBI接口實現。

U-mode(User-Mode)
在此模式下,處於用戶空間,運行用戶程序。
事實上,RISC-V芯片的啟動流程還在不斷地快速演變,進化中,下圖給出了演變的歷程。

通過遵守開源協議,任何用戶都可以修改和使用OpenSBI。面對這些復雜的情況,需要制定一系列規則來規范化 RISC-V 的啟動規則。下圖給出了啟動規范標准的演變歷程。

ARM架構中的Hypervisor與OpenSBI的對比
類比ARM64芯片,可以一一對應出RISC-V的啟動模式,但實際上在RISC-V芯片中,並不存在Hypervisor模式來承上啟下,反之,使用的是上文所提到的SBI(Supervisor Binary Interface)規范下的接口,一般是OpenSBI。


(ARM) (RISC-V )
在ARM架構中,Hypervisor層承擔了虛擬化的作用,承擔了如內存管理、設備模擬、設備分配、異常處理、指令捕獲、虛擬異常管理、中斷控制器管理、調度、上下文切換、內存轉換、多個虛擬地址空間管理等非常多的功能。
相比之下,SBI在RISC-V架構中充當了BIOS和在操作系統運行時為上層提供底層抽象的作用,功能較少。不過SBI也在不斷發展中,可能在將來SBI會去承擔虛擬化的功能。
1.9 關鍵名詞解釋
1.9.1 DDR 中的DLL/PLL
簡單來說,DDL(Delay Locked Loop)和PLL(Phase Locked Loop)是一種維持信號穩定的電路,使內存能夠更加高效地傳輸數據。
下圖給出了無DLL/PLL與有DLL/PLL的情況

DLL/PLL通過連續地比較兩個信號的關系並提供反饋來保持他們之間地固定關系。
現代系統使用同步通信來實現往返於存儲系統中DRAM的高數據傳輸速率。 同步通信的系統使用時鍾信號作為時序參考,因此可以以與該參考已知的關系來發送和接收數據。 維持這種關系的困難在於工藝,電壓和溫度變化會改變時鍾和數據信號之間的時序關系,從而導致時序裕量(timing margin)降低。 隨着信號速度的增加,該問題變得更加嚴重,從而限制了系統以更高的速度通信數據的能力。
DLL 用於維護時鍾信號和輸出數據信號之間的正時關系。DLL 的一個關鍵要素是相位探測器,它檢測時鍾和輸出數據之間的相位差異。相位探測器檢測此相差,並通過低通濾波器向可變延遲線發送控制信息,該延遲線可調整內部時鍾的正時以保持所需的正時關系(PLL 使用電壓控制振盪器來調整此正時關系)。維持這兩個信號之間的相位關系的一個困難是,向相位檢測器提供反饋的循環必須考慮到輸出邏輯和輸出驅動程序的正時特征。這一點很重要,因為它估計了時鍾和輸出驅動因素驅動的數據之間的相位差異。為了實現這一目標,模仿輸出邏輯和輸出驅動程序的行為特征的電路入到此反饋回路中,以模擬當過程、電壓和溫度變化時的時間延遲和行為變化。通過DLL和PLL以這種方式保持時鍾和輸出數據之間的正時關系,可以提高計時間距,並解決了提高信號速度的重要限制。
PLL 類似於 DL,但也可用於拆分或乘以外部系統時鍾頻率,用於芯片的其他部分。PLL 可用於向 DRAM 的核心提供較慢的時鍾頻率,而接口則以更高的時鍾頻率運行。以這種方式使用的PLL使DRAM核心預裝,允許DRAM核心以較慢的頻率運行(提高DRAM產量),同時允許接口以更高的速度運行,以提高系統性能。

1.9.2 DDR中PHY
DDR PHY是指芯片存儲器的高速接口物理層,是SOC和外界存儲之間數據地址傳輸的一個重要通道。它主要基於時鍾上下沿分別采集數據來達到提高傳輸速率的目的。
內存子系統一般理解包含下面這些部分:

在BIOS的代碼中除了按照硬件手冊和用戶的選擇填寄存器外,還存在着一部分內存初始化代碼MRC(Memory Reference Code),也叫Memory Training代碼,主要用來調整時序和提高信號完整性。內存IO的頻率十分高,微小的誤差也會被放大,Memory Training通過 “訓練” 得到一組對齊、補償和參考電壓參數,來平衡和對沖線路的差異和信號的噪聲。
PHY是物理接口的部分(其所處結構大致為DDR controller接口ßàPHYßà外部DDR接口),它包括了內存的Training所需要的物理層支持。因此它的主要功能是作為一個硬件接口,處理時序,將信號以一個較好的時序發出。
1.9.3 GEM GXL TX PLL中的PLL
(1)TX:內置時鍾
(2)PLL:(Phase Locked Loop)相鎖環
1.9.4 DTB (硬件設備樹)
硬件設備樹是一種描述硬件資源的數據結構,它通過bootloader將硬件資源傳給內核,使得內核和硬件資源描述相對獨立。
直觀來說,在Windows中,DTB即為下圖中 “我的電腦” 屬性里的 “設備管理器”。

設備樹由一系列被命名的節點及其屬性構成,一般來說,其包含的信息如下:
- CPU的數量和類別
- 內存基地址和大小
- 總線和橋
- 外設連接
- 中斷控制器和中斷使用情況
- GPIO控制器和GPIO使用情況
它基本上就是畫一棵電路板上CPU、總線、設備組成的樹,Bootloader會將這棵樹傳遞給內核,然后內核可以識別這棵樹,並根據它展開出Linux內核中的相關設備。
設備樹的組成包括三部分,DTC(Device Tree Compiler),DTS(Device Tree Source)和DTB(Device Tree Blob)。
DTS: dts文件是對Device Tree的描述,放置在內核的/arch/arm/boot/dts目錄。一個*.dts文件對應一個ARM的machine。dts文件描述了一個板子的硬件資源。以前寫在mach-xxx文件中的內容被轉成了dts文件。
DTC: DTC為編譯工具,它可以將.dts文件編譯成.dtb文件。
DTB: DTC編譯*.dts生成的二進制文件(.dtb),bootloader在引導內核時,會預先讀取.dtb到內存,進而由內核解析。

DTS與DTB可以通過DTC與FDTDUMP進行編譯與反編譯。
1.9.4.1 使用U-Boot借助DTB啟動內核
現今的內核版本使用了Device Tree:
- 內核不再包含對硬件的描述,它以二進制的形式單獨存儲在另外的位置
2)Bootloader需要加載兩個二進制文件:內核鏡像和DTB
- 內核鏡像仍然是uImage或者zImage
- DTB文件在arch/arm/boot/dts中,每一個board對應一個.dts文件
3)Bootloader通過r2寄存器來傳遞DTB地址,通過修改DTB可以修改內存信息,kernel command line,以及潛在的其它信息
啟動過程總的歸納為:
- kernel入口處獲取到uboot傳過來的.dtb鏡像的基地址
- 通過early_init_dt_scan()函數來獲取kernel初始化時需要的bootargs和cmd_line等系統引導參數
- 調用unflatten_device_tree函數來解析dtb文件,構建一個由device_node結構連接而成的單向鏈表,並使用全局變量of_allnodes保存這個鏈表的頭指針
- 內核獲取of_allnodes鏈表信息來初始化內核其他子系統、設備等
1.9.5 FSBL中的L2 LIM相關步驟解釋
Enable 15 of the 16 L2 ways (this removes almost all of the L2 LIM memory)
Ways為L2緩存hash時允許沖突的長度,此處FSBL允許L2緩存擁有長度為15的hash沖突長度。
1.9.6 OpenSBI的作用與具體案例(openSBI下的RISC-V裸機實現串口輸出)
OpenSBI不僅起到了引導啟動的作用,還提供了M-mode轉換到S-mode的實現,同時,在S-mode下的操作系統內核可以通過這一實現訪問M-mode的服務。
隨后會介紹如何利用該服務在控制台輸出Hello。
准備工作:
- 編譯openSBI
- 安裝qemu
- 安裝交叉編譯工具鏈
- 完善工程目錄
- ├──build.sh ##編譯腳本 (編譯了s與main.c,通過link.ld鏈接)
- ├──entry.s ##入口函數(執行的入口函數,設置了堆的地址,並跳轉到main)
- ├──fw_bin ##可執行的固件腳本
- │├──fw_jump.elf ##opensbi
- │├──hello.elf ##編譯完成的固件
- │└──run.sh ##直接運行的腳本
- ├──link.ld ##鏈接文件(規定了程序的布局)
- ├──main.c ##主函數(調用openSBI,可以在S-mode訪問M-mode的串口輸出服務。)
- ├──readme.md
- └──sbi.h##sbi調用api
(5)在此文件夾下輸入./run.sh即能在控制台看到輸出的Hello
在進行M-mode服務訪問時,采用ECALL進行SYSCALL
在SYSCALL過程中,ECALL會使用a0 與a7寄存器,分別保存調用參數和調用號。
在RISC-V架構中,S-mode不直接參與時鍾中斷和軟件中斷,而是使用ECALL來請求M-mode設置定時器或在代理處理器中斷。
openSBI提供的服務接口如下表(串行輸出至控制台僅使用了sbi_console_putchar接口)

Appendix:
A. 依照MSEL,ZSBL和FSBL的下一階段啟動媒介


B. 被ZSBL加載的FSBL地址

C. RAMBUS DDR3 PHY結構圖

D. 被FSBL加載的BBL的位置

Reference:
[1] SiFive FU540-C000 Manual, https://sifive.cdn.prismic.io/sifive/b5e7a29c-d3c2-44ea-85fb-acc1df282e21_FU540-C000-v1p3.pdf
[2] An Introduction to RISC-V Bootflow, https://crvf2019.github.io/pdf/43.pdf
[3] The Standardized Boot flow for RISC-V Platforms, http://crva.ict.ac.cn/crvs2020/index/slides/3-8.pdf
[4] RISC-V64 opensbi啟動過程, https://cloud.tencent.com/developer/article/1758282
[5] uboot作用和功能, https://blog.csdn.net/yilongdashi/article/details/87968572
[6] uboot啟動流程概述_關於riscv啟動部分思考, https://blog.csdn.net/weixin_39530149/article/details/112312779
[7] DLL/PLL on a DRAM, https://www.rambus.com/dllpll-on-a-dram/
[8] 內存為什么要Training? 內存初始化代碼為什么是BIOS中的另類?, https://zhuanlan.zhihu.com/p/107898009
[9] 設備樹的基本概念,https://zhuanlan.zhihu.com/p/69188823
[10] opensbi下的riscv64裸機系列編程,http://www.elecfans.com/d/1445926.html
[11] Rambus DDR3 PHY IP, https://www.rambus.com/interface-ip/ddrn-phys/ddr3-phy/
[12] ARMv8虛擬化,https://www.cnblogs.com/LoyenWang/p/13584020.html
