HID Class の全体図

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 HID の場合

USB の場合で全体図のブロックとファイルの対応を見ていきます。次の表のように "I/O Driver" が USB core drivers/usb/coredrivers/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 Driverdrivers/usb/coredrivers/usb/core/message.cハードウエア入出力をする
Transport Driverdrivers/hid/usbhid/hid-core.cハードウエア入出力をデバイスのバスに依存しない様に抽象化する
HID Coredrivers/hid/hid-core.c
drivers/hid/hid-input.c
Report Descriptor を解釈する
Report を input event に変換する
HID class driver を登録・削除する
HID class device を登録・削除する
Generic Driverdrivers/hid/hid-generic.c殆ど何もしないドライバ、HID Core の機能で十分な場合に使う。
Custom Driverdrivers/hid-*特別な対応が必要なデバイスのドライバ

Boot Interface Subclass デバイス

USB HID device のうち Boot Interface Subclass デバイスに対して専用のドライバ drivers/hid/usbhid/usbkbd.cdrivers/hid/usbhid/usbmouse.c があります。これらは input class device として動作します。Linux kernel 内では HID class device (あるいは driver) ではありません。

Generic 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 できます。

HID User-space I/O driver

Application が HID device として機能できるよう、仮想的なデバイス drivers/hid/uhid.c があります。

Transport Driver の実装

"Transport Driver" の実装を見ていきます。"Transport Driver" は struct hid_ll_driver を構成するメンバが指している関数です。

fileOpenGrok(linux-4.1.27/include/linux/hid.h:729)
Expand allFold all
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
struct hid_ll_driver {
        int (*start)(struct hid_device *hdev);
        void (*stop)(struct hid_device *hdev);
 
        int (*open)(struct hid_device *hdev);
        void (*close)(struct hid_device *hdev);
 
        int (*power)(struct hid_device *hdev, int level);
 
        int (*parse)(struct hid_device *hdev);
 
        void (*request)(struct hid_device *hdev,
                        struct hid_report *report, int reqtype);
 
        int (*wait)(struct hid_device *hdev);
 
        int (*raw_request) (struct hid_device *hdev, unsigned char reportnum,
                            __u8 *buf, size_t len, unsigned char rtype,
                            int reqtype);
 
        int (*output_report) (struct hid_device *hdev, __u8 *buf, size_t len);
 
        int (*idle)(struct hid_device *hdev, int report, int idle, int reqtype);
};

I2C, bluetooth, USB, Hyper-V(仮想環境内で外界と繋がった HID デバイス), User space I/O driver (uhid) の "Transport Driver" は次の表に示すように実装されています。

BUS"Transport Driver" すなわち struct hid_ll_driver の実装
I2Ci2c_hid_ll_driver
bluetoothhidp_hid_driver
USBusb_hid_driver
Hyper-Vmousevsc_ll_driver
uhiduhid_hid_driver

struct hid_ll_driver のメンバとそのラッパー関数の関係は次のようになっています。詳細な調査は後回しにします。

メンバ(メソッド)説明ラッパ関数HID Core 内部使用
startstart underlaying HWhid_hw_start()hid_device_probe()
stopstop underlaying HWhid_hw_stop()hid_device_remove()
opensignal underlaying HW to start delivering eventshid_hw_open()hidinput_open()
closesignal underlaying HW to stop delivering eventshid_hw_close()hidinput_close()
powerrequests underlying HW to go into given power modehid_hw_power()
parsethis method is called only once to parse the device datahid_add_device()
requestsend report request to devicehid_hw_request()
waitwait for buffered io to completehid_hw_wait()
raw_requestsend report request to devicehid_hw_raw_request()hidinput_led_worker(), hidinput_led_worker()
output_reportsend output report to devicehid_hw_output_report()
idlesend idle request to devicehid_hw_idle()

USB HID device (drivers/hid/usbhid/hiddev.c) が提供する直接 I/O 機能 struct hiddev_fops 向けに struct hid_device に関数を指すメンバがあります。

fileOpenGrok(linux-4.1.27/include/linux/hid.h:550)
Expand allFold all
550
551
552
553
554
 
 
 
 
 
        int (*hiddev_connect)(struct hid_device *, unsigned int);
        void (*hiddev_disconnect)(struct hid_device *);
        void (*hiddev_hid_event) (struct hid_device *, struct hid_field *field,
                                  struct hid_usage *, __s32);
        void (*hiddev_report_event) (struct hid_device *, struct hid_report *);

次のように実装されています。

hid_device 構造体が持つメソッドについて

hiddev_connect .. hiddev_report_event メンバは HID Class driver (device) の全体的な構造としてみると I2C, bluetooth, Hyper-V, uhid いずれのバスでも関数を実装することが可能な様に見えます。実質的には USB device だけに使われる実装です。

Custom Driver 向け Hook 機能

"Custom Driver" 向けに HID プロトコルを進める随所に hook 関数を仕込めるように struct hid_driver メンバーの一部は次の表のように関数を指すポインタになっています。詳細な解析は後回しにします。

