2015年6月28日 星期日

作業系統之前的程式 for rpi2 (0) - 點亮 led by c

這是我第三個平台的 bare-metal 程式, 第一個是 x86 在 legacy bios/uefi, 有 bios/uefi 擋路, 不算真的是 bare-metal。第二個是 stm32f4 - discovery, 有 jtag, 而且真的是從上電開始寫程式, 真的是 bare-metal 程式, 過癮, 可惜沒有 mmu, 不能練習 mmu。rpi2 是 cortex a7, 又是一個新平台, 我已經有了不少經驗, 該怎麼開始呢?

沒有 bcm2836 datasheet 實在很麻煩, 沒有 boot code source 也是一樣麻煩。好在有不少人也對 bare-metal 有興趣, 本篇文章大量參考: Step01 – Bare Metal Programming in C Pt1 (後文以 Pt1 稱呼), 能遇上同好真是開心, 我不用一人辛苦了。

不過沒有 jtag, 要除錯時就得靠「冥想」了, 而且也無法驗證程式跑的流程是不是和我想的一樣, 依照以前的經驗, 通常是不一樣 (我只是個普通程式員, 別難為我了)。

rpi2 開機流程:
Pt1 裡頭就寫到了, 不過還是大概提一下, 和一般的 arm 開發板從 arm core boot 並從 address 0 讀入第一個 arm 指令不同, rpi2 是從 gpu 開機, 也就是說某個地方放著執行 gpu 的程式碼, 它會去找 sd card 上的 bootcode.bin 然後載入並執行 bootcode.bin, bootcode.bin 再去找 sd card 上的 start.elf, 這兩個檔案都是 gpu 執行檔, 不是 arm machine code, 再來終於到 kernel.img, kernel7.img, 那個 7 就是給 rpi2 (arm cortex-A7) 用的, kernel.img 則是原來 rpi (ARM1176JZF) 用的, start.elf 會自動去判斷載入正確的 kernel*.img。

descent@NB-debian:boot$ file start.elf 
start.elf: ELF 32-bit LSB executable, Broadcom VideoCore III, version 1 (SYSV), statically linked, stripped

和 Pt1 提到的不同, 我使用的是 raspberrypi 官方提供的 toolchain

在 os 下的程式有 os 提供的 loader 來幫我們載入程式, rpi2 bare-metal 程式呢? 正常來說應該是 cpu 幫我們載入, 不過目前看來只能透過 star.elf 來載入我們的 bare-metal 程式, 把它想成 pc 的 bios/uefi 載入作業系統那樣的感覺, os kernel 也是 bare-metal 程式。

那 star.elf 從哪裡載入 0x8000? 所以你知道 linker script 要設定 0x8000 為 enter point, 為什麼? 說來複雜, 你照辦就是了, 除非你的程式可以 relocation, 搬到任意位址都可以正常執行。c.sh L3 就是在做這件事情。

c.sh
1 arm-linux-gnueabihf-gcc -O2 -mfpu=neon-vfpv4 -mfloat-abi=hard -march=armv7-a -mtune=cortex-a7 -nostartfiles -g -c v.s
2 arm-linux-gnueabihf-gcc -O2 -mfpu=neon-vfpv4 -mfloat-abi=hard -march=armv7-a -mtune=cortex-a7 -nostartfiles -g -c armc-02.c
3 arm-linux-gnueabihf-ld -Ttext 0x8000 v.o armc-02.o -o armc-02.elf
4 arm-linux-gnueabihf-objcopy armc-02.elf -O binary armc-02.bin
5 mv armc-02.bin kernel.img

該使用的 cpu 參數作者也一並列出來了, 我們不用辛苦的找這些資料。

我們的目的在點亮 OK led 燈, 就是那個綠色的 led, 這顆接在 gpio 47 和 rpi 1 不同哦, Pt1 提供的資訊。

