linux HID驅動分析 .

來源:互聯網
上載者:User

最近研究藍芽的鍵盤滑鼠,所以粗略看了一下Linux的HID架構。

HID 匯流排

HID的匯流排在hid-core.c的hid-init中初始化:

bus_register(&hid_bus_type);

hid_bus_type的定義:

static struct bus_type hid_bus_type = {

       .name            = "hid",

       .match           = hid_bus_match,

       .probe           = hid_device_probe,

       .remove  = hid_device_remove,

       .uevent          = hid_uevent,

};

一般來說,HID驅動很少定義自己的probe函數,所以HID裝置的匹配基本都是由匯流排probe和match函數完成。

HID的匹配

hid_bus_match用於檢查裝置和驅動的VID、PID是否匹配,代碼如下:

static int hid_bus_match(struct device *dev, struct device_driver *drv)

       struct hid_driver *hdrv = container_of(drv, struct hid_driver, driver);

       struct hid_device *hdev = container_of(dev, struct hid_device, dev);

       // 匹配hdev和hdrv的vendorID和productID

if (!hid_match_device(hdev, hdrv))

              return 0;

       // 如果是generic-開頭的驅動,那麼只要不在黑名單中即可匹配

       if (!strncmp(hdrv->name, "generic-", 8))

              return !hid_match_id(hdev, hid_blacklist);

       return 1;

匹配了PID、VID之後,就進入到hid_device_probe函數:

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 (!hdev->driver) {

              // 再匹配一次,這裡似乎與前面的hid_bus_match有些重複

id = hid_match_device(hdev, hdrv);

              if (id == NULL)

                     return -ENODEV;

              hdev->driver = hdrv;

              if (hdrv->probe) { // 若驅動定義了自己的probe函數則調用該probe,但一般HID驅動不會定義

                     ret = hdrv->probe(hdev, id);

              } else { // 預設的probe過程

                     ret = hid_parse(hdev);

                     if (!ret)

                            ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);

              }

              if (ret)

                     hdev->driver = NULL;

       }

       return ret;

hid_parse函數的作用是解析HID描述符,具體實現由hid_device->ll_driver->parse函數完成。關於HID描述符的文檔可在www.usb.org下載。

static inline int __must_check hid_parse(struct hid_device *hdev)

       ret = hdev->ll_driver->parse(hdev);

由於HID描述符的解析是通用操作,所以HID架構中實現了一個解析函數hid_parse_report。一般來說,hdev->ll_driver->parse函數中只要調用hid_parse_report即可。

hid_parse_report比較複雜,其功能是解析HID描述符,然後把解析出的結果放在hid_device->report_enum[type]-> report_list中。每個解析出的HID結構由一個hid_report描述。report_enum中的type可以是HID_INPUT_REPORT、HID_OUTPUT_REPORT或者HID_FEATURE_REPORT。

parse之後,probe函數又會調用hid_hw_start啟動HID裝置:

       hid_hw_start(hdev, HID_CONNECT_DEFAULT);

注意這裡的HID_CONNECT_DEFAULT被定義為:

#define HID_CONNECT_DEFAULT       (HID_CONNECT_HIDINPUT|HID_CONNECT_HIDRAW| \

              HID_CONNECT_HIDDEV|HID_CONNECT_FF)

在hid_hw_start中,首先會調用hdev->ll_driver->start啟動裝置,然後是hid_connect將裝置與HID架構關聯起來。

hdev->ll_driver->start函數由hid的具體裝置提供,由該裝置所屬的匯流排提供,用於底層的初始化,這裡暫不討論。

hid_connect會將hid_dev與具體驅動關聯起來。

