Android のための SMP 入門

目次

  • 導入
  • 理論
    • メモリ一貫性モデル
      • プロセッサ一貫性
      • CPU のキャッシュの動作
      • 可視性
      • ARM の弱い順序付け
    • データメモリバリア
      • 書き込み/書き込み と 読み込み/読み込み
      • 読み込み/書き込み と 書き込み/読み込み
      • バリア命令
      • アドレス依存と因果一貫性
      • メモリバリアの要約
    • アトミック操作
      • アトミックの基礎
      • アトミックとバリアをペアで使う
      • 取得と解放
  • 実践
    • C でしてはいけないこと
      • C/C++ と "volatile"
    • Java でしてはいけないこと
      • Java の "synchronized" と "volatile" キーワード
    • すべきこと
      • 一般的なアドバイス
      • 同期プリミティブの保証
      • C/C++ の来るべき改訂
  • 最後に
  • 付録
    • SMP の失敗例
    • 同期書き込みを実装する
    • 参考文献

ELECOM の Bluetooth マウス M-XG2BB でハマる

これは NetBSD Advent Calendar 2014 の 24 日目の記事です。

ことの始まり

最近ノート PC を LB-J300X に買い換えてわーい高解像度液晶と喜んでいたのは良いのですが、ちゃんと使いはじめるとやっぱりトラックパッドが使いにくいので ELECOM の Bluetooth マウス M-XG2BB を買ったのでした。安い割にはカーソル速度を二段階切り替えできるのが決め手ですね。
NetBSD 上で Bluetooth を使う方法は Chapter 21. Bluetooth on NetBSD に記載があるのでこれに従っていけば問題無く使える筈、でした。

という訳でこの記事では問題の調査から解決までに何をやったかを適当に書いていきたいと思います。

問題発生

  • 左ボタン、右ボタン、ホイールクリック、進むボタン、戻るボタンを押すと何故か上スクロールした後にボタンが押された事になる。
  • ホイールを上スクロール、下スクロールをしても何も起きない。

デバイス調査

dmesg(8)

ボタンは左、右、ホイールクリック、進む、戻るの五つあるので問題ありません。ただ W と Z の二軸検出されているのが気になりますが、それで問題が発生するような事は無いでしょう。

btms0 at bthidev0 reportid 2: 5 buttons, W and Z dirs.
btdevctl(8)

Wheel と AC_Pan があるので W と Z の二軸が検出されているのかな程度で Chapter 21. Bluetooth on NetBSD と見比べてみても特に問題があるようには見えません。

nonaka@koharu$ btdevctl -d ubt0 -a m-xg2bbrd -s HID
local bdaddr: XX:XX:XX:XX:XX:XX
remote bdaddr: YY:YY:YY:YY:YY:YY
link mode: auth
vendor id: 0x056e
product id: 0x00d2
device type: bthidev
control psm: 0x0011
interrupt psm: 0x0013
Collection page=Generic_Desktop usage=Mouse
Collection page=Generic_Desktop usage=Pointer
  Input id=2 size=1 count=1 page=Button usage=Button_1 Variable, logical range 0..1
  Input id=2 size=1 count=1 page=Button usage=Button_2 Variable, logical range 0..1
  Input id=2 size=1 count=1 page=Button usage=Button_3 Variable, logical range 0..1
  Input id=2 size=1 count=1 page=Button usage=Button_4 Variable, logical range 0..1
  Input id=2 size=1 count=1 page=Button usage=Button_5 Variable, logical range 0..1
  Input id=2 size=3 count=1 page=0x0000 usage=0x0000 Const, logical range 0..1
  Input id=2 size=16 count=1 page=Generic_Desktop usage=X Variable Relative, logical range -32768..32767
  Input id=2 size=16 count=1 page=Generic_Desktop usage=Y Variable Relative, logical range -32768..32767
Collection page=0x0000 usage=0x0000
  Input id=2 size=8 count=1 page=Generic_Desktop usage=Wheel Variable Relative, logical range -127..127
End collection
Collection page=0x0000 usage=0x0000
  Input id=2 size=8 count=1 page=Consumer usage=AC_Pan Variable Relative, logical range -127..127
End collection
End collection
End collection

気になるところはありますが、ボタンを押したら上スクロールするような設定があるようには見えません。
もしかしたらデバイスの故障という可能性もあるのでとりあえず Widnows で使ってみましたが特に何の問題も発生せずに使用できました。

X 上での調査

