start.asmは、リセット後のエントリ・ポイントであるstartから始まる重要な初期化ルーチンを持っています。この初期化部は、便宜的にevent_stackを作業用スタックとして利用し、割り込みディスパッチャを設定した後にIVG15に遷移してkernel_start()へ移行します。
ITRONでは割り込 みハンドラもC言語で書きます。しかしハードウェアからの割り込みをC言語の関数に渡すにはそれなりのトリックが必要です。m68kの実装を見るとこれは ラッパーマクロで対応しています。関数名をマクロに与えると、その場で関数を呼び出す割り込みコードを生成します。あとはそのコードを割り込みベクトルに 登録するだけです。
BlackfinはRISCタイプであるため、この方式ではマクロのサイズが肥大化します。これはメモリーに敏感なDSPにはよくないので違う方式を取る事にしました。VisualDSP++に近い中央ディスパッチ方式です。
今回の実装では割り込みディスパッチャーは次のような構成になっています。
このうち前処理部分だけが割り込みレベルごとに独立しており、残りは共通コードです。
Blackfinは割り込みを受け付けると登録されたアドレスにジャンプします。ジャンプ先にあるのが前処理部です。この部分は割り込み順位ごとに独立して存在します。そのためこの部分を小さくすればメモリーを節約できます。
前処理部は続くハンドラ呼び出し部に割り込み順位に応じた情報を渡すのが主な仕事です。実際のコードは以下のように割り込み順位番号をp0に設定してジャンプしています。
ivg11handler:
[--sp] = p0;
p0 = 11;
jump interrupt_dispatcher;
ivg11handler.end:
最初にパラメータとして使うp0レジスタを保存しています。
ハンドラ呼び出し部は実際の処理の中心となる部分です。冒頭でユーザーレジスタをすべ て退避した後、Lxレジスタを0クリアします。これらのレジスタはC言語の実行環境では0であることが求められますが、割り込みがアセンブリ言語部でおき ると0とは限らないため初期化が必要です。
次にIPENDレジスタを調べて割り込みがネストしたものかどうかを調べます。ネストしていない場合にはユーザースタックをイベント処理スタックに変更します。
ユーザー割り込みハンドラはデバイスの割り込み番号ごとに登録されており、その振り分けはdevice_dispatcher(int)が行います。ですのでこの部分はdevice_dispatcher()関数を割り込みレベルを引数として呼び出すだけです。レベルからデバイスの割り込み番号への変換は呼び出された側で行います。
ユー ザーの割り込みハンドラを呼んだあとは、先の逆順です。ネストしていた場合にはそのままレジスタを復帰して割り込みからもどります。ネストしていない場合 にはスタックをユーザースタックに戻してreqflgをチェックしてディスパッチかタスク例外要求が出ていないか確認します。要求がなければ出口処理に移 ります。
interrupt_dispatcher:
save_regs; // ユーザーレジスタの退避
r0 = 0;
l0 = r0;
l1 = r0;
l2 = r0;
l3 = r0;
p1.L = IPEND&0xffff; // IPENDレジスタのアドレス
p1.H = IPEND>>16;
r0 = [p1]; // IPENDの値を取得
r1 = 0x7fef; //
r0 = r0 & r1; // IVG15とGID以外を調べる
r0.L = ones r0; // 処理中のイベントの数を数える
r0 += -1; // イベントの数-1。ネストしていなければ0になる。
cc = az; // ネストしてなければ真
if !cc jump call_interrupt; // もしネストしていればハンドラを呼ぶ
// ネストしていない割り込みなら、スタックを切り替える
r6 = sp; // タスクスタックポインタを退避
sp.l = event_stack; // イベントスタックポインタを設定
sp.h = event_stack;
call_interrupt:
[--sp] = reti; // ここで割り込み可能になる
r7 = astat; // ccにはネスト情報が入っている。それを退避
sp += -12; // ダミー引数確保
r0 = p0; // 割り込み順位
p0.L = _device_dispatcher;
p0.H = _device_dispatcher;
call (p0); // C言語で書かれたデバイスディスパッチャを呼ぶ
sp += 12; // ダミー引数開放
astat = r7; // ccを復帰
reti = [sp++]; // ここで再び割り込み禁止
if !cc jump get_back; // もしネストしているならば、このまま帰る
// ここでは割り込みはネストしていない
sp = r6; // スタックをタスクスタックに設定
p0.H = _reqflg;
p0.L = _reqflg; // reqflgのアドレス
r0 = [p0]; // reqflgの値を取得
cc = r0; // ディスパッチかタスク例外の要求は出ているか
if !cc jump get_back; // 出ていないならば割り込まれたタスクに戻る
// コンテキスト切り替え作業開始
r0 = 0; //
[p0] = r0; // reqflgをクリアする.
r0 = reti; // タスクの戻り番地を取り出す
[--sp] = r0; // 戻り番地をスタックにつむ
p0.L = task_context;
p0.H = task_context;
reti = p0; // ラベル"task_context"を割り込みからの戻り番地にする
rti; // 割り込みから戻る。コンテキストはタスクになる
出口処理は割り込みディスパッチャの後半に位置します。この部分はTOPPERS/JSPの中でも一番トリッキーな部分になります。タスク・ディスパッチャが比較的簡単に実装できるのに対して、この部分は割り込みの禁止や状態の遷移が込み入っているため、移植時には注意が必要です。
こ の部分に入ってきた時点でタスク・ディスパッチかタスク例外処理の要求が出ていることになっています。ITRON4の要求によりこの二つの処理はタスク・ コンテキストで行わなければなりませんので最初にコンテキストの遷移を行います。これはrti命令によってダミーアドレスにジャンプすることで実行しま す。当然本来の戻り番地は保存しておきます。
タスク・ディスパッチと例外はそれぞれC言語によるディスパッチ関数と例外コールバックが用意されていますのでそれを呼びます。
最 後に、割り込まれたタスクに戻りますが、すでにタスク・コンテキストに遷移しておりまっとうな方法では戻れません。この部分は一番工夫を要しました。結局 excpt命令による例外経由で割り込まれたタスクへの戻りを実行することにしました。まず戻り番地をスタックにプッシュします。次にexcpt命令で例 外を発生させます。
// コンテキスト切り替え作業開始
r0 = 0; //
[p0] = r0; // reqflgをクリアする.
r0 = reti; // タスクの戻り番地を取り出す
[--sp] = r0; // 戻り番地をスタックにつむ
p0.L = task_context;
p0.H = task_context;
reti = p0; // ラベル"task_context"を割り込みからの戻り番地にする
rti; // 割り込みから戻る。コンテキストはタスクになる
task_context: // ここはタスクコンテキストで、割り込み可能である。
p0.L = _enadsp;
p0.H = _enadsp;
p1.L = _runtsk;
p1.H = _runtsk;
r2 = [p0]; // load enadsp
cc = r2; // ディスパッチ可能か?
if !cc jump go_tex; // もしディスパッチ禁止なら例外チェックに
p0.L = _schedtsk; // ディスパッチ可能の場合
p0.H = _schedtsk;
r1 = [p1]; // runtsk
r0 = [p0]; // schedtsk
cc = r0 == r1; // schedtsk == runtsk か?
if cc jump go_tex; // もし schedtsk == runtsk ならば、タスク例外に行く
// そうでなければディスパッチする
r0 = 0xc01F(z); // dispatch()はCPUロック状態で呼ぶ
sti r0; // CPUロック状態
sp += -12; // ダミー引数領域確保
call _dispatch; // レッツゴー
sp += 12; // ダミー引数領域開放
r0 = 0xffff(z);
sti r0; // CPUロック解除
jump return_as_task; // タスクに戻る
go_tex:
p1 = [p1]; // runtsk
r0 = [p1 + TCB_enatex]; // runtsk->enatexを取得
r1.H = ( TCB_enatex_mask >> 16 ) & 0xffff;
r1.L = TCB_enatex_mask & 0xffff;
r1 = r0 & r1; // タスク例外は許可されているか
cc = az;
if cc jump return_as_task; // 許可されていなければタスクに戻る
r0 = [p1 + TCB_texptn]; // runtsk->texptrnを取得 ( UINT )
cc = r0; // texptnはセットされているか
if !cc jump return_as_task; // セットされていないなら要求は無い。タスクに戻る。
sp += -12; // ダミー引数領域確保
call _call_texrtn; // 条件がそろったのでタスク例外処理を行う。
sp += 12; // ダミー引数領域開放
return_as_task: // タスクコンテキスト状態での戻り
csync;
excpt RETURNREQ; // EXCEPTION経由で割り込まれたタスクに帰る
get_back: // 非タスクコンテキスト状態での戻り
restore_regs; // 退避したレジスタを全て戻す
p0=[sp++];
rti; // タスクに戻る
interrupt_dispatcher.end:
後処理部分は例外を使ってタスクに戻ります。この部分はseqstatレジスタに反映されるexcpt命令の引数をチェックしてタスクへの復帰要求かどうかを確認します。復帰要求であればスタックから戻り番地を取り出してrtx命令で戻ります。
この方法を使うとどのような割り込みでもuITRON4が要求する処理を行った後にタスクに復帰できます。後で知ったのですがRTOSでは普通に使われている方式だそうです。
[--sp] = r0; // 一時的にレジスタ退避
[--sp] = r1;
[--sp] = astat;
r0 = seqstat;
r1 = 0x03f;
r0 = r1 & r0;
r1 = RETURNREQ; // 割り込みハンドラからの例外復帰要求か
cc = r0 == r1;
if !cc jump dispatch_exception;
astat = [sp++]; // Yes.
r1 = [sp++];
r0 = [sp++];
retx = [sp++]; // 退避していた割り込みアドレスを復帰
restore_regs; // 割り込みハンドラが退避していたレジスタを復帰
p0=[sp++]; // 最初に退避したレジスタを復帰
rtx; // 例外経由で割り込みから戻る