結論としては、toppersプロジェクトサイトに置いてあるCortex-M3依存部のコードの問題だった(と思う)。プロジェクトに報告した方が良いのかも知れないけれど、120%の自信がないので自身のブログでゴニョゴニョ・・・と書き連ねておいて、誰かの参考になればと思う。
問題のコードは、Cortex-M3依存部として公開されているarch/arm_m_gcc/prc_support.Sというアセンブラコード。もともとこのコードはカーネルバージョン1.3.*用に作られたもので、今回ポーティングを行ったtoppers/asp バージョン1.7.0とは互換性がないのが原因なのかと思っていたが、そうではなかったようだ。
元のコードで、svc_handlerとラベルのある部分は次のようにコーディングされていた(435行目あたり)。
0435: svc_handler:
0440: cpsid f /* 割込みロック状態へ */
0441: mrs r0, psp
0442: add r0, #EXC_FRAME_SIZE /* スタックを捨てる */
0443: msr psp, r0
0444: mov r0, #0
このコードは、SVCサービスコール命令によってThreadモードからHandlerモードへ移行したあと、スタックに入れられたレジスタ値を強制的に捨てることを意図したものだけれど、この部分が場合によってはよろしくない。
「場合によっては」というその条件は、
- Cortex-M3の”構成制御レジスタ”にあるSTKALIGNビットが1である
- SVCサービスコール命令を呼び出すときのSPの値が8バイト境界に並んでいない
というものだった。
もし上記の2条件が成立しているときにSVC命令を実行すると、スタックに保存されるのは通常の[R0-R3,R12,LR,PC,xPSR]という8レジスタの前に1ワードのパッドデータがスタックされるので、都合9ワードがスタックに入ることになる。しかし、上に示したsupport.Sのコードは8ワード固定量のスタックを破棄するだけなのでスタックポインタがずれてしまい、結果として(多くの場合)Usage Faultで落ちる、ということになる。
この問題を回避するには、SVC呼び出しによってスタックに積まれたのが8ワードなのか、それともパッドを含む9ワードなのかを調べて(8バイト境界への調整が行われたかどうかを調べて)、正しい量をスタックから捨てなければならない。
幸いなことに(というか当然だけど)スタックに積まれたxPSRのビット[9]とみると、8バイト境界調整が行われたかどうかが分かるようになっているので、これを利用することにする。というわけで、修正後のコードは、
svc_handler:
cpsid f /* 割込みロック状態へ */
mrs r0, psp
--- add r0, #EXC_FRAME_SIZE /* スタックを捨てる */
+++ add r0, #(EXC_FRAME_SIZE-4) /* スタックから[R0-R3,R12,LR,PC]を捨てる */
+++ ldmfd r0!, {r1} /* スタックからxPSRを取り出す */
+++ tst r1,#0x200 /* 8byte境界に調整されたか? */
+++ it ne
+++ addne r0,#4 /* そうであれば更に1ワード調整する */
msr psp, r0
とした。
上記のSTKALIGNビットはCortex-M3のコアリセット時には'1'に設定されると言うことなので、この問題に引っかかる可能性は純粋に1/2の確率になると思うんだけれど、世の中の人はこの地雷を踏んでいないのかなぁ。
(2011 7/6 AM11:30 追記)
CQ出版から出ている「ARM Cortex-M3システム開発ガイド」によると、Cortex-M3コアのリビジョンが1の場合は、問題の8ワードの”例外スタックフレーム”はワード境界にも、ダブルワード境界にも配置することができ、その切り替えにSTKALIGNビットがあると書いてある、Cortex-M3コアのリビジョンが2になると、例外スタックフレームはダブルワード境界にしか配置できないようになっているそうなので、今後toppersのこの問題の可能性は1/2よりも大きくなってくるのかも知れないな。
(というか、これまで問題視されてきた経緯がなさそうなのは、toppers+Cortex-M3という組み合わせでの採用実績が少ないのかな?)
0 件のコメント:
コメントを投稿