とりあえず xev を使って X 上でどのようなイベントが発生しているのかを見てみましょう。
例えば左ボタンを押すと以下のような ButtonPress event が発生します。左ボタンは button 1 です。

ButtonPress event, serial 39, synthetic NO, window 0x3400001,
    root 0x9b, subw 0x3400002, time 54411455, (30,30), root:(1221,655),
    state 0x0, button 1, same_screen YES

M-XG2BB で左クリックしてみると確かに button 1 の ButtonPress event の前に button 4 (上スクロール) の ButtonPress event が発生しています。他のボタンを押しても同じように実際のボタンの ButtonPress event の前に上スクロールの event が発生します。

ButtonPress event, serial 39, synthetic NO, window 0x3400001,
    root 0x9b, subw 0x3400002, time 55659295, (42,39), root:(1233,664),
    state 0x0, button 4, same_screen YES
(snip)
ButtonPress event, serial 39, synthetic NO, window 0x3400001,
    root 0x9b, subw 0x3400002, time 55659295, (42,39), root:(1233,664),
    state 0x0, button 1, same_screen YES

という事は Bluetooth マウスのデバイスドライバが悪いということでしょうか。

btms(4) 調査その1: 入力データ

NetBSDBluetooth マウスドライバは src/sys/dev/bluetooth/btms.c にあります。btms.c を見ると btms_input() がマウスの移動やボタンが押された、離されたなどを扱っている事がわかります。わかりますよね?

/*****************************************************************************
 *
 *	btms input routine, called from our parent
 */

static void
btms_input(struct bthidev *hidev, uint8_t *data, int len)
{
	struct btms_softc *sc = (struct btms_softc *)hidev;
	int dx, dy, dz, dw;
	uint32_t buttons;
	int i, s;

	if (sc->sc_wsmouse == NULL || sc->sc_enabled == 0)
		return;

	dx =  hid_get_data(data, &sc->sc_loc_x);
	dy = -hid_get_data(data, &sc->sc_loc_y);
	dz =  hid_get_data(data, &sc->sc_loc_z);
	dw =  hid_get_data(data, &sc->sc_loc_w);

	if (sc->sc_flags & BTMS_REVZ)
		dz = -dz;

	buttons = 0;
	for (i = 0 ; i < sc->sc_num_buttons ; i++)
		if (hid_get_data(data, &sc->sc_loc_button[i]))
			buttons |= BUTTON(i);

	if (dx != 0 || dy != 0 || dz != 0 || dw != 0 || buttons != sc->sc_buttons) {
		sc->sc_buttons = buttons;

		s = spltty();
		wsmouse_input(sc->sc_wsmouse,
				buttons,
				dx, dy, dz, dw,
				WSMOUSE_INPUT_DELTA);
		splx(s);
	}
}

btms_input() で重要なのはマウスから渡されたデータであろう data とそれから導き出される dx, dy, dz, dw, buttons です。なのでこれらの変数の中身を出力するようなデバッグ出力を適当に入れて、実際にボタンを押した結果を見てみます。