int hid_connect(struct hid_device *hdev, unsigned int connect_mask)

       if (hdev->quirks & HID_QUIRK_HIDDEV_FORCE)  // 一般不會到這裡

              connect_mask |= (HID_CONNECT_HIDDEV_FORCE | HID_CONNECT_HIDDEV);

       if (hdev->bus != BUS_USB) // 如果不是USB匯流排,那麼去掉HID_CONNECT_HIDDEV標記

              connect_mask &= ~HID_CONNECT_HIDDEV;

       if (hid_hiddev(hdev))  // 匹配某些特定vendorID和productID

              connect_mask |= HID_CONNECT_HIDDEV_FORCE;

 

       if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev,

                            connect_mask & HID_CONNECT_HIDINPUT_FORCE))

              hdev->claimed |= HID_CLAIMED_INPUT;

       if ((connect_mask & HID_CONNECT_HIDDEV) && hdev->hiddev_connect &&

                     !hdev->hiddev_connect(hdev,

                            connect_mask & HID_CONNECT_HIDDEV_FORCE))

              hdev->claimed |= HID_CLAIMED_HIDDEV;

       if ((connect_mask & HID_CONNECT_HIDRAW) && !hidraw_connect(hdev))

              hdev->claimed |= HID_CLAIMED_HIDRAW;

 

由此可見,hid_connect共支援3種裝置,首先是input裝置,調用hidinput_connect登記;其次是hid_dev裝置,調用hdev->hiddev_connect登記;最後是raw裝置,調用hidraw_connect登記。

HID input

HID中最常用的是input裝置,使用hidinput_connect登記到系統。hidinput_connect的主要作用是對hiddev中的每一個report,都建立一個input_dev裝置,並登記到input架構中。

int hidinput_connect(struct hid_device *hid, unsigned int force)

       // 對每一個report,建立一個input裝置

for (k = HID_INPUT_REPORT; k <= max_report_type; k++)

              list_for_each_entry(report, &hid->report_enum[k].report_list, list) {

                     if (!hidinput) {

                            hidinput = kzalloc(sizeof(*hidinput), GFP_KERNEL);

                            input_dev = input_allocate_device();

                            。。。

                            input_set_drvdata(input_dev, hid);

                            input_dev->event = hid->ll_driver->hidinput_input_event;

                            input_dev->open = hidinput_open;

                            input_dev->close = hidinput_close;

                            input_dev->setkeycode = hidinput_setkeycode;

                            input_dev->getkeycode = hidinput_getkeycode;

                            input_dev->name = hid->name;

                            input_dev->phys = hid->phys;

                            input_dev->uniq = hid->uniq;

                            input_dev->id.bustype = hid->bus;

                            input_dev->id.vendor  = hid->vendor;

                            input_dev->id.product = hid->product;

                            input_dev->id.version = hid->version;

                            input_dev->dev.parent = hid->dev.parent;

                            hidinput->input = input_dev;

                            list_add_tail(&hidinput->list, &hid->inputs);

                     }

                     for (i = 0; i < report->maxfield; i++)

                            for (j = 0; j < report->field[i]->maxusage; j++)

                                   hidinput_configure_usage(hidinput, report->field[i],

                                                         report->field[i]->usage + j);

              }

HID dev

HID dev裝置目前僅在USB匯流排中用到,其用於登記的hiddev_connect函數指標目前僅有一個執行個體hiddev_connect,在usbhid_probe函數中被賦值。

hid->hiddev_connect = hiddev_connect;

HID raw dev

hidraw.c中定義了一個class hidraw,並建立裝置裝置驅動

alloc_chrdev_region(&dev_id, HIDRAW_FIRST_MINOR, HIDRAW_MAX_DEVICES, "hidraw");

cdev_init(&hidraw_cdev, &hidraw_ops);

hidraw_ops中定義了一個基本的字元裝置驅動

static const struct file_operations hidraw_ops = {

       .owner =        THIS_MODULE,

       .read =         hidraw_read,

       .write =        hidraw_write,

       .poll =         hidraw_poll,

       .open =         hidraw_open,

       .release =      hidraw_release,

       .unlocked_ioctl = hidraw_ioctl,

};

由於是raw裝置,所以這個驅動中不會解析任何資料,只是簡單的將應用程式層資料傳給下層裝置,以及將裝置產生的資料傳給應用程式層。具體實現可查看代碼。

資料的傳輸

HID中資料的傳輸有兩部分,一部分是從應用程式層到裝置,另一部分是從裝置到應用程式層。

對於HID input裝置,從裝置到應用程式層走的是標準的input架構,底層裝置通過hid_input_report函數將收到的資料送入HID架構,由HID架構解析並最終調用input_report_key之類的函數將資料上傳。

從應用程式層到裝置也由input架構完成:

hidinput_connect

input_dev->event = hid->ll_driver->hidinput_input_event;

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.