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;