ベアメタルRaspberry Piの例外処理を書いてみた

timer-irq.png

前回調べた情報に基づいて、ラズパイのベアメタルでの割り込み処理を試してみました。
コードは以下に置いてあります。

bare_matal_rpi_zero/timer-irq at master · boochow/bare_matal_rpi_zero

行った内容自体は

・タイマで0.5秒ごとに割り込みをかける
・グローバル変数(整数)を割り込みハンドラから変更
・メインルーチンは無限ループでグローバル変数を監視。値が変化したらシリアルポートに出力

というシンプルなものです。

割り込みベクタは0番地ではなく、スタティック変数を指定することにしました。
データ構造は以下のようにしています。

割り込みベクタの構造は前回紹介したインターフェース2017年2月号や、dwelch67さんのサンプルと同じで、ジャンプ命令の直後にアドレステーブルを置いています。
アドレステーブルは実際には関数のポインタ(exception_hander_t型として定義)です。

重要なのは

__attribute__((aligned(32)))

の部分で、割り込みベクタは32バイト境界に配置する必要があります。
最初、4バイト境界にしていて、想定と違う割り込みハンドラが呼ばれてハマリました。
考えてみれば、割り込みベクタは32バイトでセットなので、32バイト境界でも不思議は無いですね。

gccでは割り込みハンドラを書くための__attribute__が使えますので、割り込みハンドラはCのみで書いています。
ただ、なぜか__attribute__((interrupt(“FIQ”)))で関数を書くとコンパイラの内部エラーになったので、FIQのハンドラはFIQではなくIRQを使っています。
ちなみにエラーはこんな内容でした。

main.c: In function 'fiq_handler':
main.c:189:1: error: insn does not satisfy its constraints:
}
^
(insn/f 13 12 14 (set (reg/f:SI 13 sp)
(plus:SI (reg/f:SI 11 fp)
(const_int 4 [0x4]))) main.c:189 4 {*arm_addsi3}
(expr_list:REG_CFA_ADJUST_CFA (set (reg/f:SI 13 sp)
(plus:SI (reg/f:SI 11 fp)
(const_int 4 [0x4])))
(nil)))
main.c:189:1: internal compiler error: in extract_constrain_insn, at recog.c:2246
Please submit a full bug report,
with preprocessed source if appropriate.
See  for instructions.

スタティック変数として定義した割り込みベクタを使用するには、CP15のVBARレジスタに設定しますが、これは前回のmrvnさんのset_vbar()をそのまま借用しています。

タイマの設定についてはdwelch67さんのものをほぼそのまま使用しています。
このタイマの解説は、毎度おなじみのマニュアル「BCM2835 ARM Peripherals」の196ページに掲載されています。簡単に要約すると、

・値が減算されていくカウンタである
・値がゼロになったとき、割り込みをかけることができる
・そのあと、また初期値から減算していく

というシンプルなタイマです。

クロックソースは250MHzですが、それをプリディバイダで分周することができます。
今回はプリディバイダを0xF9(=249)にしているので、1MHzクロックになります。
カウンタの初期値は(500,000 – 1)にしているので、50万カウント(=0.5秒)ごとに割り込みがかかります。
割り込み処理後、処理が終わったことを示すために、ARM_TIMER_CLIをクリアする必要があります。

割り込みを有効にするのは、ちょっと面倒です。
CPUのレジスタとメモリマップドレジスタの両方を設定してやる必要があります。

CPUでは、CPSRレジスタのbit 7がIRQ、bit 6がFIQのイネーブル・ビットになります。
これが割り込み全体の有効・無効の制御になります。

Raspberry Piでは、これに加えて割り込み制御レジスタで割り込み要因(タイマー割り込みを許可するか等)ごとの有効・無効設定を行う必要があります。
マニュアルでは112ページあたりから解説されています。

割り込み制御レジスタは6つあります。
割り込みは全てが同等なのではなく、8つの基本割り込みと、それ以外の割り込みに分かれます。
基本割り込み用レジスタが1つ、それ以外の割り込み用レジスタが2つ割り振られています。
さらに、それぞれのレジスタは「有効化」「無効化」で別のレジスタになっています。
従って、割り込み制御レジスタは計6個となります。

このほかに、割り込み要因を表すためのレジスタがやはり3つと、FIQ(高速割り込み)を使う割り込み要因を指定するためのレジスタが一つあります。
今回はタイマ割り込みを有効にするだけなので、使うレジスタは1つだけ(IRQ_ENABLE_BASIC)です。

最後に、割り込みがかかったときにはCPUは「割り込みモード」になりますので、割り込みモードのためのスタックを設定しておく必要があります。
これは初期化コード(ソースコード中ではInit_Machine()という関数)で行っています。
ブートローダがプログラムを読み込むアドレスは0x8000ですので、0x0000~0x8000までの32KBが空いています。
この空間をIRQモードとFIQモードに16KBずつ割り当てています。(ここの部分はdwelch67さんのコードを借用しました。)

コメント