RISC-V CPU加電執行流程

SirDouchebag發表於2022-01-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

ZSBLFSBL均依照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 CompilerDTS(Device Tree SourceDTB(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 ##編譯指令碼 (編譯了smain.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, 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

相關文章