之前 blog 文章有提到 JOS 作業系統初始化流程,包含 x86_64 架構下 muticore 喚醒過程等。而剛好前陣子入手採用 ARM Cortex-M0+ CPU 的 Raspberry Pi Pico 開發板,因此就研究了一下 Raspberry Pi 為 Pico 開發板所撰寫的 bootrom、memory map 和 layout 等。
(以下的 boot flow 是指開發板上電後,硬體完成 hardware controlled boot sequence ,從 processor controlled boot sequence 開始,更多說明可以參考 datasheet:2.7. Boot Sequence)
Boot Sequence
pico-bootrom 是 Raspberry Pi Pico 在出廠時燒在 ROM 上的 boot 程式,大小為 16 KB,它最主要目的在於提供 processor 初始化行為和提供不同的 boot mode 切換。
Boot Flow Diagram
Memory Map & Memory Layout
參考 datasheet 所提供的 flow diagram,以及根據 source code 所整理的 memory map,搭配 source code 來分析 boot 和 application 執行流程。
Raspberry Pi Pico 有兩個 processor,一開始兩個 processor 都會去讀取位於 0x00000000
ROM 的起始位置以取得 vector table。vector table 有兩個重要資訊:initial stack pointer (MSP) 和 reset handler,在 bootrom.ld 可以看到 MSP 被設置在 SRAM address end - 256 bytes 的位置。
1/* Leave room above the stack for stage 2 load, so that stage 2
2 can image SRAM from its beginning */
3 _stacktop = ORIGIN(SRAM) + LENGTH(SRAM) - 256;
再看 bootrom_rt0.S 找到 vector table 的內容:
1// bootrom_rt0.S
2.cpu cortex-m0
3.thumb
4.section .vectors
5.balign 2
6
7.global __vectors
8__vectors:
9.word _stacktop // MSP
10.word _start // Reset
11.word _nmi // NMI
12.word _dead // HardFault
processor 會設置 stack pointer,並且執行在 reset handler address 的 entry point _start
。
而在 boot 階段,由於僅能使用一個 processor 來完成啟動流程,因此會先判斷當前執行的 processor 是 CPU-0 還是 CPU-1:
1check_core:
2 ldr r0, =SIO_BASE // SIO_BASE (Single-cycle IO block) see. 2.3.1. SIO
3 ldr r1, [r0, #SIO_CPUID_OFFSET]
4 cmp r1, #0
5 bne wait_for_vector
6 bl _nmi
bootrom 是透過讀取 SIO_BASE + SIO_CPUID_OFFSET
SIO CPUID register (2.3.1.7. List of Registers) 的值來判斷當前 processor 編號,如果是 1 的話則將此 processor 直接切換成 sleep 模式。
接下來,檢查 Rescue DP flag 和 watchdog register 是否有設置,如有設置的話則需進行額外處理。由於使用者在正常流程中比較少會需要處理這兩個狀況,因此就省略不說,詳細內容可以參考 2.3.4.2. Rescue DP、2.8.1.1. Watchdog Boot。
最後就是選擇進入 flash boot 或是 USB device boot。flash boot 讓開發板正常執行已放在 flash 的使用者程式;而如果要將程式透過 USB massive storage 模式放入到 flash,則是 USB device boot。
選擇的方式是透過讀取 flash(QSPI interface) CS(Chip Select) pin 的電位,如果高電位是 flash boot ,反之低電位是 USB device boot。在 Raspberry Pi Pico 開發板上可以看到一個 BOOTSEL
按鈕,用來控制 flash CS pin 的電位,因此當使用者按下按鈕不放時,此時 flash CS pin 為 0,進入 USB device boot 模式。
1uint32_t sum = 0;
2for (int i = 0; i < 9; ++i) {
3 delay(1 * ROSC_MHZ_MAX / 3);
4 sum += (sio_hw->gpio_hi_in >> 1) & 1u;
5}
6
7if (sum >= 5)
8 _flash_boot();
9
10// note this never returns (and is marked as such)
11_usb_boot(0, 0);
為了確保資料讀取的正確性,會連續讀取 flash CS pin 數次,再根據 sum 決定最後電位值是 1 或是 0。
這邊要注意的是,當跳轉至 _flash_boot
,是不會再跳轉回來並且繼續執行 _usb_boot
,其原因在於:
1static void _flash_boot() {
2
3 // more code ...
4
5 asm volatile (
6 "add %0, #1\n"
7 "mov lr, %1\n"
8 "bx %0\n"
9 : "+r" (boot2_entry) : "l" (boot2_exit) :
10 );
11 __builtin_unreachable();
12}
在 asm code 中執行 bx instruction 跳轉至 boot 2 entry point,意味著無透過 link register 儲存當前 PC。
另外要注意的 asm code 中: "add %0, #1\n"
,之所以會 +1 的原因是:
For BX and BLX, bit[0] of Rm must be 1 for correct execution. (Cortex-M0+ Devices Generic User Guide)
跳轉目標位置的 bit[0] 在 ARM B instruction 是代表切換 ARM / Thumb state,而 cortex-M0+ 實作 The ARMv6-M Thumb® instruction set,所以要設為 1。
References
結語
不得不說 Raspberry Pi 提供非常完整的文件和教學內容,不論是初學者或是進階者都能夠很快上手。而對於想要細部了解其運作流程的開發者來說,只要將 RP2040 Datasheet 搭配 source code 一起看,就可以很快地了解整個運作流程。