AVR32 C Project from templateのexception.x

AVR32 StudioでAVR32 C Project from templateからStandalone版のアプリケーションを指定すると、アセンブラ・ファイルsrc/SOFTWARE_FRAMEWORK/DRIVERS/INTC/exception.xが作成される。
お勉強としてexception.xを眺めてみる。行番号は私が付加したもので、実際には存在せず、行番号が飛んでいるのは不要と判断して削除したため。

47:	#if !__AVR32_UC__ && !__AVR32_AP__
48:	  #error Implementation of the AVR32 architecture not supported by the INTC driver.
49:	#endif

AVR32 Studioでは「AVR32/GNU Preprocessing Assembler」という名前でツール登録されており、C言語プリプロセッサが走るようだ。C言語プリプロセッサ自体はinclude等のプリプロセッサ命令実行とマクロ展開等の文字列処理のみで、C言語コンパイル自体には関わっていない。GNUアセンブラ(gas)にはこれらの機能が無いので、流用しているらしい。Eclipse&CDT環境では良くあることなのかな?
__AVR32_UC__はUCシリーズのとき定義され、__AVR32_AP__はAPシリーズで定義される定義済みマクロのようだ。これら定義済みマクロを解説したドキュメントが見つからなかったが、たぶんそんなところだろう。AT32UC3B0256の定義済みマクロの一覧を調べる方法はある。

59:	  .section  .exception, "ax", @progbits

セクション.exceptionを定義。"ax"はallocatableとexecutableのフラグ、@progbitsは「contains data」タイプである。

64:	  // EVBA must be aligned with a power of two strictly greater than the EVBA-
65:	  // relative offset of the last vector.
66:	  .balign 0x200

コメントを読むと、

EVBAは、最後のベクタのEVBA相対オフセットより大きく、厳密に2のべき乗でアライメントされなければいけない。

とあるが、はて?ドキュメントのどこにそんな制約が書いてあるんだろう?そもそもそんな制約の必要性が理解できない。
とりあえず「最後のベクタのEVBA相対オフセット」は+0x100であり、「それより大きい2のべき乗」は0x200ということのようだ。
セクション.exceptionは0x80002200に存在し、サイズが0x200となる。これは.balignディレクティブによりアライメントが0x200となるため、次のデータ/コードまで埋められているからである。

68:	  // Export symbol.
69:	  .global _evba
70:	  .type _evba, @function
71:	_evba:

_evbaをグローバルで定義する。このアドレスがレジスタEVBA格納値となり、一部の例外ハンドラ領域のベースとなる。レジスタEVBAを利用する各例外ハンドラのアドレスは計算により求まるため、ハンドラ毎のシンボルをグローバルにする必要はない。

73:	        .org  0x000
74:	        // Unrecoverable Exception.
75:	_handle_Unrecoverable_Exception:
76:	        rjmp $

73行目で、.orgディレクティブで、現セクションのロケーション・カウンタを0に設定している。この値がAT32UC3B0256マニュアルの「Table 8-4. Priority and handler addresses for events」のHandler Address欄にあるオフセットと対応する。見比べて欲しい。
75行目で例外ハンドラのシンボルを定義。
76行目の「$」がちょっと??? gasで現在のロケーション位置を示すのは「.」だったような気がするのだが。やっていることは自分自身への相対ジャンプ、つまりダイナミック・ストップ(何もせずループを繰り返すことで停止)だ。ロード・モジュールの逆アセンブルでも「c0 08」となりrjmp命令で相対位置0へ飛ぶことになる。

78:	        .org  0x004
79:	        // TLB Multiple Hit: UNUSED IN AVR32UC.
80:	_handle_TLB_Multiple_Hit:
81:	        rjmp $

178:	        .org  0x100
179:	        // Supervisor Call.
180:	_handle_Supervisor_Call:
181:	        rjmp $

各ハンドラの内容はUnrecoverable例外のときと同様なので飛ばす。

184:	// Interrupt support.
185:	// The interrupt controller must provide the offset address relative to EVBA.
186:	// Important note:
187:	//   All interrupts call a C function named _get_interrupt_handler.
188:	//   This function will read group and interrupt line number to then return in
189:	//   R12 a pointer to a user-provided interrupt handler.

ここからは割込ハンドラ(EIC経由)である。

割り込みコントローラ(INTC)はEVBAへの相対アドレス・オフセットを与えなければならない。
重要な注記:
すべての割込(訳注:INT0〜3のこと)は_get_interrupt_handlerと名付けられたC言語関数を呼び出す。この関数はグループと割込ライン番号(訳注:割込要因のこと?)を読み、そのときのユーザ提供割込ハンドラへのポインタをR12に返す。

ふーん。

191:	  .balign 4

これ以降は4バイト・アライメント。

193:	  .irp    priority, 0, 1, 2, 3
194:	_int\priority:

216:	  .endr

