CodeWarrior が作る ColdFire V1 の割り込みサービスルーチンを調べた [ColdFire (ColdeFire) V1]
CodeWarrior V6.1 で ColdFire V1 のプログラムを作成するとき、 割り込みサービスルーチン (Interrupt Service Routine : ISR) を どうやって表現するかを考えました。 しかも、アセンブラで。
ColdFireでは、実行中のプログラムをある種の要因で中断する操作を「例外」と呼んでいます。 ここでは、なじみの言葉である「割り込み」を使っていますが、 扱っている内容は、「例外」でも同じです。
Cでの基本的な記述方法
Cで ISR を記述するときには、普通の関数定義の戻り値型の代わりに、 「__interrupt <ベクタ番号>」を追加します。
__interrupt VectorNumber_Vtrap0 trap0_isr (void) { }
これで、CodeWarriorは、正しいISRを作成してくれます。
0x00000000 _trap0_isr: ; trap0_isr: 0x00000000 0x4E73 rte 0x00000002 0x51FC trapf
普通の関数の最後が"RTS" (ReTurn from Subroutine) 命令になっているところが、 "RTE" (ReTurn from Exception) 命令になっています。 この結果を見て、 「なんだ、そんなに難しい話じゃないんだな。」と、最初は思ったのです。
保護すべきレジスタは、どれだ
ColdFireでは、割り込みが発生してもレジスタを自動的にはスタックに退避してはくれません。 これは、HCS08と異なっている点です。 そのため、ISRの中でレジスタを退避させる操作が必要になってきます。
まずは、こんなプログラムを作成してみました。
typedef long Data; typedef long *Pointer; void func(void) { Data x1,x2,x3,x4,x5,x6,x7,x8; Pointer y1,y2,y3,y4,y5,y6,y7,y8; for (x1=0,y1=0;x1<10;x1++,y1++) for (x2=0,y2=0;x2<10;x2++,y2++) for (x3=0,y3=0;x3<10;x3++,y3++) for (x4=0,y4=0;x4<10;x4++,y4++) for (x5=0,y5=0;x5<10;x5++,y5++) for (x6=0,y6=0;x6<10;x6++,y6++) for (x7=0,y7=0;x7<10;x7++,y7++) for (x8=0,y8=0;x8<10;x8++,y8++) { } } __interrupt VectorNumber_Vtrap0 trap0_isr (void) { Data x1,x2,x3,x4,x5,x6,x7,x8; Pointer y1,y2,y3,y4,y5,y6,y7,y8; for (x1=0,y1=0;x1<10;x1++,y1++) for (x2=0,y2=0;x2<10;x2++,y2++) for (x3=0,y3=0;x3<10;x3++,y3++) for (x4=0,y4=0;x4<10;x4++,y4++) for (x5=0,y5=0;x5<10;x5++,y5++) for (x6=0,y6=0;x6<10;x6++,y6++) for (x7=0,y7=0;x7<10;x7++,y7++) for (x8=0,y8=0;x8<10;x8++,y8++) { } }
プログラム自体に意味はありません。 全てのレジスタを使ってもまかなえないほどの局所変数を使わせたかったのです。 このプログラムをコンパイルさせると、それぞれの関数の冒頭は、以下のようになっています。
0x00000000 _func: ; func: 0x00000000 0x4FEFFFD0 lea -48(a7),a7 0x00000004 0x48EF5CF8000C movem.l d3-d7/a2-a4/a6,12(a7)
関数の中では、D0-D7/A0-A2/A4/A6を作業領域として使っています。 つまり、D0/D1/D2/A0/A1の五つのレジスタはまったく保護されていません。
0x00000000 _trap0_isr: ; trap0_isr: 0x00000000 0x4FEFFFBC lea -68(a7),a7 0x00000004 0x48EF5FFE0010 movem.l d1-d7/a0-a4/a6,16(a7)
これに対して、ISRの中では、D1-D7/A0-A4/A6を作業領域として使っています。 つまり、全てのレジスタが保護されていることになります。 なぜ、D0を使っていないのか? 理由は不明です。
関数の中にアセンブラを混ぜる
CPUの動作にかかわるような低レベルな操作は、アセンブラを使う必要があります。 Cで記述された関数の中にアセンブラでプログラムを記述するときには、 "__asm"キーワードを使います。
Data value; __interrupt VectorNumber_Vtrap1 trap1_isr (void) { __asm { move usp,a0 move (a0),d0 move d0,value } }
このISRでは、ユーザ・スタックの内容を取り出して変数valueに 代入しています。 取り出した内容に特に意味があるわけではありません。 このプログラムをコンパイルした結果を見てみます。
0x00000000 _trap1_isr: ; trap1_isr: 0x00000000 0x2F08 move.l a0,-(a7) 0x00000002 0x2F00 move.l d0,-(a7) ; ; 53: move usp,a0 ; 0x00000004 0x4E68 move usp,a0 ; ; 54: move (a0),d0 ; 0x00000006 0x2010 move.l (a0),d0 ; ; 55: move d0,value ; 56: } ; 0x00000008 0x23C000000000 move.l d0,_value ; ; 57: } ; 0x0000000E 0x201F move.l (a7)+,d0 0x00000010 0x205F movea.l (a7)+,a0 0x00000012 0x4E73 rte
どうやら、アセンブラ・プログラムの中でD0とA0が作業領域として使われているのを認識して、 その値をスタックに退避させてくれるようです。
アセンブラだけで記述したC関数?
上と同じ操作をアセンブラだけで、このように書くことが出来ます。
asm __interrupt VectorNumber_Vtrap2 trap2_isr (void) { move usp,a0 move (a0),d0 move d0,value rte }
これをコンパイルすると、このようになります。
0x00000000 _trap2_isr: ; trap2_isr: 0x00000000 0x4E68 move usp,a0 ; ; 61: move (a0),d0 ; 0x00000002 0x2010 move.l (a0),d0 ; ; 62: move d0,value ; 0x00000004 0x23C000000000 move.l d0,_value ; ; 63: rte ; 0x0000000A 0x4E73 rte
この場合には、レジスタを退避させるコードは生成されません。 従って、正しいISRとするために、レジスタを退避させるコードを追加する必要があります。 ただ、コンテキストスイッチに使う場合のように、勝手にスタックを使われると困る場合もあるので、 一概にどちらが使いやすいとも言えません。 はっきり言えるのは、「コンパイル結果を確認しながら使う」事が必要だということでしょう。
Cとアセンブラの連携
このコンパイラのアセンブラは、かなり賢くて、Cで定義した変数などをそのまま使うことができます。
__interrupt VectorNumber_Vtrap3 trap3_isr (void) { Pointer y1; __asm { move usp,a0 move a0,y1 } value = *y1; }
これもプログラム自身には、さして意味がありません。 uspレジスタの内容をCの局所変数であるy1に代入し、 Cの記述で、スタックの内容を取り出して、valueに保存します。 コンパイルするとこうなります。
0x00000000 _trap3_isr: ; trap3_isr: 0x00000000 0x4FEFFFF0 lea -16(a7),a7 0x00000004 0x48EF03020004 movem.l d1/a0-a1,4(a7) ; ; 71: move usp,a0 ; 0x0000000A 0x4E68 move usp,a0 ; ; 72: move a0,y1 ; 73: } ; 0x0000000C 0x2E88 move.l a0,(a7) ; ; 74: value = *y1; ; 0x0000000E 0x2257 movea.l (a7),a1 0x00000010 0x2211 move.l (a1),d1 0x00000012 0x2B410000 move.l d1,_value(a5) ; ; 75: } ; 0x00000016 0x4CEF03020004 movem.l 4(a7),d1/a0-a1 0x0000001C 0x4FEF0010 lea 16(a7),a7 0x00000020 0x4E73 rte 0x00000022 0x51FC trapf
Cでは書けないUSPレジスタの値を取り出す部分だけを アセンブラにしただけなので、 取り出した値の操作はCで記述することが出来て便利です。
コメント 0