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