マジックナンバー0x10000004の謎
■ きっかけ
OSC 2015 Tokyo FallにてUSBシリアル2015, NetBSD/evbmips on Linino ONEでUSBシリアルの話とLinino ONEでNetBSDを動かす話をしました。Linino ONEという小さなMIPSマシンにNetBSDを移植した話なのですが、このなかで一番最初に実行されるlocore.Sの冒頭でダンマリになってしまう現象に悩まされていました。その際、NetBSD Developerの西村さんからヒントをいただいて、無事に先に進んだという部分があります。まさに、エスパーによるリモートデバッグといえましょう。
先に進んだのは良かったのですが、見よう見まねでステータスレジスタに設定した0x10000004は謎のままです。mips/locore.Sという一番最初のファイルにマジックナンバーを入れておくのはどうにも気持ちが悪いですし、本家にフィードバックする際に説明できないのでは困るので、まじめに調べてみようと思ったのがきっかけです。
もちろんAdvent Calendar駆動で調べ始めたのですが。
■ 問題の個所
問題の個所はsys/arch/mips/mips/locore.sの以下の部分です。79 #if (defined(MIPS3_PLUS) && !defined(MIPS1)) || defined(emips) 80 /* keep firmware exception handler until we hook. */ 81 mfc0 v0, MIPS_COP_0_STATUS 82 #if defined(emips) 83 and v0, MIPS_SR_TS | MIPS3_SR_RE 84 #else 85 and v0, MIPS_SR_BEV 86 #endif 87 mtc0 v0, MIPS_COP_0_STATUS # Disable interrupts 88 COP0_SYNC 89 #else 90 mtc0 zero, MIPS_COP_0_STATUS # Disable interrupts 91 COP0_SYNC 92 #endifif-else-endifを整理すると次の三行になります。
81 mfc0 v0, MIPS_COP_0_STATUS 85 and v0, MIPS_SR_BEV 87 mtc0 v0, MIPS_COP_0_STATUS # Disable interruptsmfc0でステータスレジスタをv0にロードして、andでBEVフィールド以外はゼロクリアして、mtc0でステータスレジスタをストアしています。
if-else-endifを考慮すると、ステータスレジスタに対して様々なパターンで処理を行っています。
パターン | 処理内容 |
---|---|
今回 | BEVフィールド以外をゼロクリア |
emips | TSフィールドとREフィールド以外をゼロクリア |
それ以外の場合 | すべてゼロクリア |
■ ステータスレジスタに何をやっているのか
処理を切り替えているということは、何らかの理由があるはずです。プロセッサごとの初期化処理に違いがあるということでしょうか。そのためにフィールドのそれぞれの意味を確認します。今回は MIPS32 24kのドキュメントをあたってみます。
先に出てきたのはBEV,TS,REです。
フィールド名 | 意味 | 説明 |
---|---|---|
BEV | boot exception vectors | エクセプションベクタの場所を制御します。0だとノーマル、1だとbootstrapです。 |
TS | TLB shutdown | TLBWI or TLBWRによってマシンチェック例外が引き起こされたことを示す。書き込めるのは0だけである |
RE | reverse endianness in user mode | プロセッサがユーザモードで実行中にリバースエンディアンメモリ参照を有効にします。サポートされていません。 |
■ マジックナンバーの中身を見る
気を取り直してマジックナンバー0x10000004を考えてみます。0x10000004で立っているビットからステータスレジスタのどのフィールドなのかを確認してみると、CU0とERLです。
フィールド名 | 意味 | 説明 |
---|---|---|
CU0 | Coprocessor Unit 0 | コプロセッサ0へのアクセスを制御する。プロセッサがカーネルモードで動作しているときは、CU0ビットの状態に関係なくコプロセッサ0を常に使うことができる。 |
ERL | Error Level | Reset, Soft Reset, NMI, キャッシュエラー例外が発生した際プロセッサによってセットされる。ERLがセットされた場合、プロセッサはカーネルモードで動作する、割り込みは禁止されている、ERET命令はEPCの代わりにErrorEPCに保持された戻り先アドレスを使う、KUSEGの下位2^29バイトは未マップかつ非キャッシュ領域として扱われる。 |
■ オペレーティングモード
カーネルモードはMIPS32 24Kがサポートしているモードの一つだそうです。MIPS32 24Kプロセッサは四つのモードをサポートしているようです。
- User mode
- Supervisor mode
- Kernel mode
- Debug mode
モード | 条件 |
---|---|
Kernel mode | KSU=00 or EXL=1 or ERL=1 |
Supervisor mode | KSU=01 and EXL=0 and ERL=0 |
User mode | KSU=10 and EXL=0 and ERL=0 |
フィールド名 | 意味 | 説明 |
---|---|---|
KSU | Kernel Supervisor User | このフィールドはプロセッサのベースのモードを示します。KSUフィールドのステータスにかかわらず、ERLかEXLがセットされるとプロセッサはカーネルモードになります。 |
一方で、CU0フィールドはなぜ設定しているのでしょうか? カーネルモードであればCU0フィールドの値にかかわらずCU0が使えるのに。
釈然としません。
■ FreeBSDはどうなの
煮詰まったので気分を変えたいと思います。そういえばFreeBSDはどうやっているんでしょう。
そう思って確認すると../src-freebsd/sys/mips/mips/locore.Sに似たような処理があるのを見つけました。
91 #if defined(CPU_CNMIPS) 92 /* 93 * t1: Bits to set explicitly: 94 * Enable FPU 95 */ 96 97 /* Set these bits */ 98 li t1, (MIPS_SR_COP_0_BIT | MIPS_SR_PX | MIPS_SR_KX | MIPS_SR_UX | MIPS_SR_SX | MIPS_SR_BEV) 99 100 /* Reset these bits */ 101 li t0, ~(MIPS_SR_DE | MIPS_SR_SR | MIPS_SR_ERL | MIPS_SR_EXL | MIPS_SR_INT_IE | MIPS_SR_COP_2_BIT) 102 #elif defined (CPU_RMI) || defined (CPU_NLM) 103 /* Set these bits */ 104 li t1, (MIPS_SR_COP_2_BIT | MIPS_SR_COP_0_BIT | MIPS_SR_KX | MIPS_SR_UX) 105 106 /* Reset these bits */ 107 li t0, ~(MIPS_SR_BEV | MIPS_SR_SR | MIPS_SR_INT_IE) 108 #else 109 /* 110 * t0: Bits to preserve if set: 111 * Soft reset 112 * Boot exception vectors (firmware-provided) 113 */ 114 li t0, (MIPS_SR_BEV | MIPS_SR_SR) 115 /* 116 * t1: Bits to set explicitly: 117 * Enable FPU 118 */ 119 li t1, MIPS_SR_COP_1_BIT 120 #ifdef __mips_n64 121 or t1, MIPS_SR_KX | MIPS_SR_SX | MIPS_SR_UX 122 #endif 123 #endif上記はt0, t1に値をセットしているだけに見えますが、後続の処理でステータスレジスタに対してt0のANDとt1のORを取っています。
つまり、レジスタt0にクリアしたいもの、t1にセットしたいものを設定しているようです。
NetBSDと同様にステータスレジスタへの初期化処理ではif-else-endifを使って
MIPS_CNMIPS、CPU_RMI・CPU_NLM、それ以外の三つのパターン分けがされています。
セットしているものとクリアしているものを一覧表にしてみましょう。
まずは、セットしているもの。
パターン | CU2 | CU1 | CU0 | PX | KX | UX | SX | BEV |
---|---|---|---|---|---|---|---|---|
MIPS_CNMIPS | ○ | ○ | ○ | ○ | ○ | ○ | ||
CPU_RMI, CPU_NLM | ○ | ○ | ○ | ○ | ||||
other | ○ | ○ | ○ |
まず、CNMIPSはmips64なのでちょっと関係なさそうです。次に、RMI(Raza Microelectronics, Inc)とNLM(Netlogic Microsystems)はどちらもAtherosじゃないですね。うーん、AtherosでセットしていてくれればFreeBSDと同じだと安心できたのですが。
最後のotherはCU1つまりFPUを有効にしています。ただし、MIPS32 24kcのばあいFPUは存在しないので無視される、といったところでしょうか。Atherosの場合もこのルートに入るのでCU0はセットしていないようです。
つぎは、クリアしているもの。
パターン | BEV | DE | SR | ERL | EXL | IE | CU2 |
---|---|---|---|---|---|---|---|
MIPS_CNMIPS | ○ | ○ | ○ | ○ | ○ | ○ | |
CPU_RMI, CPU_NLM | ○ | ○ | ○ | ||||
other | BEVとSRを保持してすべてクリア |
つまり、FreeBSDではマジックナンバーに相当するフィールドは何も同じところはなかったということになります。
■ 整理してみよう
ステータスレジスタの状態をまとめると次のようになります。OS | ステータスレジスタ | 結果 |
---|---|---|
素のNetBSD | BEV=保持, 他すべてクリア | ハング |
マジックナンバー | CU0=1, ERL=1,他すべてクリア | 動く |
FreeBSD | CU1=1, BEV=保持, SR=保持, 他すべてクリア | やったことない |
なお、CU1へのアクセスはFPUのないMIPS32 24Kcでは無視されます。
したがって、素のNetBSDとFreeBSDの違いは、SRを保持するか否か、といえるのではないか、という気がしてきました。FreeBSDではAR9331は健やかに動いているように見えるので、もしかしたらSRを保持すれば動くのかもしれません。
一方で、やはりu-bootのマジックナンバーだけが異彩を放っています。
■ まとめ
今回は時間切れでここまでしかわかりませんでした。NetBSDでBEVとSRを保持するとどうなるか、そもそも処理に入る前のステータスレジスタの値はどうなっているのか、など追加の調査をしてみようかと思います。
当時は、カーネルをシリアル転送するだけで疲れ果てていたのですが、ネットワークブートができるようになった今では気軽に試せるようになったので、じっくり取り組んでみようかと思います。
■ MIPS参考文献
- TB-URL http://www.tokuda.net/diary/0855/tb/