NetBSD-9で、QEMU USB Tabletが使えない件

## NetBSD-9で、QEMU USB Tabletが使えない

[この記事](https://qiita.com/SIGABRT/items/431271aa6a3629b08bc9)の関連です。

どうやら、ボタンは押せているようだけれども、ポインタが左上から移動しないので使い物になりません。ためしに、こんなプログラムを書いて

```wsmousetest.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <dev/wscons/wsconsio.h>

static char *progname;

static void
usage(void)
{
	printf("Usage: %s /dev/wsmouse\n", progname);
	exit(EXIT_FAILURE);
}

static struct {
	unsigned int type;
	char *name;
} wsmouse_type_table[] = {
	{0, "Zero"},
	{WSMOUSE_TYPE_VSXXX, "DEC serial"},
	{WSMOUSE_TYPE_PS2, "PS/2-compatible"},
	{WSMOUSE_TYPE_USB, "USB mouse"},
	{WSMOUSE_TYPE_LMS, "Logitech busmouse"},
	{WSMOUSE_TYPE_MMS, "Microsoft InPort mouse"},
	{WSMOUSE_TYPE_TPANEL, "Generic Touch Panel"},
	{WSMOUSE_TYPE_NEXT, "NeXT mouse"},
	{WSMOUSE_TYPE_ARCHIMEDES, "Archimedes mouse"},
	{WSMOUSE_TYPE_HIL, "HIL mouse"},
	{WSMOUSE_TYPE_AMIGA, "Amiga mouse"},
	{WSMOUSE_TYPE_MAXINE, "DEC maxine mouse"},
	{WSMOUSE_TYPE_MAPLE, "Dreamcast Maple mouse"},
	{WSMOUSE_TYPE_SGI, "SGI mouse"},
	{WSMOUSE_TYPE_BLUETOOTH, "Bluetooth mouse"},
	{WSMOUSE_TYPE_ADB, "ADB mouse or touchpad"},
	{WSMOUSE_TYPE_PSEUDO, "not actually a mouse"},
	{0, ""}
};
#define WSMOUSE_NTYPES		\
	(sizeof(wsmouse_type_table)/sizeof(wsmouse_type_table[0]))

static struct {
	unsigned int type;
	char *name;
} wsmouse_event_table[] = {
	{0, "Zero"},
	{WSCONS_EVENT_KEY_UP, "KEY_UP key code"},
	{WSCONS_EVENT_KEY_DOWN, "KEY_DOWN key code"},
	{WSCONS_EVENT_ALL_KEYS_UP, "ALL_KEYS_UP"},
	{WSCONS_EVENT_MOUSE_UP, "MOUSE_UP button # (leftmost = 0)"},
	{WSCONS_EVENT_MOUSE_DOWN, "MOUSE_DOWN button # (leftmost = 0) "},
	{WSCONS_EVENT_MOUSE_DELTA_X, "MOUSE_DELTA_X X delta amount"},
	{WSCONS_EVENT_MOUSE_DELTA_Y, "MOUSE_DELTA_Y Y delta amount"},
	{WSCONS_EVENT_MOUSE_ABSOLUTE_X, "MOUSE_ABS_X X location"},
	{WSCONS_EVENT_MOUSE_ABSOLUTE_Y, "MOUSE_ABS_Y Y location"},
	{WSCONS_EVENT_MOUSE_DELTA_Z, "MOUSE_DELTA_Z Z delta amount"},
	{WSCONS_EVENT_MOUSE_ABSOLUTE_Z, "MOUSE_ABS_Z Z location"},
	{WSCONS_EVENT_SCREEN_SWITCH, "SCREEN_SWITCH New screen number"},
	{WSCONS_EVENT_ASCII, "ASCII key code is already ascii"},
	{WSCONS_EVENT_MOUSE_DELTA_W, "MOUSE_DELTA_W W delta amount"},
	{WSCONS_EVENT_MOUSE_ABSOLUTE_W, "MOUSE_ABS_W W location"},
	{0, ""}
};
#define WSMOUSE_NEVENTS		\
	(sizeof(wsmouse_event_table)/sizeof(wsmouse_event_table[0]))

void
show_type(const char *name, unsigned int type)
{
	if (type >= WSMOUSE_NTYPES) {
		printf("unknown type %u\n", type);
		exit(EXIT_FAILURE);
	}
	printf("%s: type %s\n", name, wsmouse_type_table[type].name);
}

int
main(int argc, char *argv[])
{
	int fd, r;
	unsigned int type;
	struct wscons_event ev;

	progname = argv[0];
	if (argc != 2)
		usage();

	fd = open(argv[1], O_RDONLY);
	if (fd < 0) {
		perror("open");
		exit(EXIT_FAILURE);
	}

	r = ioctl(fd, WSMOUSEIO_GTYPE, &type);
	if (r < 0) {
		perror("WSMOUSEIO_GTYPE");
		exit(EXIT_FAILURE);
	}
	show_type(argv[1], type);

	while(1) {
		r = read(fd, &ev, sizeof(ev));
		if (r < 0) {
			perror("read");
			exit(EXIT_FAILURE);
		}
		printf("type %u, value %d -- ", ev.type, ev.value);
		if (ev.type > WSMOUSE_NEVENTS)
			printf("unknown event\n");
		else
			printf("%s\n", wsmouse_event_table[ev.type].name);
	}

	close(fd);

	return 0;
}
```

/dev/wsmouseのイベントを拾ってみると、たしかにボタンダウン/ボタンアップのイベントは飛んでくるけれど移動のイベントは飛んできません。同じプログラムを古いNetBSD-6で実行すると、ちゃんと飛んできます。

しかたがないのでデバッグしてみました。dmesgをみてみますと、QEMU USB Tabletは

```
uhidev0 at uhub3 port 3 configuration 1 interface 0
uhidev0: QEMU (0x627) QEMU USB Tablet (0x01), rev 2.00/0.00, addr 2, iclass 3/0
ums0 at uhidev0: 3 buttons and Z dir
wsmouse1 at ums0 mux 0
```

という感じで認識されているので、その辺のソースコードを見て (例えば、umsなら[こんな感じです](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/dev/usb/ums.c#L66))、デバッグ機能を有効にしてカーネルを作り直してみます。Linuxのdynamic debugのような洒落た機能はありません。

具体的には、/usr/src/sys/arch/amd64/conf/GENRIC.localというファイルを作って、

```GENERIC.local
options         WSMUX_DEBUG
options         UMS_DEBUG
options         UHID_DEBUG, UHIDEV_DEBUG, HIDMS_DEBUG
```

`cd /usr/src/sys/arch/amd64/conf ; config GENERIC ; cd ../compile/GENERIC ; make dependall`という感じ。できたnetbsdというファイルを/にコピーして (古いのは保存しておく)、再起動。bootプロンプトが出たら、3 (Drop to boot prompt) を押して、`boot -d`。-dオプションは、カーネル起動前に内蔵デバッガ (DDB) のプロンプトを出すようにします。

が、ここで止まってしまいました。なんということでしょうか、ここにもバグがありました。後で追求することにして、ひとまず普通に起動、login:プロンプトが出たら、Ctrl-Alt-Escapeを押してDDBのプロンプトを出します。ソースコードを読みますと、本来、マウスを動かすとUSB共通部→uhidev→ums→hidms→wsmouseという感じで割り込みが伝えられるようです。DDBで、

```
db{0}> w/l hidmsdebug 10
netbsd:hidmsdebug		0 = 10
db{0}> c
```

のようにすると、hidmsドライバのデバッグprintが動くようになります。これはNetBSDの各種サブシステムをデバッグする時の定番なので、覚えておきましょう。

で、調べてみると、hidmsにまでは割り込みは飛んでくるし、伝えられるポインタの座標も正しいように見えます。が、wsmouseには伝えられません。[ここ](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/dev/hid/hidms.c#L258)ですね。[このif文](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/dev/hid/hidms.c#L285)で弾かれているみたいです。

うーん、怪しいのは[このtpcalibなんとか](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/dev/hid/hidms.c#L273)というやつかなぁ。と、-currentをみると…　あ、[これ](http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/dev/usb/ums.c?rev=1.94&content-type=text/x-cvsweb-markup)かな。tpcalibってなんのライブラリかと思ったら、Touch Panel CALIBrationてことかな。このパッチを当てたら、ちゃんとwsmouseまでイベントが飛んでくるようになりましたとさ。


## XでQEMU USB Tabletがつかいものにならない

いざ、X起動、というところなんですが、やっぱりポインタ動きませんねー。色々試してみると、ポインタを左上の方に動かしてみると、なんとなく動かした方向に動く気がします。でもちょっと動かしただけでものすごく動いて、思ったところにはなかなか動かせません。さっきのwsmousetestによると、QEMU USB Tabletが伝えてくる座標はX、Yとも0から32767くらいのようですが、そのうち画面の広さに相当する0から1023 (X)、0から767 (Y) の部分に入った時だけポインタが動く、ということみたいです。デバイスによってこの座標の範囲が違うのでしょうね。

NetBSD-6の仮想マシンではこう言うことは起きないので、どうしたことかと調べてみたら、VGAとしてvmware (VMVGA) を使っていました。マウスもvmmouseというドライバが使われています。これでもいいのですが、できればQXLとかもっとそれっぽいのを使いたいものです。通常のマウスドライバを直す方から考えて、うまくいかないようなら戻ってくることにしましょう。

Xサーバのマウスドライバは[この辺](http://cvsweb.netbsd.org/bsdweb.cgi/xsrc/external/mit/xf86-input-mouse/dist/src/bsd_mouse.c?rev=1.5&content-type=text/x-cvsweb-markup)かなぁ。たしかに、wsmouseから受け取った座標をそのままXに流しています。

よく見ると、[ws](http://cvsweb.netbsd.org/bsdweb.cgi/xsrc/external/mit/xf86-input-ws/dist/src/ws.c?rev=1.11&content-type=text/x-cvsweb-markup)というドライバもあって、チラ見するとmin_xとかmax_yとか怪しげな文字が見えます。試しにこれを使ってみましょう。

まず、/var/log/Xorg.0.logから、built-in configurationを切り出してきて、これにwsドライバの記述を加えたものを/etc/X11/xorg.confに起きます。

```xorg.conf
Section "InputDevice"
	Identifier	"Mouse0"
	Driver		"ws"
	Option		"Device" "/dev/wsmouse"
EndSection

Section "Device"
        Identifier      "Builtin Default qxl Device 0"
        Driver  "qxl"
EndSection
Section "Screen"
        Identifier      "Builtin Default qxl Screen 0"
        Device  "Builtin Default qxl Device 0"
EndSection
Section "Device"
        Identifier      "Builtin Default modesetting Device 0"
        Driver  "modesetting"
EndSection
Section "Screen"
        Identifier      "Builtin Default modesetting Screen 0"
        Device  "Builtin Default modesetting Device 0"
EndSection
Section "Device"
        Identifier      "Builtin Default vesa Device 0"
        Driver  "vesa"
EndSection
Section "Screen"
        Identifier      "Builtin Default vesa Screen 0"
        Device  "Builtin Default vesa Device 0"
EndSection
Section "Device"
        Identifier      "Builtin Default wsfb Device 0"
        Driver  "wsfb"
EndSection
Section "Screen"
        Identifier      "Builtin Default wsfb Screen 0"
        Device  "Builtin Default wsfb Device 0"
EndSection
Section "ServerLayout"
        Identifier      "Builtin Default Layout"
        Screen  "Builtin Default qxl Screen 0"
        Screen  "Builtin Default modesetting Screen 0"
        Screen  "Builtin Default vesa Screen 0"
        Screen  "Builtin Default wsfb Screen 0"
	InputDevice    "Mouse0" "CorePointer"
EndSection
```

いざ、xinit! と思ったけど、うーん、全く変わらん。でも、Xorg.0.logを眺めてみると、Mouse0: minimum x position: 0とか、Mouse0: maximum x position: 1023のような文字が見えます。wsドライバのソースコードをよく見ると、これらの値はxorg.confのオプションMinX、MaxX、MinY、MaxYで変えられるようです。

```
Section "InputDevice"
	Identifier	"Mouse0"
	Driver		"ws"
	Option		"Device" "/dev/wsmouse"
	Option		"MinX" "0"
	Option		"MaxX" "32767"
	Option		"MinY" "0"
	Option		"MaxY" "32767"
EndSection
...
```

これでなんとなく動いているような気がします。しかし、こんなものをいちいち書かないといけないの、スマートじゃないですね。もうちょっとまじめにwsドライバのソースを見てみることにしましょう。

これらオプションの値は[pInfo->private](https://github.com/NetBSD/xsrc/blob/fac65f31cbeeee4c09b55c1c8ee674a5d4a743ba/external/mit/xf86-input-ws/dist/src/ws.c#L143)の[min_x、max_x、min_y、max_y](https://github.com/NetBSD/xsrc/blob/fac65f31cbeeee4c09b55c1c8ee674a5d4a743ba/external/mit/xf86-input-ws/dist/src/ws.c#L307)というメンバーに保存され、[xf86InitValuatorAxisStruct()](https://github.com/NetBSD/xsrc/blob/fac65f31cbeeee4c09b55c1c8ee674a5d4a743ba/external/mit/xf86-input-ws/dist/src/ws.c#L463)という関数でXorgサーバ に伝えられているようです。priv->typeがWSMOUSE_TYPE_TPANEL (タッチパネル) か否かで動作を変えている部分がありますが、さきほどのwsmousetestの結果にもあるように、QEMU USB TabletはWSMOUSE_TYPE_TPANELです (ただこれとは別にpInfo->type_nameとしてXI_TOUCHSCREENかXI_MOUSEを設定している部分があり、別な基準なので矛盾が発生したりしないのかは気になりますが、無視しておきます)。

さてこのmin_x、…ですが、[WSMOUSEIO_GCALIBCOORDS](https://github.com/NetBSD/xsrc/blob/fac65f31cbeeee4c09b55c1c8ee674a5d4a743ba/external/mit/xf86-input-ws/dist/src/ws.c#L277)というioctlで取得しているようです。このioctlは、[wsmouse共通部](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/dev/wscons/wsmouse.c#L695)にはなく、その後accessops経由で呼ばれる[umsのioctl処理部](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/dev/usb/ums.c#L316)にもありません。そこで、ums.cの最後に[タッチスクリーンドライバ](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/dev/usb/uts.c#L357)あたりを参考に追加してみました。うーん、これでもダメです。どうやらWSMOUSEIO_GCALIBCOORDSを発行しても、正しい座標の最大値は返してくれないようです。

同じようにumsとタッチスクリーンドライバと見比べてみると、タッチスクリーンドライバでは[このあたり](https://github.com/NetBSD/src/blob/9204b1783071a2bb1711eb13fedf55dee4fa952d/sys/dev/usb/uts.c#L240)でデバイスから情報を取得してsoftcの中に設定しているようですが、umsには該当する部分がありません。これをコピペしてみましょうか。

```
Index: ums.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ums.c,v
retrieving revision 1.93.2.3
diff -u -r1.93.2.3 ums.c
--- ums.c	21 Jan 2020 19:54:55 -0000	1.93.2.3
+++ ums.c	6 Jul 2020 09:19:42 -0000
@@ -82,6 +82,7 @@
 
 	int	sc_enabled;
 	char	sc_dying;
+	struct wsmouse_calibcoords sc_calibcoords;
 };
 
 Static void ums_intr(struct uhidev *, void *, u_int);
@@ -140,6 +141,8 @@
 	int size, error;
 	void *desc;
 	uint32_t quirks;
+	struct hid_data * d;
+	struct hid_item item;
 
 	aprint_naive("\n");
 
@@ -203,8 +206,35 @@
 		sc->sc_alwayson = true;
 	}
 
+	tpcalib_init(&sc->sc_ms.sc_tpcalib);
 	hidms_attach(self, &sc->sc_ms, &ums_accessops);
 
+	/* calibrate the touchscreen */
+	memset(&sc->sc_calibcoords, 0, sizeof(sc->sc_calibcoords));
+	sc->sc_calibcoords.maxx = 4095;
+	sc->sc_calibcoords.maxy = 4095;
+	sc->sc_calibcoords.samplelen = WSMOUSE_CALIBCOORDS_RESET;
+	d = hid_start_parse(desc, size, hid_input);
+	if (d != NULL) {
+		while (hid_get_item(d, &item)) {
+			if (item.kind != hid_input
+			    || HID_GET_USAGE_PAGE(item.usage) != HUP_GENERIC_DESKTOP
+			    || item.report_ID != sc->sc_hdev.sc_report_id)
+				continue;
+			if (HID_GET_USAGE(item.usage) == HUG_X) {
+				sc->sc_calibcoords.minx = item.logical_minimum;
+				sc->sc_calibcoords.maxx = item.logical_maximum;
+			}
+			if (HID_GET_USAGE(item.usage) == HUG_Y) {
+				sc->sc_calibcoords.miny = item.logical_minimum;
+				sc->sc_calibcoords.maxy = item.logical_maximum;
+			}
+		}
+		hid_end_parse(d);
+	}
+	tpcalib_ioctl(&sc->sc_ms.sc_tpcalib, WSMOUSEIO_SCALIBCOORDS,
+	    (void *)&sc->sc_calibcoords, 0, 0);
+
 	if (sc->sc_alwayson) {
 		error = uhidev_open(&sc->sc_hdev);
 		if (error != 0) {
@@ -327,6 +357,10 @@
 		else
 			*(u_int *)data = WSMOUSE_TYPE_USB;
 		return 0;
+
+        case WSMOUSEIO_SCALIBCOORDS:
+        case WSMOUSEIO_GCALIBCOORDS:
+                return tpcalib_ioctl(&sc->sc_ms.sc_tpcalib, cmd, data, flag, p);
 	}
 
 	return EPASSTHROUGH;
```

これでカーネルを作り直したところ、Options "MaxX"とかを追加する必要はなくなりました。

xorg.confを作らなくて済むようにするためには、xf86-input-mouseのbsd_mouseドライバを直す必要がありますが、力尽きました。

あと、よく考えるとUSB Tabletを後からホットプラグすると、GCALIBCOORDS呼ばれませんね。左上だけ認識する状態になります。これはまあ、wsmouseの仕様がイケていない、ということですね。wsmuxでうまいこと吸収して、常に0〜32767を返すようにする、というような工夫が必要でしょう。

##おわりに

[うっかり匿名のry](https://qiita.com/SIGABRT/items/431271aa6a3629b08bc9#おしまい)

そうそう、NetBSD-9.0のXには、QXLドライバありませんでしたw