RISC-V CPU加電執行流程

  • 2022 年 1 月 16 日
  • 筆記

市面上採用RISC-V架構的CPU很多,且沒有如X86那樣高度細節的標準,故採用說明文檔詳細的SiFive Freedom U540-C000晶片來做介紹(下面統一稱為FU540)。

FU540支援多種啟動方式,且由MSEL針腳控制。

 

在了解啟動流程之前,首先需要明確RISC-V的三種啟動模式

  1. M-mode(Machine Mode)
  2. S-mode(Supervisor Mode)
  3. U-mode(User Mode)

在系統加電啟動後會處於M-mode,有關啟動模式將在下文詳細講解。

 

通常,RISC-V啟動順序流程包含以下幾個階段:

RISC-V上游引導流程類似。ROM是ZSBL。FSBL載入器是SoC專用的。將由Coreboot和/或U-Boot SPL替代。運行時是OpenSBI。它提供運行時服務。U-Boot是OpenSBI中的有效負載。

     

  1. Zeroth Stage Boot Loader(ZSBL),安裝在板載的ROM中,處於M-mode
  2. First Stage Boot Loader(FSBL),brings up PPLs and DDR, 處於M-mode
  3. Berkeley Boot Loader(BBL),adds emulation for soft instructions,處於M-mode
  4. 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上運行系統做準備,可大概分為如下的這些任務:

  1. 通過配置晶片上的PLL將核心頻率變為1GHz
  2. 配置DDR PLL,PHY和DDR控制器
  3. 將GEM GXL TX PLL設置為123MHz並重置它
  4. 如果是外部PHY,重置它
  5. 從編號為2E54B353-1271-4842-806F-E436D6AF6985的GTP分區下載BBL
  6. 掃描OTP獲取的晶片序列號
  7. 將DTB(硬體設備樹)複製到DDR,填寫FSBL版本,記憶體大小和MAC地址
  8. 啟用16個中15道L2快取線路(這樣做將移除幾乎所有的L2 LIM快取)
  9. 跳轉到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)底層初始化:

  1. 判斷hart(Hardware Thread) id
  2. 程式碼重定位(判斷_load_start與_start函數是否一致)
  3. 清除除保存設備樹地址的暫存器的值
  4. 清除bss段
  5. 設置棧指針(預留棧空間)
  6. 讀取設備樹中的設備資訊
  7. 設備樹重定位,為U-Boot提供資訊
  8. 至此,底層初始化結束,執行sbi_init,進行正式的初始化程式

 

(2)設備初始化:

       首先判斷是通過S-mode還是M-mode啟動

  1. 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

  1. 內核鏡像仍然是uImage或者zImage
  2. 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
  • 安裝交叉編譯工具鏈
  • 完善工程目錄
    1. ├──build.sh ##編譯腳本 (編譯了s與main.c,通過link.ld鏈接)
    2.  
    3. ├──entry.s  ##入口函數(執行的入口函數,設置了堆的地址,並跳轉到main)
    4.  
    5. ├──fw_bin  ##可執行的韌體腳本
    6.  
    7. │├──fw_jump.elf  ##opensbi
    8.  
    9. │├──hello.elf  ##編譯完成的韌體
    10.  
    11. │└──run.sh  ##直接運行的腳本
    12.  
    13. ├──link.ld  ##鏈接文件(規定了程式的布局)
    14.  
    15. ├──main.c  ##主函數(調用openSBI,可以在S-mode訪問M-mode的串口輸出服務。)
    16.  
    17. ├──readme.md
    18.  
    19. └──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, //sifive.cdn.prismic.io/sifive/b5e7a29c-d3c2-44ea-85fb-acc1df282e21_FU540-C000-v1p3.pdf

[2] An Introduction to RISC-V Bootflow, //crvf2019.github.io/pdf/43.pdf

[3] The Standardized Boot flow for RISC-V Platforms, //crva.ict.ac.cn/crvs2020/index/slides/3-8.pdf

[4] RISC-V64 opensbi啟動過程, //cloud.tencent.com/developer/article/1758282

[5] uboot作用和功能, //blog.csdn.net/yilongdashi/article/details/87968572

[6] uboot啟動流程概述_關於riscv啟動部分思考, //blog.csdn.net/weixin_39530149/article/details/112312779

[7] DLL/PLL on a DRAM, //www.rambus.com/dllpll-on-a-dram/

[8] 記憶體為什麼要Training? 記憶體初始化程式碼為什麼是BIOS中的另類?, //zhuanlan.zhihu.com/p/107898009

[9] 設備樹的基本概念,//zhuanlan.zhihu.com/p/69188823

[10] opensbi下的riscv64裸機系列編程,//www.elecfans.com/d/1445926.html

[11] Rambus DDR3 PHY IP, //www.rambus.com/interface-ip/ddrn-phys/ddr3-phy/

[12] ARMv8虛擬化,//www.cnblogs.com/LoyenWang/p/13584020.html

Tags: