因為 MicroC/OS-II 看起來很不協調,之後將一律使用 ucosii 代替。原始程式碼可以直接到 micrium 的網站下載,我以目前可下載的版本 2.86,再搭配書上 Ch8 Porting uC/OS-II 把移植時需要的函式補齊。
之前曾將 Prex 移植到 GTA02 上,由於 Prex 已經支援 ARM arch,所以當時的工作主要是補上對 device(board) GTA02 的支援,例如 uart driver, timer isr 等,對移植 OS 來說,對於 arch 的支援是指甚麼呢,OS 負責處理的工作有 task management、memory management 、schedule 等,以排程來說,系統同時間有多個 tasks 等著使用 cpu ,OS 選擇優先權最高的 task 執行,這個動作會需要 context-switch,由於不同 arch 有不同的 register 因此這個部份是屬於 arch dependent
另外還有 critical section 的保護,保護 critical section 最簡單的方法就是透過 Disable Interrupt 的方式,而 interrupt 的控制也是和 arch 相關,對 ARM arch 我們可以透過修改 register CPSR 的 I、F bit ,而 Ch8 講的也就是將上述的兩件事補齊。
在一頭鑽進移植的工作之前,先看一下 release note,ucosii 的 release note 寫的很好,很清楚告訴我們新增了哪些檔案,有哪些新的 define、struct,或是哪些函式名稱改變了,這個部份的確幫助我許多。
我把移植 ucosii 分成三個階段,分別是 compile 階段、follow spec 階段、debug 階段。
compile 階段要達到的目的很簡單,先寫個 makefile 讓下載回來的 ucosii 程式碼可以編譯,不會有錯誤訊息,compiler 跟你說少甚麼就補甚麼,少甚麼函式就先補個 dummy function,這邊需要的只是基本判讀錯誤訊息的能力
幾個 dummy function 的例子
一開始就告訴我們需要 app_cfg.h、os_cfg.h、os_cpu.h,用 touch 把這三個檔案生出來,檔案內容都是空的,重點只是先要可以編譯而已,見招拆招。
Recompile,發現這樣的錯誤,想都不用想,我們一定是少了 typedef,由於不同 cpu arch 的 word length 是不一樣的,例如,你不能保證每個 arch 上的 long 都是 4 bytes 吧,所以透過這樣的方式以達到 portable 的目的。
其中 OS_STK 是 stack 的單位,在作 context-switch 時我們需要將 registers 存入 stack 裡,由於 ARM 是 32-bit microprocessor,所以在 os_cpu.h 裡,我補上以下的 typedef:
前面說過,critical section 的保護是 arch dependent 的,所以在 ucosii 的程式碼裏面到處可以看見 OS_ENTER_CRITICAL() 及 OS_EXIT_CRITICAL(),但將這兩個函式定義交給我們實作,一樣先寫兩個 dummy function,記住我們的目的只是先讓編譯的階段可以順利達成,函式的真正實作則是放在 follow-spec 階段
這邊缺少的 function 有 OSDebugInit() OSInitHookBegin() OSInitHookEnd() OSTaskCreateHook() OSTaskDelHook() OSTaskIdleHook() OSTaskStatHook() OSTaskStkInit() OSTaskSwHook() OSTCBInitHook() OSTimeTickHook() OSCtxSw() OSIntCtxSw() OSStartHighRdy() OSTickISR()
至於是要定義在 .h .c 或 .s 檔裡,參照 ch8 porting guide,可以清楚知道這些 dummy function 分別要定義在哪些檔案裡
最後,需要注意的是下面的錯誤訊息,因為 ARM 並不支援除法指令(只有加、減、乘),所以要透過程式支援 modular operator 或 division operator,如果使用的 toolchain 已經有支援,就直接拿來使用
在 makefile 裡,對應的設定如下:
到這裡已經可以正常編譯,但屬於半殘,編譯出來的 Bin 載入到記憶體裡執行理論上是不會動的。所以在 follow-spec 階段,我們需要參考 ARM manual 及 ch8 porting guide 把原本的 dummy function 補上真正的程式碼
TBD...
經過前面兩個階段,試著將 Binary 載入到記憶體裡跑跑看,用來 debug 的工具不是 gdb 而是透過 JTAG,我習慣在編譯時同時使用 objdump 把 memory mapping dump 到檔案,debug 時,透過 JTAG 對「位址」設斷點,要對哪些位址設 BP 則是查看 dump 檔。同時準備筆和紙,debug 時,不像一般對應用程式的除錯,這個階段我們會很常需要紀錄一些位址還有 registers 的值,例如 IRQ 或 SVC mode 下的 SP 值是否為有效記憶體位址,若是無效位址,當 interrupt 發生時,也許連 ISR 都無法被正常執行。
由於使用 JTAG 可以讀取 registers 的值、對 memory 讀寫,在 debug 時,只要有耐心,都可以找到問題所在,找到問題後,再往回去找問題的發生原因。
節錄 dump 檔的部分內容
以下看幾個例子,稍微說明如何使用 JTAG 找到 bug
我們在 0x300090a4 的地方設立中斷點,發現 ucosii 執行到 0x300090a4 就無法繼續執行接下去的程式碼,很明顯在該位址附近的指令應該是造成了 exception,所以每次執行到就會回到 vector table 處重新執行
將 memory 0x300090a4 附近的指令 dump 出來,我們看到了有對 memory 存取的 ldr 及 str 指令,但是現在還不能確定原因是甚麼
對照程式碼,這邊一開始會修改全域變數 OSRunning 的值
最後,我們看一下 objdump,發現 OSRunning 的宣告是 byte,我卻把它當作一個 word 用 str 指令去寫資料,很明顯這邊就會出大問題了,事實上也的確是 root cause
ucosii 卡在 0x300019d8 和 0x300019f4 間,我在這兩個位址設立中斷點,正常來說應該只會跑過一次,不過從下面 debug 訊息,可以看到不只一次被執行到,我忘記這邊是甚麼原因造成的 ;)
產生 IRQ 中斷後,就葛屁啦,為甚麼知道是產生 IRQ 中斷呢,最簡單的方式是觀察暫存器 CPSR 的值
檢查一下在 IRQ mode 時的 SP(r13) 值,很誇張的錯誤,我忘了初始化 SP 在 IRQ mode 的值,補上相關的程式碼後,就沒問題了
我使用 timer4 當作是 OS 的 tick timer,下面這個錯誤是當 timer 觸發幾次後,就會開始印出一堆垃圾出來,這個 bug 比較難找,但是只要有耐心的單步追蹤,最後還是會發現錯誤點。
總之,因為原先 critical section 實作會造成 nested interrupt,但是 irq handler 並不支援巢狀中斷,在中斷還沒處理完又發生中斷,不斷的中斷下去,把分配給 irq 的 stack 吃光了,所以重新修改 critical section 的保護方式即可
將 產生的 Bin 燒錄到 DMA-2440 的 nand flash 上,開機後 bootloader 自動載入到記憶體執行,拍的很模糊,可以看到有3個 tasks(task0, task1, supertask) 不斷地在 context-switch
當 porting 一個 embedded os 時,除了 arch dependent 的部份外,還要補上 board(soc) support 的程式碼,例如 GPIO 初始化、記憶體初始化、那些拉裡拉雜有的沒的,這邊我用偷吃步的方式,因為 bootloader 會將 ucosii 載入到記憶體執行,所以我假設剛剛說的那些東西都在 bootloader 作好了,所以我就省略一些初使化的程式碼,讓事情簡單點。
Download 程式碼...