マジックナンバー0x10000004の謎
2015/12/08(火) 23:21 NetBSD はてブ情報 はてブに登録 はてブ数

このエントリはNetBSD Advent Calendar 2015の8日目です。

結論から言うと、本件はまだ解決していません。
時間切れではなく実力不足ですいません。

きっかけ

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フィールド以外をゼロクリア
emipsTSフィールドとREフィールド以外をゼロクリア
それ以外の場合すべてゼロクリア

ステータスレジスタに何をやっているのか

処理を切り替えているということは、何らかの理由があるはずです。プロセッサごとの初期化処理に違いがあるということでしょうか。

そのためにフィールドのそれぞれの意味を確認します。今回は MIPS32 24kのドキュメントをあたってみます。

先に出てきたのはBEV,TS,REです。
フィールド名意味説明
BEVboot exception vectorsエクセプションベクタの場所を制御します。0だとノーマル、1だとbootstrapです。
TSTLB shutdownTLBWI or TLBWRによってマシンチェック例外が引き起こされたことを示す。書き込めるのは0だけである
REreverse endianness in user modeプロセッサがユーザモードで実行中にリバースエンディアンメモリ参照を有効にします。サポートされていません。
なるほど。よくわかりません。こまりました。

マジックナンバーの中身を見る

気を取り直してマジックナンバー0x10000004を考えてみます。
0x10000004で立っているビットからステータスレジスタのどのフィールドなのかを確認してみると、CU0とERLです。
フィールド名意味説明
CU0Coprocessor Unit 0コプロセッサ0へのアクセスを制御する。プロセッサがカーネルモードで動作しているときは、CU0ビットの状態に関係なくコプロセッサ0を常に使うことができる。
ERLError LevelReset, Soft Reset, NMI, キャッシュエラー例外が発生した際プロセッサによってセットされる。ERLがセットされた場合、プロセッサはカーネルモードで動作する、割り込みは禁止されている、ERET命令はEPCの代わりにErrorEPCに保持された戻り先アドレスを使う、KUSEGの下位2^29バイトは未マップかつ非キャッシュ領域として扱われる。
さっぱりわかりません。カーネルモードとか何でしょうか?

オペレーティングモード

カーネルモードはMIPS32 24Kがサポートしているモードの一つだそうです。

MIPS32 24Kプロセッサは四つのモードをサポートしているようです。
  1. User mode
  2. Supervisor mode
  3. Kernel mode
  4. Debug mode
DebugモードはDebugレジスタのDMビットが1の場合、そうでない場合はステータスレジスタのKSU、EXL、ERLの値によってそれぞれのモードが判別できるようです。
モード条件
Kernel modeKSU=00 or EXL=1 or ERL=1
Supervisor modeKSU=01 and EXL=0 and ERL=0
User modeKSU=10 and EXL=0 and ERL=0
ここでKSUフィールドの説明を見てみると次のような記述があります。

フィールド名意味説明
KSUKernel Supervisor Userこのフィールドはプロセッサのベースのモードを示します。KSUフィールドのステータスにかかわらず、ERLかEXLがセットされるとプロセッサはカーネルモードになります。
したがって、マジックナンバーの0x10000004の0004についてはERLを1にしていることから、どうやらプロセッサをカーネルモードに設定しているということがわかりました。

一方で、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、それ以外の三つのパターン分けがされています。

セットしているものとクリアしているものを一覧表にしてみましょう。

まずは、セットしているもの。
パターンCU2CU1CU0PXKXUXSXBEV
MIPS_CNMIPS
CPU_RMI, CPU_NLM 
other 
マジックナンバーでもセットしていたCU0をセットしているのがありますね。

まず、CNMIPSはmips64なのでちょっと関係なさそうです。次に、RMI(Raza Microelectronics, Inc)とNLM(Netlogic Microsystems)はどちらもAtherosじゃないですね。うーん、AtherosでセットしていてくれればFreeBSDと同じだと安心できたのですが。

最後のotherはCU1つまりFPUを有効にしています。ただし、MIPS32 24kcのばあいFPUは存在しないので無視される、といったところでしょうか。Atherosの場合もこのルートに入るのでCU0はセットしていないようです。

つぎは、クリアしているもの。
パターンBEVDESRERLEXLIECU2
MIPS_CNMIPS
CPU_RMI, CPU_NLM 
otherBEVとSRを保持してすべてクリア
AtherosはotherのルートなのでCU0はクリアされてしうまうようです。さらにERLもクリアしちゃってます。

つまり、FreeBSDではマジックナンバーに相当するフィールドは何も同じところはなかったということになります。

整理してみよう

ステータスレジスタの状態をまとめると次のようになります。
OSステータスレジスタ結果
素のNetBSDBEV=保持, 他すべてクリアハング
マジックナンバーCU0=1, ERL=1,他すべてクリア動く
FreeBSDCU1=1, BEV=保持, SR=保持, 他すべてクリアやったことない
BEVを保持している部分に共通点は見られますが見事にバラバラです。

なお、CU1へのアクセスはFPUのないMIPS32 24Kcでは無視されます。

したがって、素のNetBSDとFreeBSDの違いは、SRを保持するか否か、といえるのではないか、という気がしてきました。FreeBSDではAR9331は健やかに動いているように見えるので、もしかしたらSRを保持すれば動くのかもしれません。

一方で、やはりu-bootのマジックナンバーだけが異彩を放っています。

まとめ

今回は時間切れでここまでしかわかりませんでした。

NetBSDでBEVとSRを保持するとどうなるか、そもそも処理に入る前のステータスレジスタの値はどうなっているのか、など追加の調査をしてみようかと思います。

当時は、カーネルをシリアル転送するだけで疲れ果てていたのですが、ネットワークブートができるようになった今では気軽に試せるようになったので、じっくり取り組んでみようかと思います。

名前:  非公開コメント   

  • TB-URL  http://www.tokuda.net/diary/0855/tb/