変数 意味
dx
dy
dz 縦スクロール
dw 横スクロール
buttons ボタン状態
左ボタン
btms_dump_data: btms_input
--------+--------------------------------------------------+------------------+
offset  | +0 +1 +2 +3 +4 +5 +6 +7  +8 +9 +a +b +c +d +e +f | data             |
--------+--------------------------------------------------+------------------+
00000000| 01 00 00 00 00 00 00                             | .......          |
--------+--------------------------------------------------+------------------+
btms_input: dx=0, dy=0, dz=-1, dw=1, buttons=0x00000001, sc->sc_buttons=0x00000000
右ボタン
btms_dump_data: btms_input
--------+--------------------------------------------------+------------------+
offset  | +0 +1 +2 +3 +4 +5 +6 +7  +8 +9 +a +b +c +d +e +f | data             |
--------+--------------------------------------------------+------------------+
00000000| 02 00 00 00 00 00 00                             | .......          |
--------+--------------------------------------------------+------------------+
btms_input: dx=0, dy=0, dz=-2, dw=2, buttons=0x00000004, sc->sc_buttons=0x00000000
ホイールクリック
btms_dump_data: btms_input
--------+--------------------------------------------------+------------------+
offset  | +0 +1 +2 +3 +4 +5 +6 +7  +8 +9 +a +b +c +d +e +f | data             |
--------+--------------------------------------------------+------------------+
00000000| 04 00 00 00 00 00 00                             | .......          |
--------+--------------------------------------------------+------------------+
btms_input: dx=0, dy=0, dz=-4, dw=4, buttons=0x00000002, sc->sc_buttons=0x00000000
進むボタン
btms_dump_data: btms_input
--------+--------------------------------------------------+------------------+
offset  | +0 +1 +2 +3 +4 +5 +6 +7  +8 +9 +a +b +c +d +e +f | data             |
--------+--------------------------------------------------+------------------+
00000000| 10 00 00 00 00 00 00                             | .......          |
--------+--------------------------------------------------+------------------+
btms_input: dx=0, dy=0, dz=-16, dw=16, buttons=0x00000010, sc->sc_buttons=0x00000000
戻るボタン
btms_dump_data: btms_input
--------+--------------------------------------------------+------------------+
offset  | +0 +1 +2 +3 +4 +5 +6 +7  +8 +9 +a +b +c +d +e +f | data             |
--------+--------------------------------------------------+------------------+
00000000| 08 00 00 00 00 00 00                             | .......          |
--------+--------------------------------------------------+------------------+
btms_input: dx=0, dy=0, dz=-8, dw=8, buttons=0x00000008, sc->sc_buttons=0x00000000
ホイール上スクロール
btms_dump_data: btms_input
--------+--------------------------------------------------+------------------+
offset  | +0 +1 +2 +3 +4 +5 +6 +7  +8 +9 +a +b +c +d +e +f | data             |
--------+--------------------------------------------------+------------------+
00000000| 00 00 00 00 00 01 00                             | .......          |
--------+--------------------------------------------------+------------------+
btms_input: dx=0, dy=0, dz=0, dw=0, buttons=0x00000000, sc->sc_buttons=0x00000000
ホイール下スクロール
btms_dump_data: btms_input
--------+--------------------------------------------------+------------------+
offset  | +0 +1 +2 +3 +4 +5 +6 +7  +8 +9 +a +b +c +d +e +f | data             |
--------+--------------------------------------------------+------------------+
00000000| 00 00 00 00 00 ff 00                             | .......          |
--------+--------------------------------------------------+------------------+
btms_input: dx=0, dy=0, dz=0, dw=0, buttons=0x00000000, sc->sc_buttons=0x00000000

ボタンを押した時に buttons に押されたボタンに対応するビットが設定される以外に何故か dz と dw にも値が設定されています。確かにこれならボタンを押した時にスクロールしてしまうのも頷けます。
また、ホイールスクロールは実際の入力されてきたデータではオフセット +05h に 01 や ff といったデータが存在するのに関わらず dz には反映されていません。

btms(4) 調査その2: 構成情報

入力データから dx, dy, dw, dz, buttons を導き出す時に使用している hid_get_data() をとりあえず見てみましょう。
hid_get_data() は src/sys/dev/usb/hid.c にあります。Bluetooth の HID は USB の HID と同じなんですね。
hid_get_data() では data の loc->pos ビット目から loc->size ビット分のデータを取り出しています。ということで問題なのは struct hid_location *loc ということになります。

long
hid_get_data(const u_char *buf, const struct hid_location *loc)
{
	u_int hsize = loc->size;
	u_long data;

	if (hsize == 0)
		return (0);

	data = hid_get_udata(buf, loc);
	if (data < (1 << (hsize - 1)))
		return (data);
	return data - (1 << hsize);
}

u_long
hid_get_udata(const u_char *buf, const struct hid_location *loc)
{
	u_int hpos = loc->pos;
	u_int hsize = loc->size;
	u_int i, num, off;
	u_long data;

	if (hsize == 0)
		return (0);

	data = 0;
	off = hpos / 8;
	num = (hpos + hsize + 7) / 8 - off;

	for (i = 0; i < num; i++)
		data |= buf[off + i] << (i * 8);

	data >>= hpos % 8;
	data &= (1 << hsize) - 1;

	DPRINTFN(10,("hid_get_udata: loc %d/%d = %lu\n", hpos, hsize, data));
	return (data);
}

