適用範囲:DS-5, RealView Development Suite (RVDS)
解説:
RVCTコンパイラは"__irq"キーワードをCの割り込みハンドラの記述用に提供しています。しかしながらこれは単純な(リエントラントでない)割り込みハンドラの為だけにデザインされています。これはリエントラントな割り込みに必要とされる状態の情報全てをストアしないからです。
トップレベルのハンドラの複雑さのレベルは、サブルーチンの呼び出し(branch with link, BL命令を使用する)かどうか、リエントラントである必要があるかどうかに依存します。典型的なシステムでは、IRQ入力を経由して接続される、異なった優先度を持った複数の割り込みソースが存在します。リエントラントな割り込みハンドラは高い優先度のIRQが、より低い優先度のIRQのハンドルを中止させることができます。リエントラントな割り込みハンドラ(少なくともトップレベルにおいて)はアセンブラを使って書く必要があります。
単純な割り込みハンドラのソリューション:
トップレベルのハンドラからサブルーチンを呼び出せるようにするには、IRQモードのリンクレジスタ(LR_irq)にストアされていたリターンアドレスがBLの呼び出し前にスタックされ、その後リストアされる必要があります。さもなければBLはサブルーチンのリターンアドレスを上書きすることによって、割り込みハンドラのリターンアドレスを破壊してしまいます。同様に、レジスタr0-r3およびr12は関数の呼ばれる側によって保存されません(AAPCSで定義されている通り)ので、トップレベルハンドラ(呼び出し側)で保存しておく必要があります。
IRQハンドラ関数を、__irqキーワードで修飾することで上記の必要な機能を扱うことができます。
例:
__irq void IRQHandler (void) { volatile unsigned int *base = (unsigned int *) 0x80000000; if (*base == 1) // which interrupt was it? { C_int_handler(); // process the interrupt } *(base+1) = *base; // clear the interrupt }
"armcc -cpu 7"をつけてコンパイルすることで、以下のコードが出力されます:
IRQHandler PUSH {r0-r5,r12,lr} MOV r4,#0x80000000 LDR r0,[r4,#0] CMP r0,#1 BNE {pc}+0x6 BL C_int_handler LDR r0,[r4,#0] STR r0,[r4,#4] POP {r0-r5,r12,lr} SUBS pc,lr,#4
割り込みハンドラ内で、割り込みソースをクリアすることは重要です。さもないと現在ペンディングされている割り込みが割り込みハンドラから復帰した直後に取り込まれ、無限ループを生成します。これは一般的に割り込みコントローラ内の"割り込み応答"レジスタへのアクセスによって行われます。
IRQHandlerC関数と_int_handler()はARMまたはThumb状態のいずれかもとれます。リンカは、必要に応じてBL命令をBLX命令に置き換えます。
リエントラントな割り込みハンドラのソリューション:
リエントラントな割り込みハンドラはさらに複雑になります。
BL命令を使ってサブルーチンを呼び出す前に、割り込みハンドラが割り込みを再許可する状況を考えます。もし他の割り込みがサブルーチン実行中に発生すると、サブルーチンのリターンアドレス(関数エントリでLR_irqにストアされた)は2番目のIRQ例外が発生すると破壊されます。
どのようにしてこの問題を解決すればいいでしょうか。
サブルーチン/C関数へのBLの前に、他のモードにスイッチすべきです。一般的にCPSRを変更することでSupervisor(SVC)モードが用いられます(SVCモードはOperating Systemが一般的に実行されるモードであり、充分な量のスタック領域が割り当てられます)。それから割り込みを再許可する前に、SVCモードのリンクレジスタ(LR_svc)をSVCモードのスタックにプッシュします。これでBL命令を使ってあらゆる他のサブルーチンが安全に呼ばれます。サブルーチンから戻ったら割り込みを無効にし、LR_svcをリストアして、SVCモードのスタックからCPSR_irqおよびLR_irqをリストアして例外からリターンします。
上記のプロセスは、次のステップから成り立ちます。IRQ例外ハンドラのエントリでは:
- LR_irqとSPSR_irqをSVCモードのスタックに補正/保存します
- SVCモードにスイッチします
- AAPCSで破壊される可能性のあるレジスタをストアします
- 8-byteスタックアライメントを保持するようスタックを補正します
- LR_svcをストアします
- 割り込みソースをクリアします
- 割り込みを再許可します
- 第2レベルのCハンドラを呼び出します
- 割り込みを禁止します
- LR_svcをリストアします
- 補正したスタックを元に戻します
- AAPCSレジスタをリストアします
- SVCモードスタックからリターンします
以上は、アセンブラで次のように記述できます:
IRQ_Handler SUB lr, lr, #4 ; Use SRS to save LR_irq and SPSP_irq SRSFD sp!, #0x13 ; on to the SVC mode stack CPS #0x13 ; Switch to SVC mode PUSH {r0-r3, r12} ; Store AAPCS regs on to SVC stack MOV r1, sp AND r1, r1, #4 ; Ensure 8-byte stack alignment… SUB sp, sp, r1 ; …adjust stack as necessary PUSH {r1, lr} ; Store adjustment and LR_svc BL identify_and_clear_source ; Clear IRQ source CPSIE i ; Enable IRQ BL C_irq_handler ; Branch to 2nd level handler CPSID i ; Disable IRQ POP {r1, lr} ; Restore LR_svc ADD sp, sp, r1 ; Un-adjust stack POP {r0-r3, r12} ; Restore AAPCS registers RFEFD sp! ; Return from the SVC mode stack
この場合もC_irq_handler()はARMまたはThumb状態のいずれもとれます。
リンカは、必要に応じてBL命令をBLX命令に置き換えます。
上記のサンプルでは以下に注意してください:
- IRQ handlerでのエントリでLRが補正されます
- 単一のSRS命令が指定されたスタックに対してLR_irqとSPSR_irqの両方をプッシュします
- スタックは8-byteアライメントを保持するよう補正される必要があります
- 割り込みソースは割り込みを再許可する前にクリアされていなければなりません
- IRQ handlerの出口では、IRQモードに戻る必要はありません。これは、LR_irqとSPSR_irqをSystemモードスタックからリストアするからです。(System モードとUserモードは同一のレジスタセットを共有します)
- FIQがIRQに割り込みできるよう、FIQは有効なままにします。もしシステムがFIQを使わない、あるいはFIQにIRQを中断させる必要がないなら、FIQは無効にしておくべきです