QEMUのRaspberry Pi 3モデルにFreeRTOSを移植

QEMUのRaspberry Pi 3モデルにFreeRTOSを移植してみました。

QEMUのRaspberry Pi 3モデルでUARTとタイマ割り込みとタスクの使い方を習得したので、FreeRTOSを移植してみたら、できてしまいました。

ちょっとずつ機能を追加するのが大事です。

ソースコード: https://github.com/eggman/FreeRTOS-raspi3

FreeRTOSの移植は、機能を追加する順番が重要な気がします。（１回目の移植は、タイマ割り込みから始めて失敗しました。）

QEMUのバージョン : 2.12
FreeRTOSのバージョン : 10.0.1

# おおまかなFreeRTOS移植の順番
今回の経験から分かったFreeRTOS移植の順番です。

* FreeRTOSの最小限のソースコードをコピー
* アイドルタスク生成
* ソフトウェア割り込みハンドラ
* ユーザー定義タスクの起動

ここまではCPUコアに依存した部分です。
以降はチップ固有の割り込みコントローラとタイマ仕様が関連します

* 割り込みハンドラ
* タイマの設定とタイマ割り込み

ここまで動くとOSっぽく、複数タスクが動作するようになります。

以下にもうちょっと詳細を書いてみます。

# 移植の手順

ソースコードはFreeRTOS v10.0.1を使用。　
移植では、Src/portable/GCC/ARM_CA53_64_BIT と Demo/CORTEX_A53_64-bit_UltraScale_MPSoC/RTOSDemo_A53 からちょっとずつコードをコピーしました。

* start.S にブートするコードを書く。　El1にしてからmainを呼ぶ。
* Demo/main.c に UARTでhello world出力。
* FreeOSで最低限必要なtasks.c list.c heap1.cをコンパイル対象に含める。　
 * コンパイルエラーが出るので対策する
 * 必要なヘッダファイルをコピーする。
 * 最低限なConfigをDemo/FreeRTOSConfig.hに書く。
 * portable以下にport.c pormacro.hを作ってスタブ(空の関数)を書く

割り込みを使わずに自分で定義したタスクを動かすことを次のマイルストーンとした。

* main.c に、ユーザー定義タスクを作り、xTaskCreate()とvTaskStartScheduler()を呼ぶ。
* どこまで動くかをトレース出力して確認すると、vTaskStartScheduler()内でループが回って欲しいが、関数から戻ってしまう。
* port.cにpxPortInitialiseStack（）を追加する。タスク生成時のスタック初期化をする関数。
* portASM.Sに vPortRestoreTaskContext（）と、portRESTORE_CONTEXTマクロを追加する。(ただしGIC依存の優先度割り込みのコードは削除)
* port.cのxPortStartScheduler()からvPortRestoreTaskContext()を呼ぶ
* コンパイルエラーがでるので例外ベクターテーブルを追加する。

トレースをすると、アイドルタスクの起動まで動作しているが、SVC命令で止まっている。
SVC命令ということはソフトウェア割り込みのハンドラがまだないのが原因でした。

* portASM.s にFreeRTOS_SWI_HandlerとportSAVE_CONTEXTを追加する。（また優先度割り込みのコードは削除）

この時点で、ユーザー定義タスクが起動した。ただしタイマ割り込みがないので、その後カーネルに遷移しない。

ユーザー定義タスクが起動したので次に、ユーザー定義タスクでtickカウンタの値が表示できることを次のマイルストーンにした。

* main.cのユーザー定義タスクで、xTaskGetTickCount（）を表示するようにする。　現時点では0が表示
* タイマ割り込みが必要なので、まず割り込みハンドラをportASM.sに追加する。　(ただしGIC依存の優先度割り込みのコードは削除)
* タイマ割り込みの設定を FreeRTOS_tick_config.cに書く。　QEMUのRsapberry Pi 3モデルはペリフェラルタイマは動かないのでCortex-A53に内臓されているGeneric Timerを使う。
* タイマを10ms間隔に設定し、タイマハンドラからxTaskIncrementTick()を呼ぶ

こうすると、タイマ割り込み毎に、カーネルが実行されるようになり、tickのカウントアップが始まった。

こうやって文字で書いてもわかりづらいですね。コミットログを見てください。

# todo
* 実機で動かす。
* UART割り込みなどの他の割り込みに対応 済
* MMUを有効にしてキャッシュをONする。
* 別のボードへ移植

# はまりどころ
* 分かってしまえば簡単なのですが…
* Xilinxがaarch64に移植したFreeRTOSは、「ユーザー定義のタスクはEL1tで動き、カーネルはEL1hで動く」ということが分かるまでが大変だった。
* ユーザー定義タスクは、vPortRestoreTaskContext()または、FreeRTOS_SWI_Handler()から起動、再開する。この関数はEL1hで動作しており、ELR_EL1に起動したいタスク内の戻りアドレスを設定しERET命令を実行すると、EL1hに切り替わり、ELR_EL1で指定したアドレスから実行を再開する。
* という仕組みであると分かるまでが、大変でした。　EL0のスタックをを使っているのかなぁ、とは思っていたのですが、どこでELを変更しているかが分からなかった。


# 感想
* kernel側のコードを一切修正せずにportingできるのは、分割が正しくできていてすごいです。
* １回でもFreeRTOS移植を経験すれば、移植の手順は理解できる。
* 移植してもFreeRTOSの中身についてはあまり詳しくならい → 中身が分からなくても移植できる。

# 参考にした情報
* XilinxによるCortex-A53への移植 
 * https://github.com/coldnew/FreeRTOS-mirror/tree/master/FreeRTOS/Source/portable/GCC/ARM_CA53_64_BIT
 * https://github.com/coldnew/FreeRTOS-mirror/tree/master/FreeRTOS/Demo/CORTEX_A53_64-bit_UltraScale_MPSoC

* FreeRTOS のページにある Porting Guide
 * https://www.freertos.org/RTOS-Xilinx-UltraScale_MPSoC_64-bit.html
 * https://www.freertos.org/Using-FreeRTOS-on-Cortex-A-Embedded-Processors.html

* 以前の関連記事
 * [QEMUでRaspberry Pi 3のUARTをベアメタルで動かす](https://qiita.com/eggman/items/045bf5525fcf78209b79)
 * [aarch64でEL (Exception Level)を変更する](https://qiita.com/eggman/items/e500ecb33f8f126c0052)
 * [QEMUのRaspberry Pi 3モデルで割り込み](https://qiita.com/eggman/items/f0dc67a26f51f2facffa)
 * [QEMUのRaspbery Pi 3モデルでタスク切り替え](https://qiita.com/eggman/items/ddf879aa413cd511cb5d)