以下的 gpio 資料是從 bcm2835 datasheet 節錄出來的, 咦 ... 我知道你的疑惑, 沒有 bcm2836 datasheet, bcm2835 勉強撐著用了, 玩 rpi2 真辛苦, 我真佩服 Pt1 作者是從哪裡得到這些資料。當然還有最重要的 physical address, 要不然就不知道要寫入哪個位址了。

bcm2835 datasheet gpio
 1 0x 7E20 0000  GPFSEL0  GPIO Function Select 0  32  R/W 
 2 0x 7E20 0000  GPFSEL0  GPIO Function Select 0  32  R/W 
 3 0x 7E20 0004  GPFSEL1  GPIO Function Select 1  32  R/W 
 4 0x 7E20 0008  GPFSEL2  GPIO Function Select 2  32  R/W 
 5 0x 7E20 000C  GPFSEL3  GPIO Function Select 3  32  R/W 
 6 0x 7E20 0010  GPFSEL4  GPIO Function Select 4  32  R/W 
 7 0x 7E20 0014  GPFSEL5  GPIO Function Select 5  32  R/W 
 8 
 9 0x 7E20 001C  GPSET0  GPIO Pin Output Set 0  32  W 
10 0x 7E20 0020  GPSET1  GPIO Pin Output Set 1  32  W 
11 
12 0x 7E20 0028  GPCLR0  GPIO Pin Output Clear 0  32  W 
13 0x 7E20 002C  GPCLR1  GPIO Pin Output Clear 1  32  W  

Table 6-6 - GPIO Alternate function select register 4
 1 Bit(s)  Field Name  Description
 2 31-30  ---  Reserved
 3 29-27  FSEL49  FSEL49 - Function Select 49
 4 000 = GPIO Pin 49 is an input
 5 001 = GPIO Pin 49 is an output
 6 100 = GPIO Pin 49 takes alternate functio
 7 101 = GPIO Pin 49 takes alternate functio
 8 110 = GPIO Pin 49 takes alternate functio
 9 111 = GPIO Pin 49 takes alternate functio
10 011 = GPIO Pin 49 takes alternate functio
11 010 = GPIO Pin 49 takes alternate functio
12 26-24  FSEL48  FSEL48 - Function Select 48
13 23-21  FSEL47  FSEL47 - Function Select 47
14 20-18  FSEL46  FSEL46 - Function Select 46
15 17-15  FSEL45  FSEL45 - Function Select 45
16 14-12  FSEL44  FSEL44 - Function Select 44
17 11-9  FSEL43  FSEL43 - Function Select 43
18 8-6  FSEL42  FSEL42 - Function Select 42
19 5-3  FSEL41  FSEL41 - Function Select 41
20 2-0  FSEL40  FSEL40 - Function Select 40

這個要怎麼看呢? 呼 ... 比 stm32f4 簡單多了。我們要做幾件事情:
把 gpio 47 設定為 output - Table 6-6 L13, 把 bit 21, 22, 23 設定為 001 就是 ouput
把 gpio 47 寫入 0, led 暗, gpio bit 47 在 GPCLR1 bit 15
把 gpio 47 寫入 1, led 亮, gpio bit 47 在 GPSET1 bit 15

就這樣, 不用設像 stm32f4 那麼複雜的屬性。

該文章的程式碼, 我在 rpi2 上測試, 只能亮 led, 滅 led 後就無法再亮 led, 我做了些修改。

https://github.com/descent/arm-tutorial-rpi/tree/master/part-1/armc-02

gpio[11] 就是 gpclr1 的位址
gpio[8] 就是 gpset1 的位址
針對這個位址寫入 bit 0/1 就是針對 gpio 47 寫入 bit 0/1。
bit 15 寫 1 到 gpio[8] 就是將gpio 47 寫入 1
bit 15 寫 1 到 gpio[11] 就是將gpio 47 寫入 0, bit 0 是 gpio 32, bit 1 是 gpio 33 ... bit 15 就是 gpio 47
很奇怪對吧, 不過人家這麼設計你就這麼用吧!

