メッセージ欄

2015年12月の日記

一覧で表示する

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

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

前回のNetBSD Advent Calendar 8日目からの続きです。

今回の結論は、前回の考察は全くの無意味で、実は何もしなくてよかった、です。

エントリは短いのですが、かなりヤクの毛を刈りましたよ

おさらい

前回のストーリはこんな感じです。
  • 移植の際、ステータスレジスタに0x10000004を入れる必要があった
  • マジックナンバーなので気になって調べた
  • NetBSD, FreeBSD, u-bootのレジスタ設定に共通点はなかった(u-bootはマジックナンバーの出元ですね)
ということで、もっぱらソースとマニュアルを紐解くという、いわば机上での調査でした。

そこで、今回は実際にあれころ動作させてみて何かわかることはないか、という実機での調査をしようと考えたわけです。

最新のソースで試してみよう

以前に試していたのはずいぶん古い環境だったので、気を取り直して新しめのソースコードで試してみよう、と思ったのが間違いの始まりでした。

NetBSD 7.0リリース版を入れて放置していたVMイメージを起動し、2012-12-11のソースを展開。クロスコンパイラをbuild.shで一発作成して、gitから取り出したLinino ONE用のパッチをあてます。パッチはきれいにあたってカーネルの作成にも成功しました。

順調です。

いざ、起動します。
Copyright (c) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
    2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015
    The NetBSD Foundation, Inc.  All rights reserved.
Copyright (c) 1982, 1986, 1989, 1991, 1993
    The Regents of the University of California.  All rights reserved.

NetBSD 7.99.24 (INSTALL_LININO1) #0: Sat Dec 12 02:40:05 UTC 2015
        root@n70:/export/o/20151211/evbmips/sys/arch/evbmips/compile/INSTALL_LININO1
Atheros AR9331
total memory = 65536 KB
avail memory = 56424 KB
mainbus0 (root)
cpu0 at mainbus0: 400.00MHz (hz cycles = 2000000, delay divisor = 200)
cpu0: MIPS 24K (0x19374) Rev. 116 with software emulated floating point
cpu0: 16 TLB entries, 256MB max page size
cpu0: 64KB/32B 4-way set-associative L1 instruction cache
cpu0: 32KB/32B 4-way set-associative write-back L1 data cache
wdog0 at mainbus0: 5 second period
arbus0 at mainbus0
あれ? このあと文字化けした表示が続き、まるで動きません。

このあと、パッチの当て忘れに気づいたり、あれこれ試行錯誤したのですが、結局のところシェルプロンプトを拝むことはできませんでした。

これはまずい。

昔の環境なら動くはず

以前動作させていた環境なら動くはずです。動いているところから色々変化させて挙動の変化を見るほうが良いに決まっています。初めからそうすればよかったのです。

と、いうことで以前のVMイメージを起動して、当時作ったカーネルを起動します。はい、当然のことながら起動します。

それでは、ステータスレジスタを変化させてみましょう。

ということで、ステータスレジスタを変化させて試してみますが、なんだか様子が変です。

以前は止まっていたはずなのに、なにをやっても正しく起動してきます。

おかしい。

頭から見てみよう

カーネルの作成ミスや修正のセーブし忘れなど、ケアレスミスを見つけやすくするためにカーネルの作成回数や「ここを実行したよ」というデバッグ文字を出しています。

ここをよく見るとおかしなことが。

なぜか通るべきルートを通っているはずなのに、デバッグ文字が出力されていません。

シリアル部分が詰まっているのかなぁ、と思っていたのですが、いよいよ疑わねばならない感じです。リブートではなく電源OFFからやり直してみたり、ここまででずいぶん時間を食っています。

デバッグ文字が出力されていない理由、それはその部分を実行していないだけなのです。つまり、違うルートを通っていると。

絶対通らないはずと思い込んでいたルートにデバッグ文字を入れると、きっちりそこの文字が出ました。あぁ、やっぱりか。

そのルートが問題

実行されないところを一生懸命直したりしてバカだなぁ、ということにつきるのですが、問題はそのルートです。
     89 #else
     90 	mtc0	zero, MIPS_COP_0_STATUS		# Disable interrupts
     91 	COP0_SYNC
     92 #endif
そうです、ステータスレジスタにゼロを入れて完全にクリアしています。

前回、NetBSDではステータスレジスタのBEVフィールドを保持して書き戻していると書きましたが、あれは大ウソだったということです。

