700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > linux HID驱动分析

linux HID驱动分析

时间:2023-01-09 10:01:39

相关推荐

linux HID驱动分析

最近研究蓝牙的键盘鼠标,所以粗略看了一下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描述符的文档可在下载。

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;

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。