ok_led.c L30 很重要, 這是 GPIO base phyical address。

ok_led.c
 29 
 30     #define GPIO_BASE       0x3F200000UL
 31 
 86 
 87 
 88 #define DELAY_MAX 1000
 89 
 90 /** Main function - we'll never return from here */
 91 int notmain(void)
 92 {
 93 /** GPIO Register set */
 94 volatile unsigned int* gpio;
 95 
 96 /** Simple loop variable */
 97 volatile unsigned int i,j;
 98 
 99     /* Assign the address of the GPIO peripheral (Using ARM Physical Address) */
100     gpio = (unsigned int*)GPIO_BASE;
101 
102     /* Write 1 to the GPIO16 init nibble in the Function Select 1 GPIO
103        peripheral register to enable GPIO16 as an output */
104     gpio[4] &= (~(7 << 21));
105     gpio[4] |= (1 << 21);
106 
107     while(1)
108     {
109       // gpclr1
110         gpio[11] = (1 << 15); // led off
111         for(i = 0; i < DELAY_MAX; i++)
112           for(j = 0; j < 1000; j++) ;
113 
114       // gpset1
115         gpio[8] = (1 << 15); // led on
116         for(i = 0; i < DELAY_MAX; i++)
117           for(j = 0; j < 1000; j++) ;
118 
119       // gpclr1
120         gpio[11] = (1 << 15); // led off
121         for(i = 0; i < DELAY_MAX; i++)
122           for(j = 0; j < 1000; j++) ;
123 
124       // gpset1
125         gpio[8] = (1 << 15); // led on
126         while(1);
127 
137     }
138     return 0;
139 }

v.s
 1
 2 .globl _start
 3 _start:
 4     mov sp,#0x8000
 5     bl notmain
 6
 7 hang: b hang
 8 .globl PUT32
 9 PUT32:
10     str r1,[r0]
11     bx lr
12
13 .globl GET32
14 GET32:
15     ldr r0,[r0]
16     bx lr

ref: ok_led.c, v.s, makefile

這是參考 https://github.com/dwelch67/raspberrypi 改出來的。

由於我沒有 jtag, 我只能猜測是 stack 問題, 但是 armc-02.c 實在看不出來哪裡和 stack 有關。

ok_led.c 已經開始用 auto variable, 由於在 v.s 設定了 stack, 所以就放心的用 auto variable。編譯出來的 .bin 檔複製到 kernel.img 的那個分割區, 蓋掉 kernel.img, kernel7.img, 再插回 rpi2, 上電應該就可以看到 led 在閃爍。

我改變了閃燈的邏輯, 先暗 led, 再亮 led, 再暗 led, 然後永遠亮著, 以便確定有正常亮滅。綠色的那個 led 燈就是 ok led, 請參考以下影片。



有了 bootcode 之後的 bare-metal 程式簡單不少, 不用設定 dram controller 參數, 這樣才能存取 1g ram, 不用作 remap, 將 address 0 map to dram, 有些不過癮, 這些都被 bootcode 做完了, 又少學了不少東西。以這個例子來說, 從組合語言設定好 sp register 後, 就可以使用 stack, 再來便可以使用 c 語言了, 不過 bss 因為沒有作初始化, 會有不預期的行為, 但其實影響不大。也因為沒使用中斷, 所以也看不到中斷部份的程式碼, 慢慢來補上他們吧!

最後提一下有關 bootcode.bin, start.elf 我直接複製這些檔案到 fat partition 上, 似乎無法正確 work, 一定要用 2015-05-05-raspbian-wheezy.img dd 後產生的 partition fat 才能正確載入 kernel7.img, 真是奇怪。

Preparing Raspberry PI for JTAG Debugging, 我不確定能不能在 rpi2 上使用, 我們沒有 datasheet 記得嗎? 就算有, 我也看不懂, 硬體是我的弱點。

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。