それはそれでマヌケな話なのですが、それはさておき、ステータスレジスタにゼロを入れてシェルプロンプトが出るところまで動いているということはどういうことなんでしょうか?

Linino ONEを移植する際に、最初につまづいたのは「ステータスレジスタにゼロを入れて書き戻すとハングする」という事象だったはずです。

西村さんのエスパーメールにも、ステータスレジスタの全ビットを書き換えるコードの是非について言及があったので、当時もこのルートを通り、うまくうごかずu-bootをまねて0x10000004を設定して動かしたはずです。

怪奇現象

時間がたつとバグが消えるなんて話は、本当ならばうれしいですが、おかしな話です。

まぁいいです。私はマジックナンバーを設定したいのです。

以前そうしていたように0x10000004を設定してカーネルを起動します。
total memory = 65536 KB
avail memory = 56456 KB
pid 0(system): trap: cpu0, reserved instruction in kernel mode
status=0x10000003, cause=0x70008028, epc=0x8006e6f0, vaddr=0xc0001ff8
tf=0x807bbaa0 ksp=0x807bbb40 ra=0x8006e6c4 ppl=0
kernel: reserved instruction trap
Stopped in pid 0.1 (system) at  8006e6f0:       sw      s0,0(s2)
db>
あれ、死んだ。

以前と真逆の結果です。

こうなったらいろいろ試してみよう

せっかくなので、前回調べたFreeBSDの初期化を試してみたり、マジックナンバーのCU0とERLの片方だけをONにしたり、FreeBSDの設定にCU0, ERLを追加してみたりしました。
ステータスレジスタ結果
ゼロ起動する
CU0=0N起動する
ERL=ON起動しない (1)
CU0=ON && ERL=ON起動しない(2)
FreeBSDの初期化起動する
FreeBSDの初期化 + CU0=ON起動する
FreeBSDの初期化 + ERL=ON起動しない(3)
起動しないパターンの共通項はERL=ONのようです。

なお、死んだときのメッセージについては次のとおりです。

(1)ERL=ON
pid 0(system): trap: cpu0, reserved instruction in kernel mode
status=0x10000003, cause=0x70008028, epc=0x8006e6f0, vaddr=0xc0001ff8
tf=0x807bbaa0 ksp=0x807bbb40 ra=0x8006e6c4 ppl=0
kernel: reserved instruction trap
Stopped in pid 0.1 (system) at  8006e6f0:       sw      s0,0(s2)
(2)CU0=ON && ERL=ON
pid 0(system): trap: cpu0, reserved instruction in kernel mode
status=0x3, cause=0x70008028, epc=0x8006e700, vaddr=0xc0001ff8
tf=0x807bbaa0 ksp=0x807bbb40 ra=0x8006e6d4 ppl=0
kernel: reserved instruction trap
Stopped in pid 0.1 (system) at  8006e700:       sw      s0,0(s2)
(3)FreeBSDの初期化 + ERL=ON
pid 0(system): trap: cpu0, system call in kernel mode
status=0x3, cause=0x50008020, epc=0x8006e6f0, vaddr=0xc0001ff8
tf=0x807bbaa0 ksp=0x807bbb40 ra=0x8006e6c4 ppl=0
kernel: system call trap
Stopped in pid 0.1 (system) at  8006e6f0:       sw      s0,0(s2)
ほとんど同じに見えますがstatus, causeのが微妙に違っています。

まとめ

さて、いろいろと試してみましたけれど、実際に動かしてみてわかったことは、「元のコードが正しかった」ということのようです。

西村さんからもらったエスパーメールはマジックナンバーのメールだけでなく、実はそのあとにも数通いただいていたのです。

そのメールの内容は、当時はさっぱり理解できなかったのですが、いま改めて読み返してみると、テクニカルかつ示唆に富む内容だなぁと噛みしめている次第です。

個人あてメールを勝手に引用するのはマナー違反だと知りつつ、一文だけ引用させていただいて、今回のエントリーを締めたいと思います。
自分で拵えたコードは多くないはずですから、総当りして間違っていないか確認してみてはどうでしょうか?
西村さんからのメールより

蛇足

「酔った勢いでエントリー」はやめておこう!

マジックナンバー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を保持するとどうなるか、そもそも処理に入る前のステータスレジスタの値はどうなっているのか、など追加の調査をしてみようかと思います。

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