割り込みの発生方法
割り込み発生の概要
割り込みは、XILINXのXDMAコアの機能を使用して発生させます。
XDMAというのはXILINXのPCI Express IPコアで、このコアにはusr_irq_req[0:0]やusr_irq_ack[0:0]という端子があります。IPコア設定画面の中でビット幅は1~16まで設定できます。つまり、最大で16本の割り込み入力が使用できるようになります。
XDMAコアの信号について
XDMAコアにある割り込み関連信号はusr_irq_reqとusr_irq_ackだけです。
ユーザ回路が割り込みを発生させたい場合は、usr_irq_req[0]をアサートします。
PCI Expressでは割り込み信号の状態をホストとカードで同じ値を保持する機能はなく、メッセージパケットやメモリライトを使って、L→HまたはH→Lに遷移したことを通知します。
usr_irq_ackは、XDMAコアがホストPCに割り込みメッセージを送信した時にアサートされます。ackという名前ですが、ホストPCのOSやドライバが受け取ったことを示しているわけではありません。あくまでもXDMAコアの中で返しています。
ドライバでの対応
ドライバが何もしていないときにいきなりusr_irq_reqをアサートしても、usr_irq_ackは帰りません。割り込みを発生させる前に、ドライバがBAR1の0x2004に1を書いて、割り込み許可をしておかなければなりません。
実は、XDMAコアはBAR1にいろいろな設定レジスタを持っていて、
この中の0x2000~0x20A4にIRQ関係のレジスタがあります。
割り込み許可関係のレジスタは0x04,0x08,0x0cと3種類ありますが、0x04のが読み書き可能な値が書かれたレジスタの本体です。
0x2008はセット専用、0x200cはリセット専用です。これらのレジスタは、1を書き込まれたビットが許可されたり、禁止されたりするというものです。読み出してからビットORして書き込みという手順が必要ないので、割り込み処理ルーチンの中で操作するのに向いています。(読み込んで、ビット操作して書き戻すと、その間に他のビットの状態が変わっている可能性があるため)
したがって、ドライバの割り込みセットアップルーチンでは、BAR1の0x2004に1を書いて、割り込みusr_irq(0)を許可するようにします。
割り込み許可マスクが設定されていれば、usr_irq_req[0]から200ns前後でackが返ってきます。その様子をMITOUJTAGを使って波形を観測しました。
また、BAR1の0x2040と0x2048にUser Interrupt Requestと、User Interrupt Pendingというレジスタがあります。
Request (0x2040)のほうは割り込みが発生したときに立つフラグです。Pending (0x2048)というのは、usr_irq_reqが入ったけど、割り込み許可マスクが設定されていないため、発行することができない待ち状態にあるときに立つフラグです。
ユーザ回路での割り込みのハンドリング
割り込みが発生するとusr_irq_ackが返ってきますが、ユーザ回路はこのackでusr_irq_reqを下げてはいけません。
ユーザ回路はusr_irq_reqをアサートしたら、ドライバがユーザ回路で作ったレジスタにアクセスして、割り込みを解除するような操作をするまでusr_irq_reqを保持し続ける必要があります。ackだけでusr_irq_reqをデアサートすると、ドライバが割り込みを処理しようとしたときにすでに割り込みが取り下げられていて、割り込みが発生しなくなってしまいます。
正しいやり方は、ユーザ回路の中にユーザ用の割り込みコントロールレジスタを作り、ドライバの割り込み応答ルーチンでそのレジスタを操作して解除することです。
下の図をもとに、正しい割り込みの処理方法を説明します。
- 割り込みが発生し、ISRの中で0x2040を読んで、どこかのビットで割り込みが発生しているかどうかを調べます。
- 割り込みが発生していたら、その値を0x200cに書き込んで、一時的にそのビットの割り込みを禁止します。
- DPCルーチンの中でユーザ回路で作った割り込み制御レジスタに書き込んで、割り込み発生の原因をクリアします。
- BAR1+0x2008に値を書き込んで、そのビットの割り込みを再び許可します。
実際に作成したレジスタと、回路の例を示します。
ハードウェアからの割り込み要求は、usr_irq_reqにそのまま入れるのではなく、ユーザが作った回路の中に割り込みレジスタを設けて、その割り込みコントロールレジスタからusr_irq_reqを操作するのがポイントです。
ソフトウェアでの対応
割り込みの処理のうち、高速な応答が必要な部分はデバイスドライバで行っています。デバイスドライバは、割り込みが発生すると、ユーザが登録した関数を呼び出します。
ユーザは割り込みが発生した際に呼び出してほしい関数を登録しておきます。
以下の関数を使うと、割り込みを受け取る関数を登録することができます。
BOOLEAN WINAPI PcieRegistISR(CALLBACK_ISR isr);
引数のisrには、以下の書式で示されるコールバック関数を指定します。
typedef void (WINAPI *CALLBACK_ISR)(ULONG reason);
このコールバック関数は割り込みが発生したときに呼び出され、ユーザのFPGAが定義した16bitの割り込み発生要因を受け取ります。
具体的には、次のようなコードとなります。
void WINAPI myisr(ULONG reason) { printf("割り込み発生!割り込み要因=%04x\n",reason); intcount++; } int main () { ...オープン処理.. if(PcieRegistISR(myisr) == FALSE) { printf("割り込みセットアップ失敗\n"); return 0; } ..... }
main関数が終了する(ドライバがクローズされる)と、割り込みは自動的に解除されます。
対応したサンプルデザイン
2018年4月4日以降にリリースされている
「PCIe-DMAでDDR3メモリを読み書きするFPGAデザイン 2018/4/4」
では割り込みに対応しております。
ボード上の青い押しボタンスイッチを押すと、ユーザプログラムでその割り込みを受け取ることができます。