avr32-gccのAVR32関連マクロ
以前、AT32UC3B0256における定義済みマクロを紹介した。これらは-marhオプションや-mpartオプションにより定義が変わったり、定義されなくなったりする。
今回はgccのソースからAVR32関係のマクロ名を探してみた。当然だがこれらはコンパイル・オプションに従いavr32-gccが定義するマクロであり、ここで紹介したマクロを勝手に定義してもMCUが変身したり、ハードウェア機能が追加されるはずもない。ユーザは勝手に定義すべきでない。
ソースはAtmelのサイトからダウンロードできる。「AVR32 GNU Toolchain 2.2.1 - Linux Source Code」を利用したが、参照するだけなのでこれを利用する(Windows用は見あたらない)。
なお、それらしい記述を抜き出して整形しただけであり、コードまで追いかけていない。
しかしこういう情報はAtemelがドキュメントにまとめて欲しいものだ。APシリーズがAVR32Bだなんて初めて知った(UC3のマニュアルしか見たことがないから)。
avr32_gnu_toolchain_2.2.1_source\gcc-4.3.2\gcc\config\avr32\linux-elf.hおよびavr32-elf.h
にある記述から、マクロ定義情報を探してみた。「TARGET_〜」はC言語マクロである。たぶんCPU製品毎の定義情報をチェックするのだろう。「part名」、「arch名」というのは後述の表を参照のこと。
条件 | マクロ名 |
---|---|
無条件 | __avr32__ |
無条件 | __AVR32__ |
LINUX-ELF出力用 | __AVR32_LINUX__ |
ELF出力用 | __AVR32_ELF__ |
無条件 | part名 |
無条件 | arch名 |
avr32_arch->uarch_type == UARCH_TYPE_AVR32A | __AVR32_AVR32A__ |
avr32_arch->uarch_type != UARCH_TYPE_AVR32A | __AVR32_AVR32B__ |
TARGET_UNALIGNED_WORDが真のとき | __AVR32_HAS_UNALIGNED_WORD__ |
TARGET_SIMDが真のとき | __AVR32_HAS_SIMD__ |
TARGET_DSPが真のとき | __AVR32_HAS_DSP__ |
TARGET_RMWが真のとき | __AVR32_HAS_RMW__ |
TARGET_BRANCH_PREDが真のとき | __AVR32_HAS_BRANCH_PRED__ |
TARGET_FAST_FLOATが真のとき | __AVR32_FAST_FLOAT__ |
ELF出力で、TARGET_NO_MUL_INSNSが真のとき | __AVR32_NO_MUL__ |
avr32_gnu_toolchain_2.2.1_source\gcc-4.3.2\gcc\config\avr32\avr32.c
には製品毎のマクロ情報がある。左端のnameは-mpart=オプションの文字列、architecture typeは-marchオプションのアーキテクチャ名(gcc内部用マクロの都合で大文字になっているが指定は小文字)、macroはその製品で定義されるマクロ名である。part_typeはgcc内部で利用されるマクロで、通常は関係ないがavr32-gccのソースを追いかける人用に残した(というほどのものではないが)。
name | part_type | architecture type | macro |
---|---|---|---|
none | NONE | AP | __AVR32__ |
ap7000 | AP7000 | AP | __AVR32_AP7000__ |
ap7001 | AP7001 | AP | __AVR32_AP7001__ |
ap7002 | AP7002 | AP | __AVR32_AP7002__ |
ap7200 | AP7200 | AP | __AVR32_AP7200__ |
uc3a0128 | UC3A0128 | UCR2 | __AVR32_UC3A0128__ |
uc3a0256 | UC3A0256 | UCR2 | __AVR32_UC3A0256__ |
uc3a0512 | UC3A0512 | UCR2 | __AVR32_UC3A0512__ |
uc3a0512es | UC3A0512ES | UCR1 | __AVR32_UC3A0512ES__ |
uc3a1128 | UC3A1128 | UCR2 | __AVR32_UC3A1128__ |
uc3a1256 | UC3A1256 | UCR2 | __AVR32_UC3A1256__ |
uc3a1512 | UC3A1512 | UCR2 | __AVR32_UC3A1512__ |
uc3a1512es | UC3A1512ES | UCR1 | __AVR32_UC3A1512ES__ |
uc3a3revd | UC3A3REVD | UCR2NOMUL | __AVR32_UC3A3256S__ |
uc3a364 | UC3A364 | UCR2 | __AVR32_UC3A364__ |
uc3a364s | UC3A364S | UCR2 | __AVR32_UC3A364S__ |
uc3a3128 | UC3A3128 | UCR2 | __AVR32_UC3A3128__ |
uc3a3128s | UC3A3128S | UCR2 | __AVR32_UC3A3128S__ |
uc3a3256 | UC3A3256 | UCR2 | __AVR32_UC3A3256__ |
uc3a3256s | UC3A3256S | UCR2 | __AVR32_UC3A3256S__ |
uc3b064 | UC3B064 | UCR1 | __AVR32_UC3B064__ |
uc3b0128 | UC3B0128 | UCR1 | __AVR32_UC3B0128__ |
uc3b0256 | UC3B0256 | UCR1 | __AVR32_UC3B0256__ |
uc3b0256es | UC3B0256ES | UCR1 | __AVR32_UC3B0256ES__ |
uc3b164 | UC3B164 | UCR1 | __AVR32_UC3B164__ |
uc3b1128 | UC3B1128 | UCR1 | __AVR32_UC3B1128__ |
uc3b1256 | UC3B1256 | UCR1 | __AVR32_UC3B1256__ |
uc3b1256es | UC3B1256ES | UCR1 | __AVR32_UC3B1256ES__ |
uc3l064 | UC3L064 | UCR3 | __AVR32_UC3L064__ |
uc3l032 | UC3L032 | UCR3 | __AVR32_UC3L032__ |
uc3l016 | UC3L016 | UCR3 | __AVR32_UC3L016__ |
上記と同じソースにあるアーキテクチャ毎のマクロ情報がある。nameは-march=オプションの文字列、microarchitecture typeがマニュアル等に書かれているアーキテクチャ(という言い方しかないが)で、feature flagsはgccソース内のビットフラグ・マクロでCPU内部機能を表す。macroはそのアーキテクチャで定義されるマクロ名とその値である。architecture typeはgcc内部で利用されるマクロ名である。
feature flagsには、本当はプリフィックス「FLAG_AVR32_」が付いている。表の右端が削れて見えないため、このマクロ名を省略して短くした。
name | archite- cture type |
micro- archite- cture type |
feature flags |
macro |
---|---|---|---|---|
ap | AP | AVR32B | HAS_DSP ¦ HAS_SIMD ¦ HAS_UNALIGNED_WORD ¦ HAS_BRANCH_PRED ¦ HAS_RETURN_STACK ¦ HAS_CACHES | __AVR32_AP__ |
ucr1 | UCR1 | AVR32A | HAS_DSP ¦ HAS_RMW | __AVR32_UC__=1 |
ucr2 | UCR2 | AVR32A | HAS_DSP ¦ HAS_RMW ¦ HAS_V2_INSNS | __AVR32_UC__=2 |
ucr2nomul | UCR2NOMUL | AVR32A | HAS_DSP ¦ HAS_RMW ¦ HAS_V2_INSNS ¦ HAS_NO_MUL_INSNS | __AVR32_UC__=2 |
ucr3 | UCR3 | AVR32A | HAS_DSP ¦ HAS_RMW ¦ HAS_V2_INSNS | __AVR32_UC__=3 |
AVR32のMPUドライバの例題を使ってみる(その2)
AVR32のMPUドライバの例題を使ってみる(その1)からの続き。
まずはMPUドライバのIntroductionを読んでみる。形式的な内容なので「Example operating mode:」以降から。
- この例題は、MPUARIエントリと、MPUARDエントリの数を表示することにより開始する。
- メモリ領域は以下のやり方でセットアップする。
ユーザメモリ領域のアクセス許可
Memory | address | Region | sub-region | Access Permissions |
---|---|---|---|---|
Ram | 0x00000000 - 0x00003C00 | 1 | 0 to 14 | Read / Write |
Ram | 0x00003C00 - 0x00004000 | 1 | 15 | Read |
Stack | &stack - &stack + stack size | 2 | all | Read / Write |
Flash | 0x80000000 - 0x8000C000 | 0 | 0 to 11 | Read / eXecute |
Flash | 0x8000C000 - 0x80010000 | 0 | 12 to 15 | Read |
PBA memory map | 0xFFFF0000 - 0xFFFFFFFF | 3 | all | Read / Write |
- アプリケーションがリージョン1/サブ・リージョン15へ書き込みアクセスするとき。それがDTLB書き込み保護例外をトリガする→LED3が点灯する。
- アプリケーションがリージョン0/サブ・リージョン12の関数を呼び出すとき。それがITLB保護例外をトリガする→LED2が点灯する。
- 例題の最後:LED0とLED1が永遠に点滅する。
更に飛ばして「Information」の箇条書き。
- EVK1100またはEVK1101評価キット
- CPUクロック12MHz
- USART1をRS232C DB9ケーブル経由でPCのシリアル・ポートに接続する。
- PCターミナル設定は、57600[bps], 8bit data, no parity bit, 1 stop bit, no flow control。
mpu_example.cを読んでみる。
先にmain()から。
507: int main(void) 508: { 509: Enable_global_exception(); 510: Enable_global_interrupt(); 511:
関数main開始直後、例外と割込を有効にしている。509行と510行はマクロ展開されてレジスタSRのbit21(Exception Mask)とbit16(Global Interrupt Mask)をビットクリアしている(これらはcompiler.hで定義)。
514: gpio_set_gpio_pin(LED0_GPIO); gpio_set_gpio_pin(LED1_GPIO); 515: gpio_set_gpio_pin(LED2_GPIO); gpio_set_gpio_pin(LED3_GPIO);
LED消灯。
523: pm_switch_to_osc0(&AVR32_PM, FOSC0, OSC0_STARTUP);
PM(Power Manager)に対してクロックをOSC0(12MHz)へ変更指示。FOSC0は12000000。
526: init_dbg_rs232(FOSC0); 527: print_dbg("\n\n## This is the AT32UC3 MPU example ##");
RS232Cを初期化。クロック源はOSC0を使用。メッセージを出力する。
以降メッセージ出力関係は省略。
551: test_memory_area();
メモリ領域をテストし、MPUを有効化する(後述)。
557: *( (volatile int*) (0x00003C00)) = 0x55AA55AA;
意図的にDTLB保護例外を発生させ、結果としてLED3が点灯する。
561: cpu_delay_ms(2000, FOSC0);
2秒間待って次へ進む。関数cpu_delay_msはインラインでcycle_counter.hに定義されていて、サイクル・カウンタが0になるのをポーリングしている。この例題では問題ないが、実プログラムで使用すると問題になる可能性が高い。
568: ForbiddenFunc();
保護されていない領域のコードを実行したら、ITLB保護例外が発生し、LED2が点灯する。
571: DisplayLedChaserInfinitly();
LED0とLED1を点滅させる。
MPUの使用法は結局初期化(設定)とハンドラが中心となるので、関数mainはあまり重要ではない。
次はMPUを初期化している初期化関数test_memory_areaを読んでみる。
321: void test_memory_area(void) 322: { 337: *(U32 *)&config1 = (U32)Get_system_register(AVR32_CONFIG1); 338: u8NbMpuAriEntries = config1.immusz; 339: u8NbMpuArdEntries = config1.dmmusz;
システム・レジスタCONFIG1を読み出してきて、その中のIMMUエントリ数とDMMUエントリ数を、それぞれu8NbMpuAriEntriesとu8NbMpuArdEntriesに格納する。
355: dmpu_entries[REGION_0_FLASH_INDEX].addr = AVR32_FLASH_ADDRESS; 358: dmpu_entries[REGION_0_FLASH_INDEX].size = MPU_REGION_SIZE_64KB; 360: dmpu_entries[REGION_0_FLASH_INDEX].valid = 1; 361: if( set_mpu_entry(&dmpu_entries[REGION_0_FLASH_INDEX], REGION_0_FLASH_INDEX) ) 362: ToggleLedsInfinitly(); // Error
表「ユーザメモリ領域のアクセス許可」の「Memory」における"Flash"のリージョン設定である。REGION_0_FLASH_INDEXは0(ローカル・マクロ定義)、AVR32_FLASH_ADDRESSは製品ヘッダファイル(uc3b0256.h)で定義されたFlushROMも開始アドレス、MPU_REGION_SIZE_64KBはMPUドライバ・ヘッダファイル(mpu.h)で定義されたリージョン・サイズである。
mpuに設定する情報を格納するテーブルdmpu_entriesに
- リージョン開始アドレス、
- リージョン・サイズ
- 有効フラグ
を設定して、この情報をset_mpu_entry()に渡して1つのリージョンを設定する。戻り値は失敗フラグで、失敗したときにはLEDを点滅しているようだ。
366: set_access_permissions(REGION_0_FLASH_INDEX, MPU_APRA_ID, MPU_PRIVRX_UNPRIVRX); 369: set_access_permissions(REGION_0_FLASH_INDEX, MPU_APRB_ID, MPU_PRIVR_UNPRIVNONE);
ユーザメモリ領域のアクセス許可にある通り、FlushROMに対して2種類のアクセス許可を設定している。レジスタA群/B群があるから可能なのだが、逆を言えば同一リージョンに3種類のアクセス許可は出来ないかもしれない。複数リージョンを同一メモリ領域に設定すれば可能か?
第3引数でアクセス許可の種別を渡している。MPU_PRIV???_UNPRIV???という形式なのは、アーキテクチャ・マニュアルの「Table 5-3. Access permissions implied by the APn bits」を見れば一目瞭然だ。特権(Privileged)モード/非特権(Unprivileged)モード毎にアクセス許可のペアがあるからだ。
386: select_subregion(REGION_0_FLASH_INDEX, 0xF000);
リージョンとそのアクセス許可を設定したので、次はそのサブ・リージョンを設定する。第2引数がレジスタMPUPSRn(nはリージョン番号)にそのまま設定される。これはビットフラグになっていて、0のときレジスタA群、1のときレジスタB群のアクセス許可が該当サブ・リージョンに適応される。再度ユーザメモリ領域のアクセス許可と見比べて欲しい。
余談だが同一ドライバ/モジュールの接頭子などは統一して欲しいものだ。
以下、リージョン領域の設定、アクセス許可設定、サブ・リージョンのA/B選択を繰り返す。これらは省略するが、スタックだけは、サイズの求め方が面倒になっている。
448: u32kBSizeValue = (U32)*1 454: ToggleLedsInfinitly(); // Error 455: dmpu_entries[REGION_2_STACK_INDEX].size = RegionSize;
つまりドライバ関数mpu_convert_kbsize_to_eregionsizeを使って単位変換している。しかし基本的にスタック・サイズはサブ・リージョン・サイズの1〜16倍なので、そのことを考慮してスタックを設定していれば、この関数を呼ばなくても簡単に計算できるはずだ。
499: enable_mpu(); // Enable the MPU address checking.
最後にMPUの機能を有効化している。
テーブルdmpu_entriesにMPUのリージョン設定情報が残っているが、実はどこからも参照されていなかったりする(怒)。
残った関数handle_exceptionである。この関数は例外/割込すべてで呼び出される。
196: switch(exception_cause) 203: disable_mpu(); // Disable the MPU to avoid to trigger the exception again! 204: break;
そしてexception_causeで分別されて処理されている。ほとんど無関係な例外や割込では、ドライバ関数disable_mpuを呼び出してMPUを無効化している。まあ、それらには来ないはずだからそれでよい。default:でまとめていないのはEVK1100ボードでLEDを点灯しているためと思われる。
この例題で関係するのは2つのケースだけだ。
254: case AVR32_EVBA_OFFSET_DTLB_PROT_W/4: 259: ((avr32_gpio_port_t *)&AVR32_GPIO.port[LED3_GPIO >> 5])->ovrc = 1 << (LED3_GPIO & 0x1F); 266: if ( (*((unsigned char *)exception_address) & 0xE0 ) == 0xE0 ) 268: sp[1] = exception_address + 4; 269: else 270: // Compact instruction 271: sp[1] = exception_address + 2; 272: break;
1つ目がDTLB書き込み保護例外だ。説明を省いたが、本関数を呼び出している例外ハンドラではpushm命令でR0〜R12とLR(計14個)をスタックに積んでいる。引数exception_addressには呼び出し側でレジスタspの値に14*4+4を加えた値、つまり例外を引き起こしたレジスタPCの格納アドレスである(pushmの14個とSRそしてPCがスタックに積まれる)。
一方、同様に呼び出し側から渡される引数spは、レジスタSPから14*4を差し引いた、つまりpushmする前のSPが格納されている。sp[0]はスタックに積まれたSR、sp[1]はスタックに積まれたPCである。
結果、exception_addressが指すアドレス値と、sp+1に格納されたアドレス値は一致する。なんだかなあ、引数1つでいいと思うんだけど。
やっていることは、スタックに積まれたPCの値から実行された命令が2バイト長か4バイト長か判定し、そのPCを直後の命令を指すように修正しているのだ。こうして次の命令から実行を再開するのだ。
いくつかの命令を確認したが、確かに4バイト命令はMSB側3bitがセットされている。それならそうとアーキテクチャ・マニュアルに書いてくれればいいのに(どこかに書いてあるのかな?)。
214: case AVR32_EVBA_OFFSET_ITLB_PROT/4: 219: ((avr32_gpio_port_t *)&AVR32_GPIO.port[LED2_GPIO >> 5])->ovrc = 1 << (LED2_GPIO & 0x1F); 220: disable_mpu(); // Disable the MPU to avoid to trigger the exception again! 221: break;
関係するもう1つはITBL保護例外だ。
ここでは、無関係な例外同様、MPUを無効化して終了している。Data系と異なり、Instruction系はスタック情報から再実行とは行かないのでやむを得まい。
MPUによる例外発生は他にもあるが、この例題で扱っているのは上記の2つだけだ。
DTLB書き込み保護例外のリカバリ内容は興味深いが、こんなことしても必要なデータが保存されないことに変わりがなく、復帰できたからといって継続的に正常動作するとは言えないだろう。保護例外処理では、個々のデータの意味など判らず形式的に修復しているだけだ(例題を批判しているのではなく、製品で真似しないでね、という意味)。
ウォッチドッグ・タイマ同様に、異常を検出したらリセットして再起動掛けるのが正道だろう。OSが居るなら、プロセスの再起動でも良い。
ちなみに、実際にMPUドライバの例題を実行したところ、(当たり前だが)問題なく動作した。
AVR32のSRAM開始アドレス&NULLポインタ問題
今頃気が付いたが、現在ターゲットにしているAT32UC3B0256はSRAMがアドレス0から割り付けられている。つまり変更可能な変数を定義したとき、この変数がSRAM領域先頭に割り付けられると、変数のポインタが0となる。
一方NULLポインタは通常0であり、実際avr32-gccでは0が割り付けられる(逆アセンブルでも確認済み)。NULLのヘッダフィルにおける定義は以下のようになっている。
stddef.h, 400: #define NULL __null
上記の定義はGCC特有の定義だが、__nullはコンパイラ内部で0となっているようだ。
NULLポインタは、さまざまな関数で特殊なポインタとして利用されており、特殊な意味でNULLなのか、本来の変数ポインタとしてのNULLなのか、ソフトウェア側では判断がつかなくなる。
C言語の規約からすれば、NULLが0である必要はないのだから、avr32-gccでは0以外の値をNULLに割り付ける手もある。しかし0ならレジスタSRのZビットをチェックすればいいだけで、特定の値と比較するより処理が早くなるから、結論としては「RAMをアドレス0に割り付けるな」となる(これは他のCPUでも通じる話)。
intc.cの話でも書いたように、どうもAtmelのMCU開発者はソフトウェアというものを知らないらしい。
実際のところ、アドレス0に何が割り付けられているのか?スタンドアローン版のデフォルト・プロジェクトだとセクション.dataがあり、ソフト開発者の立場からすると目眩がする。これでバグ出したら、原因究明が大変そう。
今更SRAMのアドレスなど変えようもない。だからよく考えてハードを設計して欲しいものだが、ここではソフトウェア側での対処方法を考えたい。
一番楽な方法は、アドレス0に何も割り付けないことだ。いくつか方法があるが、中でも簡単な方法はリンカ・スクリプトでSRAM領域開始アドレスを+4だけずらし、サイズを-4縮めることである。当然1byteで十分なのだが、アライメントを取るため4byteとした。
9: MEMORY 10: { 11: FLASH (rxai!w) : ORIGIN = 0x80000000, LENGTH = 256K 12: CPUSRAM (wxa!ri) : ORIGIN = 0x00000000, LENGTH = 32K 13: USERPAGE : ORIGIN = 0x80800000, LENGTH = 512 14: FACTORYPAGE : ORIGIN = 0x80800200, LENGTH = 512 15: }
12行目がSRAM領域なので、以下のように変更すればよい。
12: CPUSRAM (wxa!ri) : ORIGIN = 0x00000004, LENGTH = 32K-4
この方法の欠点は、絶対に使われることのないメモリが4byte出来てしまうことだ。
異なる方法としては、NULL判定されない変数のみを集めたセクション、あるいはそのような変数1つをアドレス0へ配置することだ。当然用途が確定しにくいヒープ領域やスタック領域を持ってきてはいけない。
この条件は意外に難しい。そんな変数をわざわざ見つけて異なるセクションに分けるのが面倒、そして後日メンテナンスでNULLチェックするよう修正する可能性があるかもしれない等々、あまり勧められない。
面倒なバグの元を潰せるコストとして、4byteは安いかもしれない。
ちなみにこの問題を好意的に解釈すると、内部高速SRAMを設計するにあたり、アドレス・デコーダが早くなるようアドレス0を開始したかったのだと思う。
それでもデフォルトのリンカ・スクリプトや例題でソフト開発者を導くように配慮が欲しいものだ。
AVR32のMPUドライバの例題を使ってみる(その1)
「Memory Protection Unit」のことであって、世間一般の「Micro Proccessing Unit」のことではない(私は初め間違えた)。MPUは、MMU(Memory Management Unit」の簡略版の位置付けで、仮想記憶まではサポートしていない。またMMU同様、CPUに近いモジュールのため詳細は、製品マニュアルではなく、アーキテクチャ・マニュアルの方に掲載されている。仮想メモリが使える訳でもなく、ちょっとした組み込みではあまり使う意義を感じないが、不要なアクセスを検知してデバッグが進むなり、実製品で暴走を検出してフェイル・セーフ処理ができるならいいかなと考えている。
だが本来アクセス可能な場所を防御してしまい、要らぬバグを埋め込む可能性もある。自作するとバグを作り込む可能性もあるし、余計な時間も掛けたくない。
そこでAT32UC3 Series Software FrameworkのMPUドライバを試してみたい。メーカのドライバだと、芋づる式にいろいろ取り込む必要が出てくるかもしれないが、MPUだから初期化と例外処理くらいなので依存性は低いだろう。
実ボード/デバイスは、いつも通りEVK1101/AT32UC3B0256。
AVR32 Studio自身がMPUドライバと例題(EXAMPLE)を持っているのでこれを利用する。
MPUの概要(「5.2 Understanding the MPU」から)。
- 8つのプロテクション・リージョンを持ち、
- リージョンの開始アドレスとサイズで指定し、
- 異なる特権レベルを設定する。
- 異なるアクセス・パーミッション/キャッシュ有無/バッファ・レジスタを持つ。
- リージョンは4kB〜4GBのサイズであり、必ず2のべき乗である。
- すべてのリージョンは開始アドレスを持ち、リージョン・サイズに応じてアライメントされている。もしリージョンが8kBなら、bit13未満がすべて0である。
- 1つのリージョンは、16のサブ・リージョンを持ち、
- サブ・リージョンのサイズは、リージョンを16分割した256B〜256MBである。
- MPUにより設定したメモリ・リージョンにアクセスしたとき、ハードウェアは、その中でヒットしたサブ・リージョンを決定する。この決定に使用される情報は、サブ・リージョンのアクセス許可がレジスタ群MPUAPRA/MPUBRA/MPUCRAかMPUAPRB/MPUBRB/MPUCRBで与えられている(訳注:レジスタMPUPSRnで選択)。
- どのリージョンにもアクセスがヒットしないなら、アクセスは中断され、例外が発生する。
AT32UC3 Series Software Frameworkのダウンロードはここから。今回使用したのは「AVR32 UC3 Software Framework 1.5.0」である。
ドキュメントは、AVR32 Studioの「Help」→「Help Contents」でヘルプを開き、「AT32UC3B Software Framework」→「Drivers」→「CPU」→「Memory Protection Unit」と辿ればよい。いちおうフッタにあるバージョンを確認すること。現在のFramework最新版は「1.5.5」だ。
MPUドライバの例題プロジェクトを作成する。
- AVR32 Studioのメニュー「File」→「New」→「Example」を選択。
- まずは「Wizards」に"EVK1101"と入力し、候補を絞り込む。
- "EVK1101 - DRIVERS - CPU MPU example"を選択し、Nextボタンを押す。
- 適当なプロジェクト名を「Project name」に設定、残りはお好みで、Finishボタンを押す。
出来上がったプロジェクトのsrcフォルダ以下を見ると、思った以上に余計なものが取り込まれている。例題が要求するのか、MPUドライバが要求するのか、見分ける必要がある。
ソースファイル名 | mpu.cからの依存性 | 説明 |
---|---|---|
crt0.x | × | 通常のC言語スタートアップ・ルーチン |
gpio.c | × | mpu_example.cから利用。LED点灯に使用されている。led.cを使えばいいのに。 |
intc.c | × | 汎用的な割込処理 |
led.c | × | LEDボード・ドライバ。例題で使用。 |
mpu.c | − | MPUドライバ本体。関数呼び出し無し。 |
mpu_example.c | △ | この例題のmain処理。関数handle_exception(例外処理本体)もここにある。 |
mpu_exception.x | △ | exception.cの変更版(exception.xは使用されない)。64〜69行目が0x100未満のハンドラ、71〜79行目が0x100(supervisor call)のハンドラの二種類が登録されている。いずれも関数handle_exceptionを呼び出す。 |
pm.c | × | mpu_example.cの中でクロック源を外部クリスタルに切り替えるため、利用している(必要なのか?)。 |
pm_conf_clocks.c | × | pm.cの使用法を簡素化するものだが、どこからも使用されていない... |
print_funcs.c | × | print関数群。USARTドライバ上位のデバッグ・モジュール |
trampoline.x | × | リセット・ハンドラ |
usart.c | × | USARTドライバ。例題で使用。 |
依存性はほとんど無いといっていいだろう。mpu_example.cはmpu.cの使用方法を示すものだし、mpu_exception.xは例外処理本体であり、アプリ毎に変わるだろう。
つまりMPUドライバは単独利用できると言える。
「AVR32のMPUドライバの例題を使ってみる(その2)」へ続く。
スタック・サイズのデフォルト値
リンカ・スクリプトまで追いかけて、スタック・サイズを規定するシンボルが_stack_sizeであることまでは判ったが、そのシンボル定義が見つからない。
リンク・オプションでもないらしい。じゃあリンカそのものに埋め込まれているのか?
という話の続きである。
リンカ外部から突き詰めていくという手もあるのだろうけど、ここは直接ソースコードを当たることにした。
AtmelのサイトからToolchainのソースをダウンロードする。今回は
avr32_gnu_toolchain_2.2.1_source.zip
だ。Linux用ではあるがソースコードを確認するだけなので、これでよい。
このzipファイルを解凍ソフトで展開すると、複数の.tar.gzファイル他が出来る。リンカgnu-ldはbinutilsに含まれるので、次にavr32-binutils-2.19.atmel.1.0.0.tar.gzを解凍する。
解凍したディレクトリの中にある「ld」ディレクトリが目的のソースファイルを格納している。
このディレクトリの中で「_stack_size」を検索すると、avr32関係では以下の2つが見つかった。
emulparams\avr32elf.sh
emultempl\avr32elf.em
avr32elf.shはシェル・スクリプトでその記述内容からすると、リンカ・スクリプトを生成するものらしい。昨日見たリンカ・スクリプトと同じ記述が見つかる。AVR32 Studio内でこのシェル・スクリプトを起動しているかと思い、AVR32 Studioのインストール・ディレクトリ以下を検索したがこのシェル・スクリプトは存在しなかった。どうやらToolchainを生成するときだけ実行するもののようだ。
avr32elf.emの方を見る。.emという拡張子はよく知らないが、ファイルの先頭に
# This shell script emits a C file. -*- C -*-
# Copyright (C) 2007 Atmel Corporation
#
# This file is part of GLD, the Gnu Linker.
と書かれているので、gnu-ldの一部のようだ。更に読み進めるとC言語の記述そのもののように見える。この中で「_stack_size」を調べると
64: lang_add_assignment (exp_assop ('=', "_stack_size", 65: exp_intop (stack_size)));
とあり
54: static int stack_size = 0x1000;
が見つかる。
と言う訳で、_stack_sizeはリンカが内部定義したシンボルで、初期値が0x1000ということである。あー、すっきりした。
それでは判ったことで何か良いことがあるかというとそうでもない。
AVR32 STudioでは、この_stack_sizeを書き換える手段を提供していないからだ。通常ならプロジェクトのプロパティ画面から値を設定できそうなものだが、そうはなっていない。
となると、自分でリンカ・スクリプトを書き換えるしかない訳だが、それならなにも_stack_sizeを変更する必要はなく、自分で好きなようにスタックを定義すればいいのである。
今、MPU(Memory Protection Manager)ドライバの例題を見ているのだが、例題にはリンカ・スクリプトが付いていて、そのスタック関係の記述は以下のようになっている。
72: __stack_size__ = DEFINED(__stack_size__) ? __stack_size__ : 4K; 258: .stack ORIGIN(INTRAM) + LENGTH(INTRAM) - __stack_size__ : 259: { 260: _stack = .; 261: *(.stack) 262: . = __stack_size__; 263: _estack = .; 264: } >INTRAM AT>INTRAM :INTRAM
このような感じで自分で定義してしまえばよいのである。
2009/11/20 追記
自己完結したが、デフォルト・スタック・サイズの指定方法を見つけた。
"%AVR32_HOME%\bin\avr32-gcc" --target-help
とすると表示されるターゲット毎に指定可能オプションとして
- stack
というのがあり、プロジェクトのプロパティ画面からリンカ・オプションに指定する(まだ試していないけど)。
AT32UC3B0256のデフォルト・リンカ・スクリプト
AT32UC3B0256のスタンドアローン版アプリケーションで暗黙的に使用されるリンカ・スクリプトを読んでみる。リンカ・スクリプトの格納位置についてはこちらを。
今回読むリンカ・スクリプトはavr32elf_uc3b0256.xだ。
参考URL GNU リンカ LD の使い方 − 3. リンカスクリプト
2: OUTPUT_FORMAT("elf32-avr32", "elf32-avr32", 3: "elf32-avr32") 4: OUTPUT_ARCH(avr32:uc)
OUTPUT_FORMATは出力フォーマットを指定し、エンディアンを指定するリンカ・オプションの「-EB」や「-EL」により第1〜3引数のいずれかが有効になる。指定がなければ第1引数、-EBなら第2引数、-ELなら第3引数である。どれを指定しても"elf32-avr32"である。ELFフォーマットはエンディアン種別を格納できるので、これで問題ないのだろう。
OUTPUT_ARCHは出力機種アーキテクチャを指定する。指定可能なアーキテクチャはWindowsなら以下のコマンドで判る。
"%AVR32_HOME%\bin\avr32-objdump" --help
末尾に「supported architectures:」と書いてある部分がそれだ。「avr32:uc」はucr1やucr2の共通のようだ。ucr3に相当するものが無いが...
5: ENTRY(_start)
エントリポイントを指定する。オプションで指定されていれば、オプションが優先する。余談だが、エントリポイントの決定は以下の優先度がある。
- コマンド・オプション「-e」
- リンカスクリプトのENTRY(symbol)コマンド
- 定義されていれば、シンボル start の値
- 存在するなら、.textセクションの先頭のバイトのアドレス
- アドレス 0
6: SEARCH_DIR("/home/mingwbuild/mingwavr32/avr32/lib");
ライブラリ・ファイル検索パスに"/home/mingwbuild/mingwavr32/avr32/lib"を追加する。しかし、そのようなパスはインストール・ディレクトリ以下にも存在しない。
7: /* Do we need any of these for elf? 8: __DYNAMIC = 0; */
コメントでそんなこと書かれても...たぶんダイナミック・ライブラリ(gcc的にはシェアード・ライブラリか)のとき必要かも、って他人のことは言えませんな。
9: MEMORY 10: { 11: FLASH (rxai!w) : ORIGIN = 0x80000000, LENGTH = 256K 12: CPUSRAM (wxa!ri) : ORIGIN = 0x00000000, LENGTH = 32K 13: USERPAGE : ORIGIN = 0x80800000, LENGTH = 512 14: FACTORYPAGE : ORIGIN = 0x80800200, LENGTH = 512 15: }
MEMORYコマンドはターゲットのメモリブロック=リージョンの位置と大きさを記述する。ブロック内の一般形は以下の通り。
name [(attr)] : ORIGIN = origin, LENGTH = len
nameはリージョン名。属性attrは省略可能。originが開始位置、lenが大きさである。attrを構成する文字の意味は以下の通り。
R | 読み出し専用セクション |
W | 読み書き両用セクション |
X | 実行可能セクション |
A | 割り当て可能セクション |
I | 初期化済みセクション |
L | Iと同じ |
! | 後に続く属性の意味を否定 |
RとWの意味がread/writeでないのが要注意。チップ内蔵SRAMのリージョンCPUSRAMが「!R」となるのが象徴的。リージョンUSERPAGEとFACTORYPAGEの役割は最後まで読んでもよく判らない。該当するアドレスになにがあるのか製品マニュアルやアーキテクチャ・マニュアル見ても載っていない。
16: SECTIONS 17: { 18: /* Read-only sections, merged into text segment: */ 19: PROVIDE (__executable_start = 0x80000000); . = 0x80000000;
SECTIONSコマンドは、オブジェクトファイル等の入力セクションから出力セクションへの対応方法、それに出力セクションのメモリ配置方法について指示する。
SECTIONSコマンドのブレイス記号({}のこと)内にセクション・コマンドを複数行にわたり記述できる。セクション・コマンドには以下のものがある。
- ENTRYコマンド
- シンボル代入
- 出力セクション記述
- オーバーレイ記述
詳細については参考URLを参照して欲しい。
19行目の「PROVIDE」は、シンボル__executable_startが入力オブジェクトで参照されていて、かつ未定義のとき、__executable_startを値0x80000000で定義する(一種のシンボル代入)。入力に定義があればそちらを使用する。同じ行の後半(1つ目のセミコロン以降)にはもう1つシンボル代入があり、位置(ロケーション)カウンタに0x80000000を代入している。セミコロンは代入文の数式の直後に必須だ。他の記述ではセミコロンを付けないので注意が必要だ。
20: .interp : { *(.interp) } >FLASH AT>FLASH
これは出力セクション記述である。先頭の「.interp」が出力セクション名、アドレスとタイプが省略されていて、「*(.interp)」が出力セクション・コマンドの入力セクション記述である。「>FLASH」がリージョンで、「AT>FLASH」がLMAリージョンである。
セクション名の前後に空白が必須、またコロンとブレイスも必須である。省略されたアドレスには、リージョンが存在すればその値が、存在しなければ位置カウンタの値が使用される。
出力セクション・コマンドには、以下の4つが記述できる。
- シンボル代入
- 入力セクション記述
- 直接取り込むデータ値
- 特別な出力セクション・キーワード
入力セクション記述の一部のみ説明する。「*(.interp)」のケースで説明すると、「*」はすべての入力オブジェクトファイル名(つまりワイルドカード)を示し、「.interp」がそのファイル内のセクションを示す。特定の入力ファイルを記述すれば、同一セクションでも別々に入力できる。また空白で区切り複数のセクション名を記述可能だ。このケースでは、全入力ファイルのセクション.interpが入力セクションとなる。
30: .rel.text : { *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*) } >FLASH AT>FLASH
これが複数入力セクションの例だ。
出力セクションは仮想アドレス(VMA)とロードアドレス(LMA)を持つ。先の記述に出てきたリージョンとLMAリージョンが、それぞれVMAとLMAに対応する。「AT>」の記述が無ければ、VMAとLMAは同じに設定されるが、「AT>」によりLMAを異なるLMAリージョンに変更できる。これは(C言語の初期値付きstatic変数のような)ROMイメージを作成するときに使用する。
95: .init_array : { KEEP (*(.init_array)) } >CPUSRAM AT>FLASH
これなどはROMイメージの例だ。FLASHのLMAで出力され、その後CPUSRAMのVMAへ転送される(アドレスが設定されるだけで転送はプログラムの仕事)。
VMAとLMAが一致していれば「AT>」は不要だが、このリンカ・スクリプトでは省略されていないケースがほとんどだ。
54: .init : 55: { 56: KEEP (*(.init)) 57: } >FLASH AT>FLASH =0xd703d703
この出力セクション記述は、KEEP()と=0xd703d703という記述方法が加わっている。
このリンカ・スクリプトには多くの出力セクション・コマンドがあるが、実際に出力されているセクションはもっと少ない。これはリンク・オプション--gc-sectionsが指定されているとき、入力のないセクションを出力から削除するためである。入力が無くても出力したければ、入力セクション記述で「KEEP(*(.init))」のようにすればよい。従って、この.initセクションは必ず出力される。
「=0xd703d703」は出力セクション埋め潰し属性で、セクションに属し、かつ指定のない部分を指定データを繰り返しながら埋め潰す。ちなみに0xd703はAVR32のnop命令であるが、個人の好みとしては後続が何なのか不安なため、「rjmp 0」の0xc008の方が良いと思う。
59: .text : 60: { 61: *(.text .stub .text.* .gnu.linkonce.t.*) 62: KEEP (*(.text.*personality*)) 63: /* .gnu.warning sections are handled specially by elf32.em. */ 64: *(.gnu.warning) 65: } >FLASH AT>FLASH =0xd703d703
これは今まで説明した内容を越えないが、複雑な記述だ。
出力セクション.textには、ワイルドカードを含む複数のセクションを入力し、そのうち*(.text.*personality*)に該当するものは内容が無くても残し、リージョンFLASHに出力、隙間は0xd703d703で埋める。
70: PROVIDE (__etext = .); 71: PROVIDE (_etext = .); 72: PROVIDE (etext = .);
セクション.textの末尾を示すためによく使われるシンボル__etext、_etext、etextを自動的に生成している。
76: .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) } >FLASH AT>FLASH
ONLY_IF_ROは「もし入力セクションがread onlyなら」入力するということらしい(読んでそのままだが)。これとは別にONLY_IF_RWもある。
78: .dalign : { . = ALIGN(8); PROVIDE(_data_lma = .); } >FLASH AT>FLASH 79: PROVIDE (_data = ORIGIN(CPUSRAM)); 80: . = ORIGIN(CPUSRAM);
78行目は、位置カウンタを8でアライメントし、_data_lmaが未定義のときそのアドレスをシンボル_data_lmaに定義する。
79行目は、シンボル_dataが未定義のとき、リージョンCPUSRAMの先頭アドレスを_dataに定義する。つまりC言語ソースでグローバル変数dataを定義すると恐ろしいことが起きる...かもしれない。
80行目は、位置カウンタにリージョンCPUSRAMの先頭を設定する。これ以降がSRAMに配置(出力)するセクションになる。もちろん初期値が必要なセクションは「AT>」でROM化しておくことになる。
86: .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } >CPUSRAM
.tbssはTLS(スレッド・ローカル・ストレージ)の初期化無しデータ領域なので、「>CPUSRAM」のみが記述されている(「AT>」が付かない)。
131: .data : 132: { 133: *(.data .data.* .gnu.linkonce.d.*) 134: KEEP (*(.gnu.linkonce.d.*personality*)) 135: SORT(CONSTRUCTORS) 136: } >CPUSRAM AT>FLASH
「SORT(CONSTRUCTORS)」について。まずSORT()は出力する前に、ファイル名やセクション名について昇順ソートしてから出力する(デフォルトは入力順)。シンボルを探すとき高速になるからと思われる。
CONSTRUCTORSは出力セクションキーワードの1つであり、C++のコンストラクタとデストラクタに対応するためのものである。たぶんC++はオーバーロードのため、これらやメソッドのシンボルを特殊な方法で命名している。そのために必要な記述なのだろう。
142: .bss : 143: { 144: *(.dynbss) 145: *(.bss .bss.* .gnu.linkonce.b.*) 146: *(COMMON) 150: . = ALIGN(8); 151: } >CPUSRAM
COMMONはコモンシンボル用の入力セクションである。コモンシンボルは現在あまり使われていないようなので無視する。
155: __heap_start__ = ALIGN(8); 156: . = ORIGIN(CPUSRAM) + LENGTH(CPUSRAM) - _stack_size; 157: __heap_end__ = .;
__heap_start__と__heap_end__はヒープ領域の先頭と末尾を示すシンボルである。
ここまでSRAMに割り付けたセクション・サイズ分だけ位置カウンタが移動しているので、現在の位置カウンタからSRAM末尾までが未使用領域である。この未使用領域からスタックを除いた部分をヒープ領域とする、
155行目で、位置カウンタをアライメントしてヒープ先頭とする。
156行目で、位置カウンタをリージョンCPUSRAMの末尾−スタック・サイズとする、
157行目で、位置カウンタをヒープ末尾+1とする。ここでアライメントを取らないのは「スタック・サイズは常識で8や4の倍数だろ」ということらしい。
ところでSRAM出力セクションとスタックだけでSRAMサイズをオーバーしても、この記述だとエラーが出ない(後ろの.stackでエラーが出る)。また、__heap_start__と__heap_end__はセクションに属さない。
この後に続くのはデバッグ関係セクションである。この辺は知識が乏しいのでパス。
ヒープを計算してしまったので、SRAMに出力する出力セクション記述はスタックのみだ。
191: .stack ORIGIN(CPUSRAM) + LENGTH(CPUSRAM) - _stack_size : 192: { 193: _stack = .; 194: *(.stack) 195: . = _stack_size; 196: _estack = .; 197: } >CPUSRAM
デバッグ関係の出力セクションで位置カウンタが変更されているので、アドレスを計算し直している(ヒープの直後に記述しないのはなぜ?)。_stack_sizeが4の倍数でないと、スタック関連のシンボルがアライメントされず、たぶん例外が発生するだろう。
_stackはスタック先頭アドレスを示すシンボル、_estackは末尾+1を示すシンボルである。位置カウンタにスタック・サイズ_stack_sizeを設定すると、なぜスタック末尾+1になるんだろう?
195: . = . + _stack_size;
なら解るけど。
試しにリンカ・スクリプトを修正してみた。
修正箇所 | _stack, _estack | 備考 |
---|---|---|
195: /*. = _stack_size;*/ | 0x00007000, 0x00007000 | セクション.stack消滅 |
195: . = . + _stack_size; | 0x00007000, 0x00008000 | |
195: . += _stack_size; | 0x00007000, 0x00008000 | |
195: . = ORIGIN(CPUSRAM) + LENGTH(CPUSRAM); | - | リンク・エラー |
195: . = 0x8000; | - | リンク・エラー (0x8000はSRAM末尾+1) |
195: . = 0x7000; | - | リンク・エラー (0x7000はスタック先頭) |
195: . = 0x1000; | 0x00007000, 0x00008000 | |
195: . = 0x1001; | - | リンク・エラー |
195: . = 0x100; | 0x00007000, 0x00007100 |
「.」への代入が入力セクション記述内では相対移動になるようだ。
156行目のように入力セクション記述ではないときと異なるようだ。
198: .userpage : { *(.userpage .userpage.*) } >USERPAGE AT>USERPAGE 199: .factorypage : { *(.factorypage .factorypage.*) } >FACTORYPAGE AT>FACTORYPAGE
ずっと上の方で「役割が判らない」と書いたUSERPAGEとFACTORYPAGEへ出力するセクション.userpageと.factorypage。
200: /DISCARD/ : { *(.note.GNU-stack) }
/DISCARD/は出力セクション削除で、入力セクションは捨てられる。
とりあえずスタンドアローンな組み込みプログラムなら、この程度リンカ・スクリプトが読めれば問題ないだろう。
しかし、「スタックの指定はどこにあるのだろう?」という当初の問題は解決しなかった。リンカ・スクリプトで「_stack_size」を参照しているのだから、リンカ・オプションか、リンカに組み込まれているかのいずれかだと思う。しかし、Console出力にあるリンカ・オプションには出てこないし、リンカに埋め込まれていたら変更できないし、どうなっているんだろう。
194: *(.stack) 195: . = _stack_size; 196: _estack = .;
194行目のセクション.stack入力があると、入力セクション・サイズ+_stack_sizeのスタックができる。_stack_sizeを変更せずに、こういう方法でスタックを拡張しろということか?
余分(_stack_size)なスタックまで確保してしまい無駄だなあ。
デフォルトのリンカ・スクリプトの場所
以前「スタックの指定はどこにあるのだろう?」みたいなことを書いたが、セクションの配置はリンクのお仕事なので、オプションに指定がなければ、リンカ・スクリプトにあるだろうとは察しがついていた。
今まではソースコード中心に見てきたので放っておくことにしたが、あらかたソースコードは見たので、リンカ・スクリプトを読むことにする。
デフォルトのリンカ・スクリプトは、Windowsの標準でインストールしたなら以下のディレクトリに格納されている。
C:\Program Files\Atmel\AVR Tools\AVR32 Toolchain\avr32\lib\ldscripts
ただし、ファイルは1つでない。すべてAT32UC3B0256のELF作成用である。descriptionは、各ファイル先頭に記述されたコメントを書き写したもので、内容を理解してはいない。
file name | description |
---|---|
avr32elf_uc3b0256.x | Default linker script, for normal executables |
avr32elf_uc3b0256.xbn | Script for -N: mix text and data on same page; don't align data |
avr32elf_uc3b0256.xn | Script for -n: mix text and data on same page |
avr32elf_uc3b0256.xr | Script for ld -r: link without relocation |
avr32elf_uc3b0256.xu | Script for ld -Ur: link w/out relocation, do create constructors |
avr32elf_uc3b0256.xwr | Linker script for writable rodata |
はて?スタンドアローン版で読み込まれるのはどれだろう?たぶん.xか.xwrだろうけど。とりあえずavr32elf_uc3b0256.xのファイル名を変更した上でビルドしてみると
toolchain/bin/../lib/gcc/avr32/4.3.2/../../../../avr32/bin/ld.exe: cannot open linker script file ldscripts/avr32elf_uc3b0256.x: No such file or directory
めでたく(?)avr32elf_uc3b0256.xであることが確認出来た。
今後、リンカ・スクリプトも勉強目的で読む予定。