.irpディレクティブは一種のマクロ定義/展開である(最初見たとき割込処理を展開する専用ディレクティブかと思った)。第1引数がマクロ名で、.endrディレクティブが出てくるまでに記述されている「\」で始まるマクロ名を、第2引数以降で置換し、ブロックを展開する。
194行目の場合、1回目のブロック展開で「_int0:」のように置換される。
INT0からINT3における処理を一回の記述で済ませている訳で、すべて同じ処理にしているから出来る話。

194:	_int\priority:
195:	#if __AVR32_UC__
196:	  // R8-R12, LR, PC and SR are automatically pushed onto the system stack by the
197:	  // CPU upon interrupt entry. No other register is saved by hardware.
198:	#elif __AVR32_AP__
199:	  // PC and SR are automatically saved in respectively RAR_INTx and RSR_INTx by
200:	  // the CPU upon interrupt entry. No other register is saved by hardware.
201:	  pushm   r8-r12, lr
202:	#endif

割込ハンドラの先頭部分。今回は__AVR32_UC__が定義済みなので、196〜197行目が有効、つまりコードはない。とりあえずコメントを読むと

R8〜R12, LR, PC そしてSRレジスタは割込エントリのCPUによってシステム・スタックへ自動的にpushされる。

203:	  mov     r12, \priority  // Pass the int_lev parameter to the _get_interrupt_handler function.
204:	  call    _get_interrupt_handler
205:	  cp.w    r12, 0          // Get the pointer to the interrupt handler returned by the function.

「\priority」は展開ブロックにおいて0〜3に置換される。割込レベル(0〜3)をR12に格納して、関数_get_interrupt_handlerを呼び出す。R12にユーザ提供割込ハンドラへのポインタが返されているので、NULLポインタでないかコンペア・チェックする。
(2009/11/11 追記)
204行目のcallは仮想オペコードで、実際にはmcall命令に変換される。

206:	#if __AVR32_UC__
207:	  movne   pc, r12         // If this was not a spurious interrupt (R12 != NULL), jump to the handler.
208:	#elif __AVR32_AP__
209:	  breq    spint\priority  // If this was a spurious interrupt (R12 == NULL), branch.
210:	  st.w    --sp, r12       // Push the pointer to the interrupt handler onto the system stack since no register may be altered.
211:	  popm    r8-r12, lr, pc  // Restore registers and jump to the handler.
212:	spint\priority:
213:	  popm    r8-r12, lr
214:	#endif

ここでは207行目が有効。movne命令は、Not Equal成立時にmovを実行するので、R12がNULLポインタでないとき、R12のハンドラへジャンプする(代入相手がPCだから)。

215:	  rete                    // If this was a spurious interrupt (R12 == NULL), return from event handler.

ここへ来るのはR12が0のときである。rete命令で割込ハンドラから返る。同時に自動的にpushされたレジスタ群がシステム・レジスタからpopされる。

221:	  .balign 4

ここから先は4バイト・アライメント。元々同じアライメントだが、安全のため、と解釈しておこう。

223:	  // Values to store in the interrupt priority registers for the various interrupt priority levels.
224:	  // The interrupt priority registers contain the interrupt priority level and
225:	  // the EVBA-relative interrupt vector offset.
226:	  .global ipr_val
227:	  .type ipr_val, @object

コメントは

別々の割込レベルのための割込優先レジスタ(訳注:IPRのこと)へ保存する値である。
割込優先レジスタは割込優先レベルとEVBA相対割り込みベクタ・オフセットを格納する。

シンボルipr_valをグローバルなデータとして宣言。

228:	ipr_val:
229:	  .word (AVR32_INTC_INT0 << AVR32_INTC_IPR_INTLEVEL_OFFSET) | (_int0 - _evba),\
230:	        (AVR32_INTC_INT1 << AVR32_INTC_IPR_INTLEVEL_OFFSET) | (_int1 - _evba),\
231:	        (AVR32_INTC_INT2 << AVR32_INTC_IPR_INTLEVEL_OFFSET) | (_int2 - _evba),\
232:	        (AVR32_INTC_INT3 << AVR32_INTC_IPR_INTLEVEL_OFFSET) | (_int3 - _evba)

ipr_valの定義。.wordディレクティブで式を複数並べることにより、word(32bit)配列を作成。改行前に「\」でエスケープが必要なのは気をつけないと。
値は製品マニュアルの「15.4.4 Interrupt Priority Registers」と見比べて欲しい。



途中で出てきた関数_get_interrupt_handlerはintc.cで定義されている。ユーザ提供割込ハンドラもたぶんそこで定義されているのだろう。
割込要因の判定とその割込処理を、関数_get_interrupt_handlerとユーザ提供割込ハンドラへ括りだしているのは、あくまでも実装例であり、このような形にする必要はない。実際のプログラムなら_get_interrupt_handler相当の関数で、割込要因を認識した時点で、すぐ割込処理を開始したり、処理関数へ直接ジャンプすることになるだろう。

2009/11/09 追記
直ぐ上で「あくまでも実装例であり、このような形にする必要はない。」と書いたが、intc.cの関数_get_interrupt_handlerのコメントを見ると、newlibに適応するために必要らしいので、newlibを利用するならこの実装で合わせるしかないようだ。