btms.c では btms_input() を見ればわかるように sc_loc_x, sc_loc_y, sc_loc_z, sc_loc_w, sc_loc_button[] とそれぞれのイベントに対応する struct hid_location が存在します。
それらの struct hid_location はどこで設定されるのかと言えば btms.c の btms_attach() になります。btms_attach() は長いので Z と W に関連する箇所だけ抜き出します。

	/* Try the wheel first as the Z activator since it's tradition. */
	hl = hid_locate(ba->ba_desc,
			ba->ba_dlen,
			HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL),
			ba->ba_id,
			hid_input,
			&sc->sc_loc_z,
			&flags);

	zloc = &sc->sc_loc_z;
	if (hl) {
		if (NOTMOUSE(flags)) {
			aprint_error("Wheel report 0x%04x ignored\n", flags);

			/* ignore Bad Z coord */
			sc->sc_loc_z.size = 0;
		} else {
			sc->sc_flags |= BTMS_HASZ;
			/* Wheels need the Z axis reversed. */
			sc->sc_flags ^= BTMS_REVZ;
			/* Put Z on the W coordinate */
			zloc = &sc->sc_loc_w;
		}
	}

	hl = hid_locate(ba->ba_desc,
			ba->ba_dlen,
			HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z),
			ba->ba_id,
			hid_input,
			zloc,
			&flags);

	/*
	 * The horizontal component of the scrollball can also be given by
	 * Application Control Pan in the Consumer page, so if we didnt see
	 * any Z then check that.
	 */
	if (!hl) {
		hl = hid_locate(ba->ba_desc,
				ba->ba_dlen,
				HID_USAGE2(HUP_CONSUMER, HUC_AC_PAN),
				ba->ba_id,
				hid_input,
				zloc,
				&flags);
	}

	if (hl) {
		if (NOTMOUSE(flags))
			zloc->size = 0;	/* ignore Z */
		else {
			if (sc->sc_flags & BTMS_HASZ)
				sc->sc_flags |= BTMS_HASW;
			else
				sc->sc_flags |= BTMS_HASZ;
		}
	}

関連する hid_locate() の戻り値と struct hid_location の値を出力するようなデバッグ出力を適当に入れてみました。

btms_attach: X=1, pos=8, size=16, flags=6
btms_attach: Y=1, pos=24, size=16, flags=6
btms_attach: WHEEL=1, pos=0, size=8, flags=6
btms_attach: Z=0, pos=0, size=0, flags=6
btms_attach: AC_PAN=1, pos=0, size=8, flags=6

注目するのは WHEEL と AC_PAN の pos と size です。どちらも pos=0 と size=8 になっています。という事は data の先頭の 8 ビットをホイールの入力値として扱っているということになります。先ほどの各ボタンを押した時のデータを見てみると先頭 8 ビットはどのボタンを押したかのデータが設定されています。

--------+--------------------------------------------------+------------------+
offset  | +0 +1 +2 +3 +4 +5 +6 +7  +8 +9 +a +b +c +d +e +f | data             |
--------+--------------------------------------------------+------------------+
00000000| 01 00 00 00 00 00 00                             | .......          |
--------+--------------------------------------------------+------------------+
          ^^

解決方法

今回の問題は M-XG2BB から取得した軸やボタンの構成情報が適切で無かったため、ボタンを押すと一緒にスクロールしてしまうという事がわかりました。
ただし Windows 上ではちゃんと動いているので NetBSD でこの情報を取得する際の処理が何かおかしいという可能性も無くはないのですが、他の Bluetooth マウスではそんな事は起きていないようですし今回はそこに深入りする事は避けて btms(4) に閉じた対処方法で逃げたいと思います。

今回の対処方法は ELECOM M-XG2BB であれば以下の様に構成情報を補正して正しく使えるようにするという事になります。

  • sc_loc_z.pos を実際のデータが設定される 40 に変更
  • 横スクロールは無いので sc_loc_w.size を 0 に変更して、更に sc_flags から BTMS_HASW をクリア

Bluetooth マウスが ELECOM M-XG2BB かどうかは btms_attach() に渡される struct bthidev_attach_args *baa の ba_vendor と ba_product で判断できます。ELECOM M-XG2BB であれば ba_vendor が 0x056e、ba_product が 0x00d2 になります。
上記のことを踏まえつつ NetBSD 的な流儀でパッチを書くとこんな感じになります。次に同じような事があった時の為にデバッグ出力も ifdef で出力できるように残してあります、まあ無いんでしょうけど。

パッチを適用したカーネルではボタンをクリックしてももうスクロールすることは無くなりましたし、ホイールもちゃんと動作するようになりました。幸せ。

btms0 at bthidev0 reportid 2: 5 buttons and Z dir.

おわりに

稀に何かおかしいデバイスも存在するという話でした。
ちなみに NetBSDカーネルを quirks で検索すると色々なところで何かおかしなデバイスに対応している処理などが見つかりますので、興味があればどうぞ。