HID のドキュメントは Documentation/hid 以下にあります。HID の全体図は Documentation/hid/hid-transport.txt にあります。
+-----------+ +-----------+ +-----------+ +-----------+ | Device #1 | | Device #i | | Device #j | | Device #k | +-----------+ +-----------+ +-----------+ +-----------+ \\ // \\ // +------------+ +------------+ | I/O Driver | | I/O Driver | +------------+ +------------+ || || +------------------+ +------------------+ | Transport Driver | | Transport Driver | +------------------+ +------------------+ \___ ___/ \ / +----------------+ | HID Core | +----------------+ / | | \ / | | \ ____________/ | | \_________________ / | | \ / | | \ +----------------+ +-----------+ +------------------+ +------------------+ | Generic Driver | | MT Driver | | Custom Driver #1 | | Custom Driver #2 | +----------------+ +-----------+ +------------------+ +------------------+
USB の場合で全体図のブロックとファイルの対応を見ていきます。次の表のように "I/O Driver" が USB core drivers/usb/core の drivers/usb/core/message.c が主に該当します。"Transport Driver" が drivers/hid/usb/hid-core.c に該当します。"HID Core" が drivers/hid/hid-core.c (こちらも hid-core.c という名前なので要注意), drivers/hid/hid-input.c に該当します。キーボードやマウスの場合は "Generic Driver" が drivers/hid/hid-generic.c に該当します。
ブロック | 主なファイル | 機能 |
I/O Driver | drivers/usb/core の drivers/usb/core/message.c | ハードウエア入出力をする |
Transport Driver | drivers/hid/usbhid/hid-core.c | ハードウエア入出力をデバイスのバスに依存しない様に抽象化する |
HID Core | drivers/hid/hid-core.c drivers/hid/hid-input.c | Report Descriptor を解釈する Report を input event に変換する HID class driver を登録・削除する HID class device を登録・削除する |
Generic Driver | drivers/hid/hid-generic.c | 殆ど何もしないドライバ、HID Core の機能で十分な場合に使う。 |
Custom Driver | drivers/hid-* | 特別な対応が必要なデバイスのドライバ |
Boot Interface Subclass デバイス
USB HID device のうち Boot Interface Subclass デバイスに対して専用のドライバ drivers/hid/usbhid/usbkbd.c と drivers/hid/usbhid/usbmouse.c があります。これらは input class device として動作します。Linux kernel 内では HID class device (あるいは driver) ではありません。
drivers/hid/hid-generic.c の実装をみると殆ど何もしていません。Report Descriptor の解釈、Report を input event に変換する処理は "HID core" を構成する drivers/hid/hid-core.c, drivers/hid/hid-input.c で行われています。Application と直接 I/O する処理は drivers/hid/hidraw.c です。USB HID device は drivers/hid/usbhid/hiddev.c も Application と直接 I/O できます。
Application が HID device として機能できるよう、仮想的なデバイス drivers/hid/uhid.c があります。
"Transport Driver" の実装を見ていきます。"Transport Driver" は struct hid_ll_driver を構成するメンバが指している関数です。 I2C, bluetooth, USB, Hyper-V(仮想環境内で外界と繋がった HID デバイス), User space I/O driver (uhid) の "Transport Driver" は次の表に示すように実装されています。
BUS | "Transport Driver" すなわち struct hid_ll_driver の実装 |
I2C | i2c_hid_ll_driver |
bluetooth | hidp_hid_driver |
USB | usb_hid_driver |
Hyper-V | mousevsc_ll_driver |
uhid | uhid_hid_driver |
struct hid_ll_driver のメンバとそのラッパー関数の関係は次のようになっています。詳細な調査は後回しにします。
メンバ(メソッド) | 説明 | ラッパ関数 | HID Core 内部使用 |
start | start underlaying HW | hid_hw_start() | hid_device_probe() |
stop | stop underlaying HW | hid_hw_stop() | hid_device_remove() |
open | signal underlaying HW to start delivering events | hid_hw_open() | hidinput_open() |
close | signal underlaying HW to stop delivering events | hid_hw_close() | hidinput_close() |
power | requests underlying HW to go into given power mode | hid_hw_power() | |
parse | this method is called only once to parse the device data | hid_add_device() | |
request | send report request to device | hid_hw_request() | |
wait | wait for buffered io to complete | hid_hw_wait() | |
raw_request | send report request to device | hid_hw_raw_request() | hidinput_led_worker(), hidinput_led_worker() |
output_report | send output report to device | hid_hw_output_report() | |
idle | send idle request to device | hid_hw_idle() |
USB HID device (drivers/hid/usbhid/hiddev.c) が提供する直接 I/O 機能 drivers/hid/usbhid/hiddev.c 向けに struct hid_device に関数を指すメンバが次のように実装されています。
hid_device メンバ(メソッド) | 呼び出し元 |
hiddev_connect | hid_connect() |
hiddev_disconnect | hid_disconnect() |
hiddev_hid_event | mt_touch_event(), ntrig_event(), hid_process_event() |
hiddev_report_event | hid_report_raw_event() |
hid_device 構造体が持つメソッドについて
hiddev_connect .. hiddev_report_event メンバは HID Class driver (device) の全体的な構造としてみると I2C, bluetooth, Hyper-V, uhid いずれのバスでも関数を実装することが可能な様に見えます。実質的には USB device だけに使われる実装です。
"Custom Driver" 向けに HID プロトコルを進める随所に hook 関数を仕込めるように struct hid_driver メンバーの一部は次の表のように関数を指すポインタになっています。詳細な解析は後回しにします。
メンバ (メソッド) | 説明 | HID Core 内部の呼び出し元(一部は Transport Driver) |
probe | new device inserted | hid_device_probe() |
remove | device removed (NULL if not a hot-plug capable driver) | hid_device_remove() |
report_table | on which reports to call raw_event (NULL means all) | hid_match_report(), hid_input_report() この中で raw_event の呼び出し判定をしている。 |
raw_event | if report in report_table, this hook is called (NULL means nop) | hid_input_report() |
usage_table | on which events to call event (NULL means all) | hid_match_usage() &ogdef(hid_process_event(),hid_process_event); この中で event の呼び出しを判定している |
event | if usage in usage_table, this hook is called (NULL means nop) | hid_process_event() |
report | this hook is called after parsing a report (NULL means nop) | hid_report_raw_event() |
report_fixup | called before report descriptor parsing (NULL means nop) | hid_open_report() |
input_mapping | invoked on input registering before mapping an usage | hidinput_configure_usage() |
input_mapped | invoked on input registering after mapping an usage | hidinput_configure_usage() |
input_configured | invoked just before the device is registered | hidinput_connect() |
feature_mapping | invoked on feature registering | report_features() |
suspend | invoked on suspend (NULL means nop) | usbhid hid_suspend(), i2c_hid_suspend() |
resume | invoked on resume if device was not reset (NULL means nop) | hid_resume_common() |
reset_resume | invoked on resume if device was reset (NULL means nop) | usbhid hid_reset_resume(), i2c_hid_resume() |
HID class device の登録と HID class driver の関係を見ていきましょう。
USB, bluetooth, I2C, 他 接続にて HID device と認識し、HID class device として hid_allocate_device(), hid_add_device() にてデバイスを登録すると、hid_ignore() によって HID class device として扱うか判断されます。判断した結果エラーになることもあります。Linux Kernel の中では珍しい動作です。
hid_ignore_list に含まれているデバイスのドライバ
hid_ignore() はいくつかの条件判定と除外リスト hid_ignore_list で構成されています。除外されたデバイスの一部は別のドライバで扱います。例えば drivers/input/mouse/synaptics_usb.c, drivers/usb/misc/ldusb.c は、hid_ignore_list に含まれるデバイスのドライバです。
hid_add_device() は "Transport Driver" の struct hid_ll_driver->parse を呼び出し Report Descriptor の取得を行います。続けて hid_have_special_driver を hid_match_id() に渡して "Generic Driver" と同じ動作で良ければ(すなわち、hid_have_special_driver の要素に無ければ) hid_scan_report() を呼び出して Report Descriptor をパースします。device_add() を呼び出し、drivers/base/dd.c の device_attach() にてドライバと照合する処理を始めます。
hid_have_special_driver の要素に格納したデバイスと hid_*.c の struct hid_driver.id_table 要素について
hid_have_special_driver の要素に格納してあるデバイスは "Custom Driver" の実装である /hid-.*/(ドライバソースの正規表現です) の struct hid_driver の id_table メンバが指す配列の要素にも格納してあります。2 重に記述する必要が有り、保守や driver を module として組み込む場合に注意が必要です。
ドライバを後から組み込んだ場合について
hid_register_driver() にてデバイスが接続された後、ドライバを組み込んだ場合は、bus_add_driver(), driver_attach() を経てデバイスと照合する処理を始めます。
デバイスとドライバの照合は hid_bus_match() で行います。適合したと判断したドライバがあれば、hid_device_probe() が呼ばれます。hid_device_probe() の実装は巧妙な点が多いです。probe が呼ばれた時点で struct hid_device hdev の driver メンバが != NULL になっている場合があります。Hyper-V の mouse ドライバ (drivers/hid/hid-hyperv.c) の場合だけです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| - | - | | ! | | - | - | | | ! - | | ! ! |
|
hid_match_device()をもう一度呼んでいる
hid_match_device() をもう一度呼ぶ理由はなぜだろうか?もっともらしい理由が見つかっていないです。drivers/base/dd.c の処理で hid_match_device() を呼び出しドライバは適合していると確認済みです。強いて言うならば、struct hid_driver の id_table から適合デバイスを見つけ出す処理を "Custom Driver" それぞれで実装する負担を減らすためかと思っています。
HID class driver struct hid_driver に probe 関数を指すメンバがあります。これは珍しいことです。しかも、実装は必須ではありません。probe 関数が有る場合は、既定動作の Report Descriptor の解釈(hid_open_report()) と 接続処理(hid_hw_start()) の代わりに probe 関数を呼び出します。