メンバ (メソッド)説明HID Core 内部の呼び出し元(一部は Transport Driver)
probenew device insertedhid_device_probe()
removedevice removed (NULL if not a hot-plug capable driver)hid_device_remove()
report_tableon which reports to call raw_event (NULL means all)hid_match_report(), hid_input_report() この中で raw_event の呼び出し判定をしている。
raw_eventif report in report_table, this hook is called (NULL means nop)hid_input_report()
usage_tableon which events to call event (NULL means all)hid_match_usage() hid_process_event() この中で event の呼び出しを判定している
eventif usage in usage_table, this hook is called (NULL means nop)hid_process_event()
reportthis hook is called after parsing a report (NULL means nop)hid_report_raw_event()
report_fixupcalled before report descriptor parsing (NULL means nop)hid_open_report()
input_mappinginvoked on input registering before mapping an usagehidinput_configure_usage()
input_mappedinvoked on input registering after mapping an usagehidinput_configure_usage()
input_configuredinvoked just before the device is registeredhidinput_connect()
feature_mappinginvoked on feature registeringreport_features()
suspendinvoked on suspend (NULL means nop)usbhid hid_suspend(), i2c_hid_suspend()
resumeinvoked on resume if device was not reset (NULL means nop)hid_resume_common()
reset_resumeinvoked on resume if device was reset (NULL means nop)usbhid hid_reset_resume(), i2c_hid_resume()

HID デバイスを接続した場合の流れ

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 の中では珍しい動作です。

fileOpenGrok(linux-4.1.27/drivers/hid/hid-core.c:2553)
Expand allFold all
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
 
-
|
|
|
|
|
|
-
!
|
|
|
-
|
!
-
|
|
!
|
-
|
|
!
|
|
|
|
|
|
-
|
!
-
|
!
-
|
|
|
!
|
-
!
|
|
|
|
|
|
|
|
|
|
|
!
int hid_add_device(struct hid_device *hdev)
{
        static atomic_t id = ATOMIC_INIT(0);
        int ret;
 
        if (WARN_ON(hdev->status & HID_STAT_ADDED))
                return -EBUSY;
 
        /* we need to kill them here, otherwise they will stay allocated to
         * wait for coming driver */
        if (hid_ignore(hdev))
                return -ENODEV;
 
        /*
         * Check for the mandatory transport channel.
         */
         if (!hdev->ll_driver->raw_request) {
                hid_err(hdev, "transport driver missing .raw_request()\n");
                return -EINVAL;
         }
 
        /*
         * Read the device report descriptor once and use as template
         * for the driver-specific modifications.
         */
        ret = hdev->ll_driver->parse(hdev);
        if (ret)
                return ret;
        if (!hdev->dev_rdesc)
                return -ENODEV;
 
        /*
         * Scan generic devices for group information
         */
        if (hid_ignore_special_drivers) {
                hdev->group = HID_GROUP_GENERIC;
        } else if (!hdev->group &&
                   !hid_match_id(hdev, hid_have_special_driver)) {
                ret = hid_scan_report(hdev);
                if (ret)
                        hid_warn(hdev, "bad device descriptor (%d)\n", ret);
        }
 
        /* XXX hack, any other cleaner solution after the driver core
         * is converted to allow more than 20 bytes as the device name? */
        dev_set_name(&hdev->dev, "%04X:%04X:%04X.%04X", hdev->bus,
                     hdev->vendor, hdev->product, atomic_inc_return(&id));
 
        hid_debug_register(hdev, dev_name(&hdev->dev));
        ret = device_add(&hdev->dev);
        if (!ret)
                hdev->status |= HID_STAT_ADDED;
        else
                hid_debug_unregister(hdev);
 
        return ret;
}

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_driverhid_match_id() に渡して "Generic Driver" と同じ動作で良ければ(すなわち、hid_have_special_driver の要素に無ければ) hid_scan_report() を呼び出して Report Descriptor をパースします。device_add() を呼び出し、drivers/base/dd.cdevice_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) の場合だけです。下のコード断片は hid_device_probe() です。

fileOpenGrok(linux-4.1.27/drivers/hid/hid-core.c:2105)
Expand allFold all
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
 
-
|
|
|
|
|
|
|
|
-
|
|
!
|
|
-
|
-
|
|
!
|
|
-
|
-
|
|
|
!
-
|
|
!
!
|
|
|
|
|
|
!
static int hid_device_probe(struct device *dev)
{
        struct hid_driver *hdrv = container_of(dev->driver,
                        struct hid_driver, driver);
        struct hid_device *hdev = container_of(dev, struct hid_device, dev);
        const struct hid_device_id *id;
        int ret = 0;
 
        if (down_interruptible(&hdev->driver_lock))
                return -EINTR;
        if (down_interruptible(&hdev->driver_input_lock)) {
                ret = -EINTR;
                goto unlock_driver_lock;
        }
        hdev->io_started = false;
 
        if (!hdev->driver) {
                id = hid_match_device(hdev, hdrv);
                if (id == NULL) {
                        ret = -ENODEV;
                        goto unlock;
                }
 
                hdev->driver = hdrv;
                if (hdrv->probe) {
                        ret = hdrv->probe(hdev, id);
                } else { /* default probe */
                        ret = hid_open_report(hdev);
                        if (!ret)
                                ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
                }
                if (ret) {
                        hid_close_report(hdev);
                        hdev->driver = NULL;
                }
        }
unlock:
        if (!hdev->io_started)
                up(&hdev->driver_input_lock);
unlock_driver_lock:
        up(&hdev->driver_lock);
        return ret;
}

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 関数を呼び出します。


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-09-27 (水) 11:03:48 (2405d)