メモ: 9front MP Kernel panics on KVM

MP対応な9frontカーネル様がKVM環境でブート時にパニックしたのでメモ。
ここでは手前でコンパイルして配置したpcfカーネルを利用。
pccpufカーネルだと引っかからないらしい。
実機試験はPlan 9の方のみ実施。こちらではpcfカーネルでもきちんと動くようだ (結局別のところでおかしくなったけど)



再現方法 (またはMP対応有効化)
通常、MP対応は無効になっているのでブート時にこれを有効にする必要がある。
例の如くplan9.iniに設定を突っ込めばいい。
9front向けplan9.iniのマニュアルによると以下のオプション行が必要とのこと。
既に記述はあるが、1 (=無効)になっているので0 (=有効)にする必要がある。

*nomp=
          A multiprocessor machine will enable all processors by
          default.  Setting *nomp restricts the kernel to starting
          only one processor and using the traditional interrupt con-
          troller.

ブートコンフィグたるplan9.iniは9fat領域に納められている。
Plan 9ではここをマウントする時には以下のコマンドを使っていた。ところがこれは9frontでは存在しない。

term% 9fat:

そのかわりに9fsコマンドを使って/n/9fat/の位置にマウントする。

term% 9fs 9fat


この状態でKVMで起動すると以下のようにパニックする。

cpu0: 3193MHz Genuine Intel Atom (cpuid: AX 0x106C2 DX 0xFCBFBF9)
....
panic: lapic clock 999812570 > cpu clock > 205454592

追っかけ
dumpstackなんてまるっきり読めないので, panicメッセージを手がかりに
落ちている場所をさがしてみる。以下の関数の155行目らしい。

/sys/src/9/pc/apic.c
129 /*
130  *  use the i8253 clock to figure out our lapic timer rate.
131  */
132 static void
133 lapictimerinit(void)                                                                                                                                                                                                           
134 {
135         uvlong x, v, hz;
136 
137         v = m->cpuhz/1000;
138         lapicw(LapicTDCR, LapicX1);
139         lapicw(LapicTIMER, ApicIMASK|LapicCLKIN|LapicONESHOT|(VectorPIC+IrqTIMER));
140 
141         if(lapictimer.hz == 0ULL){
142                 x = fastticks(&hz);
143                 x += hz/10;
144                 lapicw(LapicTICR, 0xffffffff);
145                 do{
146                         v = fastticks(nil);
147                 }while(v < x);
148 
149                 lapictimer.hz = (0xffffffffUL-lapicr(LapicTCCR))*10;
150                 lapictimer.max = lapictimer.hz/HZ;
151                 lapictimer.min = lapictimer.hz/(100*HZ);
152 
153                 if(lapictimer.hz > hz-(hz/10)){
154                         if(lapictimer.hz > hz+(hz/10))
155                                 panic("lapic clock %lld > cpu clock > %lld\n",
156                                         lapictimer.hz, hz);
157                         lapictimer.hz = hz;
158                 }
159                 lapictimer.div = hz/lapictimer.hz;
160         }
161 }

Local APIC Timerのクロックレートを調べているらしい。
手順としては以下の流れになっている。

  1. lapicタイマの初期値を0xffffffffに設定
  2. fastticks()を使って100msecの間ループ
  3. lapicタイマの現在値と初期値との差分計算、lapicタイマのクロックレートの計算
  4. hz (= cpu clock ? )と比べる
  5. 落ちる

改めてパニック時に出力されているlapictimer.hzとhzを眺めてみる

lapic clock = lapictimer.hz = 999812570 = 999MHz
cpu clock = hz = 205454592 = 205MHz

あきらかにCPUのクロックレートじゃないよね... 上に3192MHzって出てるし...
それもそのはずで、関数の上のコメントにもあるようにここでfastticks()を用いて
取得できるhzにはi8253、つまりPITのクロックレートが渡されるようだ。
i8253はだいたい200MHzなのでまさにそうなっている模様。



