NetBSD-9.0 on Qemu/KVM

# NetBSD-9.0 on Qemu/KVM

もう使ってる人なんていないのかしらん。久々にQemu/KVMの上で使ってみた。

## ftp.jp.netbsd.org.にはNetBSD-9.0はミラーしていない

たぶん、メンテナンスをする人がいないんでしょうね。NetBSD-9.0も8.2も、ミラーできていません。しかたがないので、[IIJのミラー](ftp://ftp.iij.ad.jp/pub/NetBSD/NetBSD-9.0/)から、amd64のisoイメージを取得しました。

## 最近のlibvirt + Qemu/KVMではvirtioを認識できない

[この件](https://qiita.com/SIGABRT/items/b61549501bcd7d025900)。これはデグレードしているというわけではなく、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では直っていました](http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/dev/pci/ichsmb.c?rev=1.61&content-type=text/x-cvsweb-markup)。

あとで試してみたところ、これもUEFIのときだけで、BIOSのときはpanicしないみたいです。dmesgで、attach時のメッセージを確認してみると、[このif文](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/dev/pci/ichsmb.c#L171) (cvswebは特定の行を指し示せないので、githubのミラーを使います) の結果が違うみたいです。BIOSだとLPCIB_SMB_HOSTC_HSTEN bitがセットされていて、この先でI/O空間のレジスタのマップなど、初期化処理が進みますが、UEFIではクリアされていて、ここで中断してしまいます。。detach処理では、初期化されているのが前提になっていたので、UEFIでは落ちる、と言うわけです。SeaBIOSとOVMFのハードウェア初期化処理の差、ということでしょうか。

ちなみに、Linuxでは[問答無用でこのビットをセットしています](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/i2c/busses/i2c-i801.c?h=v5.7&id=3d77e6a8804abcc0504c904bd6e5cdf3a5cf8162#n1823)。これに倣うと、

```
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が使えない

これは長くなったので[別記事](https://qiita.com/SIGABRT/items/eb1e6e156930fd81f710)に。

## UEFIのとき、boot -dでハングアップする

[USB Tabletのデバッグ中に見つけた](https://qiita.com/SIGABRT/items/eb1e6e156930fd81f710)この問題です。ハングアップというか、どうやら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でバックトレースを見たり、変数の内容を覗いたりできます。ここでわかるのは、[このあたり](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/ddb/db_input.c#L361)から呼ばれるcngetc()が、[cn_tabがNULLであるために](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/dev/cons.c#L259)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
```

コンソールデバイスが違いました。この差がどの辺で生まれるのか、ちょっと辿ってみますと、[こんなコード](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/arch/x86/x86/consinit.c#L151)に行き当たります。おそらく、UEFIでは[このif文](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/arch/x86/x86/consinit.c#L186)が成立し、BIOSでは成立しないのでその下のvgaのコードに進むのでしょう。

ここでgenfbの時に呼ばれているx86_genfb_cnattach()は、[ここ](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/arch/x86/x86/genfb_machdep.c#L209)にあります。ちょっと目を疑うようなコードですね。

```
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()が呼ばれる前のアーキテクチャ依存の初期化部](https://github.com/NetBSD/src/blob/658ee66776803a72e7e52efc9276332d81a45371/sys/arch/amd64/amd64/machdep.c#L1714)、[ここ、すなわちmain()の中](https://github.com/NetBSD/src/blob/658ee66776803a72e7e52efc9276332d81a45371/sys/kern/init_main.c#L295)、[ここ、すなわちmain()から呼ばれるcpu_startup()の中](https://github.com/NetBSD/src/blob/658ee66776803a72e7e52efc9276332d81a45371/sys/arch/amd64/amd64/machdep.c#L349)の3回呼ばれています。boot -d、すなわちRB_KDBを処理しているのは、[1回目の直後](https://github.com/NetBSD/src/blob/658ee66776803a72e7e52efc9276332d81a45371/sys/arch/amd64/amd64/machdep.c#L1922)です。

```
#ifdef DDB
	if (boothowto & RB_KDB)
		Debugger();
#endif
```

試しに、このRB_KDBを処理しているif〜Debugger();を、3回目のconsinit()の後に移動してみると、無事DDBのプロンプトが拝めました。ここに移動するのが正しいのでしょうか。ちょっと他のアーキテクチャのソースコードを調べてみました。

結果、大体3通りあることがわかりました。

1. amd64やi386と同じ、main()の前の初期化部 (多数派)
2. main()から呼ばれるcpu_startup()の中
3. 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はしにくくなってしまいました。どなたか目に留まった方報告しといてくださいな。