NetBSD-9.0 on Qemu/KVM
もう使ってる人なんていないのかしらん。久々にQemu/KVMの上で使ってみた。
ftp.jp.netbsd.org.にはNetBSD-9.0はミラーしていない
たぶん、メンテナンスをする人がいないんでしょうね。NetBSD-9.0も8.2も、ミラーできていません。しかたがないので、IIJのミラーから、amd64のisoイメージを取得しました。
最近のlibvirt + Qemu/KVMではvirtioを認識できない
この件。これはデグレードしているというわけではなく、NetBSDのvirtioドライバが新しい仕様に追随できていないだけですね (だけ?)。
sysinstが満足にパーティションを切れない。
/と/usr、/var、/homeに分けたいだけなんですけどね。手でパーティションを切りました。後で試してみたところ、UEFIではなくBIOSで仮想マシンを作ったら、ちゃんとsysinstも機能しました。使っていく上で困ることではないので、追求していません。
shutdownすると必ずpanicする
ddb.onpanic=0にしていると、shutdown -pして席を離れている間に、なぜか再起動していたりします。ddb.onpanic=1にしてみると、panicしているのは、ichsmbのdetach処理。currentや、9-STABLEでは直っていました。
あとで試してみたところ、これもUEFIのときだけで、BIOSのときはpanicしないみたいです。dmesgで、attach時のメッセージを確認してみると、このif文 (cvswebは特定の行を指し示せないので、githubのミラーを使います) の結果が違うみたいです。BIOSだとLPCIB_SMB_HOSTC_HSTEN bitがセットされていて、この先でI/O空間のレジスタのマップなど、初期化処理が進みますが、UEFIではクリアされていて、ここで中断してしまいます。。detach処理では、初期化されているのが前提になっていたので、UEFIでは落ちる、と言うわけです。SeaBIOSとOVMFのハードウェア初期化処理の差、ということでしょうか。
ちなみに、Linuxでは問答無用でこのビットをセットしています。これに倣うと、
diff -u -r1.60 ichsmb.c
--- sys/dev/pci/ichsmb.c 10 Dec 2018 06:23:54 -0000 1.60
+++ sys/dev/pci/ichsmb.c 5 Jul 2020 10:41:21 -0000
@@ -169,8 +169,8 @@
DPRINTF(("%s: conf 0x%08x\n", device_xname(sc->sc_dev), conf));
if ((conf & LPCIB_SMB_HOSTC_HSTEN) == 0) {
- aprint_error_dev(self, "SMBus disabled\n");
- goto out;
+ conf |= LPCIB_SMB_HOSTC_HSTEN;
+ pci_conf_write(pa->pa_pc, pa->pa_tag, LPCIB_SMB_HOSTC, conf);
}
/* Map I/O space */
という解決もありますが、この先のI2Cバスに何かつながっている、というわけでもなさそうなので、あまり意味はありません。
QEMU USB Tabletが使えない
これは長くなったので別記事に。
UEFIのとき、boot -dでハングアップする
USB Tabletのデバッグ中に見つけたこの問題です。ハングアップというか、どうやらboot processorのCPU利用率が100%に張り付きます。似たような起動オプションに、-cというのがあり、これはuserconfのプロンプトを呼び出すものですが、こちらは正しく動作するようです。
DDBが使えませんので、Qemuのgdbserverの機能でも使ってみましょうか。ホスト側にクロス開発用のgdbを用意してもいいのですが、BIOSでの動作も知りたかったので、もう一つBIOSのVMを作り、そこにNetBSD-9.0をインストールしてみました。BIOSでは、-dオプションをつけると正しくDDBのプロンプトが呼び出されます。そうそう、sysinstでパーティションが切れることも、shutdown時にichsmbでpanicしないことも確認できました。
さて、このBIOSのVM (デバッグ側) には、あらかじめ、先ほどのマウスドライバデバッグ済みのカーネルをコンパイルしたディレクトリにあるnetbsd.gdbというファイルを転送しておきます。デバッグ対象のUEFIのVM (ターゲット側) は、再起動して「3」でブートプロンプトを出し、再現直前の状態にしておきましょう。
ここで、ホストでQemuのgdbserver機能を有効にします。libvirt配下の場合、Qemuのモニターコマンドはvirshを使って実行することになります。
$ virsh qemu-monitor-command --hmp VMNAME gdbserver
Waiting for gdb connection on device 'tcp::1234'
gdbserverがホストのTCPポート1234で待ち受け始めました。デバッグ側でgdbを起動します。ホストのファイアウォール (iptables) に注意。
$ gdb .../netbsd.gdb
GNU gdb (GDB) 8.3
...
(gdb) target remote HOSTIPADDRESS:1234
ターゲット側の実行は止まりますが、この状態ではまだターゲット側のNetBSDカーネルはロードされていませんので、gdbの真価は発揮できません。gdbのcコマンドでターゲット側の実行を再開させ、boot -dで現象を再現させます。
通常のユーザースペースのプログラムと同じように、gdbからCtrl-Cで実行を止めてbtでバックトレースを見たり、変数の内容を覗いたりできます。ここでわかるのは、このあたりから呼ばれるcngetc()が、cn_tabがNULLであるために0を返し、while()が無限ループになっている、ということです。この時点でまだコンソールが初期化されていないようです。
BIOSのときはこの時点でもう初期化されていたのでしょうか。デバッグ側とターゲット側をひっくり返して同じことをしようとして気づきました。UEFIのときはBIOSの時と比べると、画面が広いのです。つまり、UEFIのときはVGA (640x480/80x25) ではありません。dmesgを見ると、BIOSのときは
agp0 at pchb0autoconfiguration error: : can't find internal VGA config space
vga0 at pci0 dev 1 function 0: vendor 1b36 product 0100 (rev. 0x04)
wsdisplay0 at vga0 kbdmux 1: console (80x25, vt100 emulation), using wskbd0
wsmux1: connecting to wsdisplay0
wsmux1というのがキーボードです。一方UEFIのときは、
agp0 at pchb0autoconfiguration error: : can't find internal VGA config space
genfb0 at pci0 dev 1 function 0: Red Hat QXL Video (rev. 0x04)
genfb0: framebuffer at 0xc4000000, size 1024x768, depth 32, stride 4096
genfb0: shadow framebuffer enabled, size 3072 KB
wsdisplay0 at genfb0 kbdmux 1: console (default, vt100 emulation), using wskbd0
wsmux1: connecting to wsdisplay0
drm at genfb0 not configured
コンソールデバイスが違いました。この差がどの辺で生まれるのか、ちょっと辿ってみますと、こんなコードに行き当たります。おそらく、UEFIではこのif文が成立し、BIOSでは成立しないのでその下のvgaのコードに進むのでしょう。
ここでgenfbの時に呼ばれているx86_genfb_cnattach()は、ここにあります。ちょっと目を疑うようなコードですね。
int
x86_genfb_cnattach(void)
{
static int ncalls = 0;
struct rasops_info *ri = &x86_genfb_console_screen.scr_ri;
long defattr;
/* XXX jmcneill
* Defer console initialization until UVM is initialized
*/
++ncalls;
if (ncalls < 3)
return -1;
// 以下略
3回呼ばれるまでは-1を返していて、なぜかというとUVM (仮想メモリ) が初期化されていないからだ、というのです。確かに、x86_genfb_cnattach()を呼ぶconsinit()は、ここ、すなわちmain()が呼ばれる前のアーキテクチャ依存の初期化部、ここ、すなわちmain()の中、ここ、すなわちmain()から呼ばれるcpu_startup()の中の3回呼ばれています。boot -d、すなわちRB_KDBを処理しているのは、1回目の直後です。
#ifdef DDB
if (boothowto & RB_KDB)
Debugger();
#endif
試しに、このRB_KDBを処理しているif〜Debugger();を、3回目のconsinit()の後に移動してみると、無事DDBのプロンプトが拝めました。ここに移動するのが正しいのでしょうか。ちょっと他のアーキテクチャのソースコードを調べてみました。
結果、大体3通りあることがわかりました。
- amd64やi386と同じ、main()の前の初期化部 (多数派)
- main()から呼ばれるcpu_startup()の中
- consinit()の成功時 (m68k系)
DDBのプロンプトは、できるだけ早く拝みたいので、3がいいんじゃないかと思います。
Index: sys/arch/amd64/amd64/machdep.c
===================================================================
RCS file: /cvsroot/src/sys/arch/amd64/amd64/machdep.c,v
retrieving revision 1.335
diff -u -r1.335 machdep.c
--- sys/arch/amd64/amd64/machdep.c 24 Jul 2019 16:36:47 -0000 1.335
+++ sys/arch/amd64/amd64/machdep.c 6 Jul 2020 22:04:06 -0000
@@ -1922,10 +1922,6 @@
splraise(IPL_HIGH);
x86_enable_intr();
-#ifdef DDB
- if (boothowto & RB_KDB)
- Debugger();
-#endif
#ifdef KGDB
kgdb_port_init();
if (boothowto & RB_KDB) {
Index: sys/arch/i386/i386/machdep.c
===================================================================
RCS file: /cvsroot/src/sys/arch/i386/i386/machdep.c,v
retrieving revision 1.820
diff -u -r1.820 machdep.c
--- sys/arch/i386/i386/machdep.c 19 May 2019 08:46:15 -0000 1.820
+++ sys/arch/i386/i386/machdep.c 6 Jul 2020 22:04:11 -0000
@@ -1393,10 +1393,6 @@
splraise(IPL_HIGH);
x86_enable_intr();
-#ifdef DDB
- if (boothowto & RB_KDB)
- Debugger();
-#endif
#ifdef KGDB
kgdb_port_init();
if (boothowto & RB_KDB) {
Index: sys/arch/x86/x86/consinit.c
===================================================================
RCS file: /cvsroot/src/sys/arch/x86/x86/consinit.c,v
retrieving revision 1.31
diff -u -r1.31 consinit.c
--- sys/arch/x86/x86/consinit.c 31 May 2019 03:10:31 -0000 1.31
+++ sys/arch/x86/x86/consinit.c 6 Jul 2020 22:04:13 -0000
@@ -37,6 +37,7 @@
#include <sys/device.h>
#include <sys/bus.h>
#include <sys/cpu.h>
+#include <sys/reboot.h>
#include <machine/bootinfo.h>
#include <arch/x86/include/genfb_machdep.h>
@@ -224,7 +225,7 @@
if (error)
printf("WARNING: no console keyboard, error=%d\n",
error);
- return;
+ goto doddb;
}
#if (NCOM > 0)
if (!strcmp(consinfo->devname, "com")) {
@@ -235,7 +236,7 @@
puc_cnprobe(NULL);
rv = puc_cninit(NULL);
if (rv == 0)
- return;
+ goto doddb;
#endif
if (addr == 0)
@@ -247,7 +248,7 @@
COM_FREQ, COM_TYPE_NORMAL, comcnmode);
if (rv != 0)
panic("can't init serial console @%x", consinfo->addr);
- return;
+ goto doddb;
}
#endif
#if (NNULLCONS > 0)
@@ -259,6 +260,12 @@
}
#endif
panic("invalid console device %s", consinfo->devname);
+
+doddb:
+#ifdef DDB
+ if (boothowto & RB_KDB)
+ Debugger();
+#endif
}
#ifdef KGDB
ほんとはgenfb_machdep.cのXXX jmcneillも直したいですね。UVMが初期化済みかどうか、簡単に知る方法はないんでしょうかね。
おしまい
うっかり匿名のQiitaに記事を書いてしまったので、メールアドレスが必要なsend-prはしにくくなってしまいました。どなたか目に留まった方報告しといてくださいな。