panic()の中身を信じるならばここにはcpuのクロックレートが入っているハズだが....
どうも単純に比較先を間違えているのか、何かクロック周りを間違えているのか。
あるいはKVM環境だとlapic clock周りで特殊な事情があるのでわ!?とも考えられる。
local apicのクロックレートの適正値とかよくわからない。ここらへんは実機で試して見ないとなんとも。


とりあえず比較対象をhzから、m->cpuhz(CPUのクロックレートが入っているはず)に
変えたところ、正常に起動するのを確認。
コードの変更はこんな感じ : https://github.com/enukane/9front-work/commit/79807778262f3f7ec174a4ca97f1c99a5138e010
果たして同じコードで実機動作するのか。

とりあえずこれでMSI周りを叩けるようになりましたとさ。



補足:fastticks() ?
pccpufカーネルだと引っかからないという当たりが腑に落ちないので少し中身を追いかけてみた。対象はfastticks()。

/sys/src/9/pc/devarch.c
1039 /*
1040  *  return value and speed of timer set in arch->clockenable
1041  */
1042 uvlong
1043 fastticks(uvlong *hz)                                                                                                                                                                                                         
1044 {
1045         return (*arch->fastclock)(hz);
1046 }

通常、x86アーキテクチャにてfastclockはi8253のクロックレートを参照している。
これはMPカーネルでも同様で、archの中身は以下のようになっている。

59 PCArch archmp = {
60 .id= "_MP_",
61 .ident= identify,
62 .reset= mpshutdown,
63 .intrinit= mpinit,
64 .intrenable= mpintrenable,
65 .intron= lapicintron,
66 .introff= lapicintroff,
67 .fastclock= i8253read,
68 .timerset= lapictimerset,
69 };

i8253read()ではではクロックレートを引数hzのアドレスに突っ込んで返す。
これを用いて先ほどのhz≒200MHzを取得している。これが一般的なフロー。

ところが、場合によってはこの部分が書き換わる。

/sys/src/9/pc/archmp.c
 71 static int
 72 identify(void)
 73 {
 74         char *cp;
 75         PCMP *pcmp;
 76         uchar *p, sum;
 77         ulong length;
 78 
 79         if((cp = getconf("*nomp")) != nil && strtol(cp, 0, 0) != 0)
 80                 return 1;
 81 
 82         /*
 83          * Search for an MP configuration table. For now,
 84          * don't accept the default configurations (physaddr == 0).
 85          * Check for correct signature, calculate the checksum and,
 86          * if correct, check the version.
 87          * To do: check extended table checksum.
 88          */
 89         if((_mp_ = mpsearch()) == 0 || _mp_->physaddr == 0)
 90                 return 1;
 91 
 92         pcmp = KADDR(_mp_->physaddr);
 93         if(memcmp(pcmp, "PCMP", 4))
 94                 return 1;
 95 
 96         length = pcmp->length;
 97         sum = 0;
 98         for(p = (uchar*)pcmp; length; length--)                                                                                                                                                                                
 99                 sum += *p++;
100 
101         if(sum || (pcmp->version != 1 && pcmp->version != 4))
102                 return 1;
103 
104         if(cpuserver && m->havetsc)
105                 archmp.fastclock = tscticks;
106         return 0;
107 }

上記104行目で、cpuserverかつTSCが有効な場合にtsticks()とかいう関数に置き換わる。
cpuserverはpccpufカーネルの時有効。
tscticks()は以下に挙げてあるように、CPUのクロックレートそのもので動作する。

/sys/src/9/pc/archmp.c
131 uvlong
132 tscticks(uvlong *hz)
133 {
134         if(hz != nil)
135                 *hz = m->cpuhz;
136 
137         cycles(&m->tscticks);   /* Uses the rdtsc instruction */                                                                                                                                   
138         return m->tscticks;
139 }

これなら比較されても大丈夫だったね! ってかわざわざcpuserverに限定するのはどうしてなんだ...