AVR32 StudioのStandalone版アプリケーション構成ファイル
AVR32 Studioのプロジェクト作成で「AVR32 C Project from template」からStandalone版のアプリケーションを指定したとき、作成される各種ファイルや設定等について記事をまとめた。
対象ボードはEVK1101で、搭載チップはAT32UC3B0256。
このページはまとめを随時書き加えるので、断り無くたびたび修正する予定。
パス | ファイル | 備考 |
---|---|---|
src/SOFTWARE_FRAMEWORK/ASM/ | trampoline.x | リセット・ハンドラ_trampolineがある。リセットからの流れを追うにはここから読み始める。 |
src/SOFTWARE_FRAMEWORK/DRIVERS/INTC/ | intc.c | exception.xから回されてきた割込を割込グループと割込ラインから割込要因を判定し、対応する割込ハンドラを呼び出す。割込ハンドラの登録ルーチンがあり、登録されなければデフォルト・ハンドラが設定される。 |
exception.x | 例外ハンドラ群(デフォルトはダイナミック・ストップ)を定義している。割込もまずはこの中の_int0〜_int3で受け付け、initc.cの関数へ処理を回す。 | |
src/SOFTWARE_FRAMEWORK/UTILS/LIBS/NEWLIB_ADDONS/ | libnewlib_addons-at32ucr1-speed_opt.a | C言語ライブラリのアドオン(後述)。 |
src/SOFTWARE_FRAMEWORK/UTILS/STARTUP_FILES/GCC/ | crt0.x | リセット処理に続き、呼び出されるC言語スタートアップ・ルーチン_start。最後にアプリケーションのmainを呼び出す。 |
newlib内のオブジェクトファイルとシンボルの一覧
libnewlib_addons-at32ucr1-speed_opt.a内のオブジェクトファイルとシンボルの一覧です。descriptionは該当APIを使うことがあったら書き入れていく。あまり使うことが無いような気もするけど。
object file | symbol | description |
---|---|---|
cpu.o | set_cpu_hz | |
get_cpu_hz | ||
udelay | ||
exception_handling.o | _register_exception_handler | |
_register_scall_handler | ||
handle_scall | ||
unhandled_exception | ||
init_exceptions | ||
interrupts.o | set_interrupts_base | |
unhandled_interrupt | ||
_handle_interrupt | ||
init_interrupts | ||
register_interrupt | ||
syscalls.o | unimplemented_syscall | |
isatty | ||
_fstat | ||
_stat | ||
_fcntl | ||
_link | ||
_kill | ||
_getpid | ||
_gettimeofday | ||
_unlink | ||
_execve | ||
_fork | ||
_wait | ||
sigfillset | ||
sigprocmask | ||
_init_argv | ||
_open | ||
_close | ||
_lseek | ||
_write | ||
_read | ||
_exit | ||
_sbrk | ||
_times | ||
_init_startup | ||
usart.o | usart_setbrg | |
set_usart_base | ||
usart_putc | ||
usart_getc | ||
usart_tstc | ||
usart_puts | ||
usart_init | ||
setjmp.o | setjmp | |
longjmp | ||
exception.o | _evba | |
_int0 | ||
_int1 | ||
_int2 | ||
_int3 | ||
_handle_breakpoint | ||
_breakpoint_unhandled | ||
_handle_exception | ||
_exception_return | ||
_exception_unhandled | ||
_handle_scall | ||
syscalls_sim.o | _init_argv_sim | |
_open_sim | ||
_close_sim | ||
_read_sim | ||
_write_sim | ||
_lseek_sim | ||
_exit_sim | ||
loop |
AVR32 C Project from templateのリンク・オプション
AVR32 StudioでAVR32 C Project from templateからStandalone版のアプリケーションを指定し、ビルドするとリンクで以下のようなコマンドが実行される(オブジェクトファイルの指定は削除)。
デバイスはAT32UC3B0256、プロジェクト名「SimpleTest」。
avr32-gcc -nostartfiles -L../src/SOFTWARE_FRAMEWORK/UTILS/LIBS/NEWLIB_ADDONS -march=ucr1 -Wl,--gc-sections -Wl,-e,_trampoline -mpart=uc3b0256 -Wl,--gc-sections --rodata-writable --direct-data -oSimpleTest.elf -lnewlib_addons-at32ucr1-speed_opt
-Lや-oの有名なオプションはともかく、組み込みであることから見かけないオプションもあり、今回調べてみることにした。
とりあえず参考リンク。
コマンドはavr32-gccであるが、実際にはavr32-ldがサブコマンドとして実行される。このときオプションは基本的にavr32-gccで解釈されるが、「-Wl,」と付いたものはこの部分を削除した上でavr32-ldへ渡される。
-nostartfiles
デフォルトのスタートアップ・ファイルを使用しないことを示す。
組み込みなので自分でスタートアップ・ルーチンのソースファイルを記述し、コンパイル&リンクする場合、このオプションを使用する。
-L
おなじみのライブラリ検索パスを指定する。オプションの直後にディレクトリ・パスが続く。
-march=
AVR32アーキテクチャを指定する。AVR32-gccだとわざわざ-mcpuを指定せず、アーキテクチャから指定すればよい。AT32UC3B0256の場合「ucr1」を続けて記述する。
現在指定できるのは「ap」、「ucr1」、「ucr2」、「ucr3」、「all-insn」だ。
何でAT32UC3Bが「ucr1」なのかは不明(追記:昨日書いていた)。現状AVR32 Studioにお任せだ。
この辺のCPU依存オプションについては、以下のコマンドの出力を読んで欲しい。
"%AVR32_HOME%\bin\avr32-gcc" --target-help
-Wl,--gc-sections
「gc」とはガベージ・コレクションのことで「ゴミ集め」。セクションは定義してあるのにコードもデータも無い、といった無入力セクションを片付ける(削除する)。動的ライブラリをリンクする場合は、これを付けてはいけない。
-Wl,-e,
このプログラムのエントリ・ポイントを指定する。スタートアップ・ルーチンの先頭アドレスがエントリ・ポイントとなる。これはシンボル名でも良いし、アドレス値でも良い(0xで始まれば16進数、0なら8進数、その他は10進数)。
スタートアップ・ルーチンが、resetベクタのアドレスを含むセクションであってもresetベクタと一致するとは限らない。エントリ・ポイントが指定されると、その辺を調整あるいはチェックしてくれるのかもしれない(エラーが起きるよう試したことはない)。
個人的には、このオプションが無くても、1セクション=スタートアップ・ルーチンにして、問題なくリセット→スタート開始できるのが良いと思っている。
-mpart=
パートを指定する。AT32UC3B0256なら「uc3b0256」である。製品が存在してもAVR32-gccがサポートしているとは限らないので、AVR32 Studioにお任せした方がよいかもしれない。
--rodata-writable
リード・オンリーなデータを書き込み可能なデータ・セクションへ配置するオプション。
--direct-data
最適化するとき、直接データ参照を許可する。
-o
ご存じ出力ファイルを指定するオプション。ここで出力ファイルは、ELF形式のロード・モジュールを指す。
-l
存在しないシンボルを、直後に続く名称のライブラリから検索し、見つけたらリンクする。実際のライブラリ・ファイルは、名称の先頭に「lib」が付き、末尾に「.a」が付く。動的ライブラリのときは末尾が「.so」。
アーカイブ・ファイル(ライブラリ)のダンプ
AVR32 StudioでAVR32 C Project from templateからStandalone版のアプリケーションを指定すると、以下のライブラリがソースに追加される。
src/SOFTWARE_FRAMEWORK/UTILS/LIBS/NEWLIB_ADDONS/libnewlib_addons-at32ucr1-speed_opt.a
ロード・モジュール(.elf)やオブジェクトファイル(.o)はELFフォーマットなので、該当ファイルのアイコンをダブル・クリックすれば、AVR32 Studioが自動的にリストを表示してくれる。
しかし、ライブラリ・ファイルはアーカイブ・ファイル(.a)であり、ELFファイルの集まりなので、ダブル・クリックしても同様に表示してくれない(バイナリをテキストとして無理矢理表示してくれる...)。
ELFファイルからシンボルや逆アセンブル・リストを取り出すのはコマンドavr32-objdumpであるが、これ自身はアーカイブ・ファイルが対象でも動作する。想像だが、AVR32 Studioがファイル拡張子".a"を認識していないようだ。.elfや.oと同じように扱うよう設定すればいいのだろうが、残念ながらeclipseやCDTに詳しくないので、さっさと断念した。
代わりにテキスト・ファイルにダンプして、そちらを参照することにする。Windowsならコマンドプロンプトから以下のようにコマンドを実行する。
"%AVR32_HOME%\bin\avr32-objdump" -x -d 対象アーカイブ・ファイル・パス > 出力テキスト・ファイル・パス
何をどのようにダンプするかオプションで指定する。「-x -d」やその他のオプションは以下参照(--helpによる出力から抜粋)。
sort option | long option | Meaning |
---|---|---|
-a | --archive-headers | Display archive header information |
-f | --file-headers | Display the contents of the overall file header |
-p | --private-headers | Display object format specific file header contents |
-h | --[section-]headers | Display the contents of the section headers |
-x | --all-headers | Display the contents of all headers |
-d | --disassemble | Display assembler contents of executable sections |
-D | --disassemble-all | Display assembler contents of all sections |
-S | --source | Intermix source code with disassembly |
-s | --full-contents | Display the full contents of all sections requested |
-g | --debugging | Display debug information in object file |
-e | --debugging-tags | Display debug information using ctags style |
-G | --stabs | Display (in raw form) any STABS info in the file |
-W | --dwarf | Display DWARF info in the file |
-t | --syms | Display the contents of the symbol table(s) |
-T | --dynamic-syms | Display the contents of the dynamic symbol table |
-r | --reloc | Display the relocation entries in the file |
-R | --dynamic-reloc | Display the dynamic relocation entries in the file |
@ |
Read options from |
|
-v | --version | Display this program's version number |
-i | --info | List object formats and architectures supported |
-H | --help | Display this information |
AVR32-gccのtargetヘルプ
AVR32-gcc限定の話ではないが、CPU製品(target)毎の違いを指定するオプションとして、-mcpu=、-march=、-mpart=がある(AVR32-gccだとわざわざ-mcpu=を指定する必要はない)。
これらのオプションと、その指定により別途指定可能となるオプションについてヘルプを表示する場合、--target-helpを指定すればよい。
Windowsなら、コマンドプロンプトから以下のコマンドを実行する。
"%AVR32_HOME%\bin\avr32-gcc" --target-help
残念ながらこのヘルプを見ても-marchに指定する値の「ucr1」が何なのか、特定の製品がどのアーキテクチャに該当するのか判らない。一方、-mpartに指定できるパートにどのようなものがあるかは判る。
-marchと-mpartは基本的にAVR32 Studioにお任せでよいのかもしれない。
2009/11/11 追記
AVR32-SoftwareFramework-AT32UC3-1.5.0の中の.docsrc/AVR32_FrameworkGCC.htmlを見ると
# GCC Architecture and parts: the AVR32 UC architecture and part number.
ARCH = ucr2
PART = uc3a0512ARCH = ucr1
PART = uc3b0256ARCH = ucr2
PART = uc3a3256ARCH = ucr3
PART = uc3l064
とあるのでアーキテクチャは以下のようである。
Product Prefix | -march= |
---|---|
AT32UC3B | ucr1 |
AT32UC3A | ucr2 |
AT32UC3L | ucr3 |
AVR32 C Project from templateのintc.c
AVR32 StudioでAVR32 C Project from templateからStandalone版のアプリケーションを指定すると、C言語ファイルsrc/SOFTWARE_FRAMEWORK/DRIVERS/INTC/intc.cが作成される。
INTCについてはこちらを。
お勉強としてintc.cを眺めてみる。行番号は私が付加したもので、実際には存在せず、行番号が飛んでいるのは不要と判断して削除したため。
53: //! Values to store in the interrupt priority registers for the various interrupt priority levels. 54: extern const unsigned int ipr_val[AVR32_INTC_NUM_INT_LEVELS];
AVR32_INTC_NUM_INT_LEVELSは(1<<2)=4であり、ipr_valは4要素の配列である。
ipr_valはexception.xで定義されている。ダンプ内容は次の通り。INTLEVELはbit31〜30、AUTOVECTORはbit13〜0。
要素番号 | 値(4byte) | INTLEVEL | AUTOVECTOR |
---|---|---|---|
0 | 00 00 01 04 | 0 | 0x0104 |
1 | 40 00 01 12 | 1 | 0x0112 |
2 | 80 00 01 20 | 2 | 0x0120 |
3 | c0 00 01 2e | 3 | 0x012e |
56: //! Creates a table of interrupt line handlers per interrupt group in order to optimize RAM space. 57: //! Each line handler table contains a set of pointers to interrupt handlers. 58: #if __GNUC__ 59: #define DECL_INT_LINE_HANDLER_TABLE(GRP, unused) \ 60: static volatile __int_handler _int_line_handler_table_##GRP[Max(AVR32_INTC_NUM_IRQS_PER_GRP##GRP, 1)]; 61: #elif __ICCAVR32__ 62: #define DECL_INT_LINE_HANDLER_TABLE(GRP, unused) \ 63: static volatile __no_init __int_handler _int_line_handler_table_##GRP[Max(AVR32_INTC_NUM_IRQS_PER_GRP##GRP, 1)]; 64: #endif 65: MREPEAT(AVR32_INTC_NUM_INT_GRPS, DECL_INT_LINE_HANDLER_TABLE, ~); 66: #undef DECL_INT_LINE_HANDLER_TABLE
AT32UC3B0256におけるavr32-gccの定義済みマクロによると、__GNUC__が定義済みなので、60行と65,66行目が有効である。
60行目は、割込ライン毎のハンドラ・テーブルを宣言する関数型マクロ定義DECL_INT_LINE_HANDLER_TABLEである。第1引数はグループ番号で、宣言されるテーブル名の一部に使われ、またテーブル・サイズの計算に利用される。第2引数は使用されない。
65行目の関数型マクロMREPEATの定義は以下のようになっている。C言語プリプロセッサにはマクロをループ展開する機能がないから、こういう手段を使うことになる。
ヘッダファイル mrepeat.h 64: #define MREPEAT(count, macro, data) TPASTE2(MREPEAT, count)(macro, data) 65: #define MREPEAT0( macro, data) 66: #define MREPEAT1( macro, data) MREPEAT0( macro, data) macro( 0, data) 67: #define MREPEAT2( macro, data) MREPEAT1( macro, data) macro( 1, data) 以下、同様にMREPEAT_LIMIT(=256)まで続く。 ヘッダファイル tpaste.h 59: #define TPASTE2( a, b) a##b
AVR32_INTC_NUM_INT_GRPSは18なので以下のように展開される。
DECL_INT_LINE_HANDLER_TABLE( 0, ~ );
DECL_INT_LINE_HANDLER_TABLE( 1, ~ );
...
DECL_INT_LINE_HANDLER_TABLE( 18, ~ );
更に
static volatile __int_handler _int_line_handler_table_0[Max(AVR32_INTC_NUM_IRQS_PER_GRP0, 1)];
static volatile __int_handler _int_line_handler_table_1[Max(AVR32_INTC_NUM_IRQS_PER_GRP1, 1)];
...
static volatile __int_handler _int_line_handler_table_18[Max(AVR32_INTC_NUM_IRQS_PER_GRP18, 1)];
へ展開される。AVR32_INTC_NUM_IRQS_PER_GRP???(uc3b0256.hで定義)のいくつかは0なんだけど、そのままだとエラーになるので、最低でも1を確保するようにしている。
ということで、割込グループ毎のハンドラ・テーブルを確保している(初期値無しなので0でクリア)。
66行目で、使い終わったマクロDECL_INT_LINE_HANDLER_TABLEを削除している。
68: //! Table containing for each interrupt group the number of interrupt request 69: //! lines and a pointer to the table of interrupt line handlers. 70: static const struct 71: { 72: unsigned int num_irqs; 73: volatile __int_handler *_int_line_handler_table; 74: } _int_handler_table[AVR32_INTC_NUM_INT_GRPS] = 75: { 76: #define INSERT_INT_LINE_HANDLER_TABLE(GRP, unused) \ 77: {AVR32_INTC_NUM_IRQS_PER_GRP##GRP, _int_line_handler_table_##GRP}, 78: MREPEAT(AVR32_INTC_NUM_INT_GRPS, INSERT_INT_LINE_HANDLER_TABLE, ~) 79: #undef INSERT_INT_LINE_HANDLER_TABLE 80: };
58〜65行目で作成した割込グループ毎のハンドラ・テーブルは個別に定義されているので、これらをまとめる全体のテーブル_int_handler_tableを定義している。
70〜74行目でデータ構造(割込数とハンドラ・テーブルへのポインタ)を定義し、76行目で割込グループ数分の配列で_int_handler_tableを定義し、75〜80行目で初期値を設定している。
このテーブルはconst宣言されているので、固定データ領域(FlushROM)に割り付けられる一方、割込グループ毎のハンドラ・テーブルは書き換え可能なSRAM領域となる。もし_int_handler_tableの初期化部分に割込グループ毎のハンドラ・テーブルをコードしてしまうと、ここも固定データ領域に確保されてしまい、後で書き換えられなくなる。
83: /*! \brief Default interrupt handler. 84: * 85: * \note Taken and adapted from Newlib. 86: */ 87: #if __GNUC__ 88: __attribute__((__interrupt__)) 89: #elif __ICCAVR32__ 90: __interrupt 91: #endif 92: static void _unhandled_interrupt(void) 93: { 94: // Catch unregistered interrupts. 95: while (TRUE); 96: }
デフォルトのハンドラ定義。ダイナミック・ストップしている。
ハードから直接呼ばれる訳ではないが、この関数に割込の標「__attribute__((__interrupt__))」(GNUの場合)を付ける。
詳細を調べていないが、少なくともrete命令で返る必要があるからだ。
106: * \note Taken and adapted from Newlib. 107: */ 108: __int_handler _get_interrupt_handler(unsigned int int_lev) 109: { 110: // ICR3 is mapped first, ICR0 last. 111: // Code in exception.S puts int_lev in R12 which is used by AVR32-GCC to pass 112: // a single argument to a function. 113: unsigned int int_grp = AVR32_INTC.icr[AVR32_INTC_INT3 - int_lev]; 114: unsigned int int_req = AVR32_INTC.irr[int_grp]; 161: return (int_req) ? _int_handler_table[int_grp]._int_line_handler_table[32 - clz(int_req) - 1] : NULL; 162: }
割込グループ毎に、どの割込要因があったか判定し、該当割込のハンドラをR12に入れて返す関数_get_interrupt_handlerの定義。ハンドラがないときNULLを返す。
ということはAVR32-gccの戻り値は、デフォルトでR12に格納するのか。知らなかった。
それから、この関数は先の関数_unhandled_interruptよりも先に呼び出されるが、ここでは「__attribute__((__interrupt__))」を付けてはいけない。rete命令でいきなり割込から抜けてしまうからだ。
処理内容は、引数の割り込み優先レベルint_levがINT番号と逆順なのでひっくり返した上で、INTCの該当レジスタICRから割込グループ番号(変数int_grp)、割込グループ番号から割込ライン(変数int_irq、ビットフラグ)に取得している。そしてint_grpとint_irqを元に_int_handler_tableからハンドラ・アドレスを取得している。int_irqが0の場合を考慮しているのは、割込要因発生からEVBAで登録されたベクタへ飛んでくるまでの間に、割込要因がクリアされるケースがあるためだ。例えば、発生直後かつ割り込まれる前に通常処理で割込要因フラグをクリアとか、より優先度の高い割込処理でクリアした場合があるだろう。
H8やSHのように割込要因毎のベクタ・テーブルを持ち、ハードウェア処理で例外/割込ハンドラへ飛んでくれる方が高速なので、私は好きだ。
AVR32のような方式も有りだとは思うが、やはり本当の割込処理を開始するまでに時間が掛かりすぎる。さらに関数_get_interrupt_handler処理において113行目や161行目の配列インデックス内の余計な引き算、割込優先度(引数int_lev)、int_grp、int_reqの3つも情報が必要な点を見ると、ソフトを解っていない開発者が仕様を考えたんだなぁと残念に思う。
165: void INTC_init_interrupts(void) 166: { 167: unsigned int int_grp, int_req; 168: 169: // For all interrupt groups, 170: for (int_grp = 0; int_grp < AVR32_INTC_NUM_INT_GRPS; int_grp++) 171: { 172: // For all interrupt request lines of each group, 173: for (int_req = 0; int_req < _int_handler_table[int_grp].num_irqs; int_req++) 174: { 175: // Assign _unhandled_interrupt as default interrupt handler. 176: _int_handler_table[int_grp]._int_line_handler_table[int_req] = &_unhandled_interrupt; 177: } 178: 179: // Set the interrupt group priority register to its default value. 180: // By default, all interrupt groups are linked to the interrupt priority 181: // level 0 and to the interrupt vector _int0. 182: AVR32_INTC.ipr[int_grp] = ipr_val[AVR32_INTC_INT0]; 183: } 184: }
割込グループ毎のハンドラ・テーブルの初期化。デフォルトの関数_unhandled_interruptを設定する。また各IPRレジスタに割り込みレベルINT0を設定する。
187: void INTC_register_interrupt(__int_handler handler, unsigned int irq, unsigned int int_lev) 188: { 189: // Determine the group of the IRQ. 190: unsigned int int_grp = irq / AVR32_INTC_MAX_NUM_IRQS_PER_GRP; 191: 192: // Store in _int_line_handler_table_x the pointer to the interrupt handler, so 193: // that _get_interrupt_handler can retrieve it when the interrupt is vectored. 194: _int_handler_table[int_grp]._int_line_handler_table[irq % AVR32_INTC_MAX_NUM_IRQS_PER_GRP] = handler; 195: 196: // Program the corresponding IPRX register to set the interrupt priority level 197: // and the interrupt vector offset that will be fetched by the core interrupt 198: // system. 199: // NOTE: The _intx functions are intermediate assembly functions between the 200: // core interrupt system and the user interrupt handler. 201: AVR32_INTC.ipr[int_grp] = ipr_val[int_lev & (AVR32_INTC_IPR_INTLEVEL_MASK >> AVR32_INTC_IPR_INTLEVEL_OFFSET)]; 202: }
どこからも参照されず、ロード・モジュールに取り込まれない関数。
引数irq、int_levで指定される割込と割込レベルで引数handlerの割込ハンドラを登録する。
1つ前の関数INTC_init_interruptsにおける初期化と一見似ているが、配列要素へのアクセス方法が異なることに注意が必要だ。関数INTC_init_interruptsでは、単純に割込グループ番号とグループ内割込数でループを回している。一方、この関数では単独の割込番号(引数irq)から割込グループ番号(変数int_grp)とグループ内の割込ラインを求め、テーブルにアクセスする。
従ってこの関数の第2引数へ以下の計算結果を渡すことになる。
割込グループ番号 × AVR32_INTC_MAX_NUM_IRQS_PER_GRP + 割込ライン
つまり2つの情報を引数1つにまとめるため、呼び出し前に掛け算し、呼び出されたら割り算と剰余を実行するのだ。ハンドラ登録だし、AVR32では掛け算も割り算も1サイクルで終了するが、無駄があるなあ。
内部(ハードウェア)で割込グループと割込ラインを分けるにしても、AVR32_INTC_MAX_NUM_IRQS_PER_GRPを2のべき乗にして、末尾にギャップを設けて割込グループ毎のサイズを揃えれば、統一した割込番号を振ってソフト側から簡単に隠蔽できるはずだ。というか他社CPUはそうしているのだから、開発者の知識と経験が浅いのか?それともそんなにチップサイズに影響があるのかなあ。
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を利用するならこの実装で合わせるしかないようだ。