マジックナンバー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 #endif
if-else-endifを整理すると次の三行になります。
     81 	mfc0	v0, MIPS_COP_0_STATUS
     85 	and	v0, MIPS_SR_BEV
     87 	mtc0	v0, MIPS_COP_0_STATUS		# Disable interrupts
mfc0でステータスレジスタを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参考文献
コメント(0件)
		- TB-URL http://www.tokuda.net/diary/adiary.cgi/0855/tb/

