Linux设备驱动之HID驱动
来源:互联网 发布:淘宝网精品女装 编辑:程序博客网 时间:2024/06/05 10:48
------------------------------------------
一:前言
继前面分析过UHCI和HUB驱动之后,接下来以HID设备驱动为例来做一个具体的USB设备驱动分析的例子.HID是Human Interface Devices的缩写.翻译成中文即为人机交互设备.这里的人机交互设备是一个宏观上面的概念,任何设备,只要符合HID spec,都可以称之为HID设备.常见的HID设备有鼠标键盘,游戏操纵杆等等.在接下来的代码分析中,可以参考HID的spec.这份spec可以在www.usb.org上找到.分析的代码主要集中在linux-2.6.25/drivers/hid目录下.
二:HID驱动入口分析
USB HID设备驱动入口位于linux-2.6.25/drivers/hid/usbhid/hid-core.c中.该module的入口为hid_init().代码如下:
static int __init hid_init(void)
{
int retval;
retval = usbhid_quirks_init(quirks_param);
if (retval)
goto usbhid_quirks_init_fail;
retval = hiddev_init();
if (retval)
goto hiddev_init_fail;
retval = usb_register(&hid_driver);
if (retval)
goto usb_register_fail;
info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
usb_register_fail:
hiddev_exit();
hiddev_init_fail:
usbhid_quirks_exit();
usbhid_quirks_init_fail:
return retval;
}
首先来看usbhid_quirks_init()函数.quirks我们在分析UHCI和HUB的时候也接触过,表示需要做某种修正的设备.该函数调用的参数是quirks_param.定义如下:
static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };
module_param_array_named(quirks, quirks_param, charp, NULL, 0444);
从此可以看出, quirks_param是MAX_USBHID_BOOT_QUIRKS元素的字符串数组.并且在加载module的时候,可以动态的指定这些值.
分析到这里.有人可以反应过来了,usbhid_quirks_init()是一种动态进行HID设备修正的方式.具体要修正哪些设备,要修正设备的那些方面,都可以由加载模块是所带参数来决定.
usbhid_quirks_init()的代码如下:
int usbhid_quirks_init(char **quirks_param)
{
u16 idVendor, idProduct;
u32 quirks;
int n = 0, m;
for (; quirks_param[n] && n < MAX_USBHID_BOOT_QUIRKS; n++) {
m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x",
&idVendor, &idProduct, &quirks);
if (m != 3 ||
usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) {
printk(KERN_WARNING
"Could not parse HID quirk module param %s/n",
quirks_param[n]);
}
}
return 0;
}
由此可以看出, quirks_param数组中的每一项可以分为三个部份,分别是要修正设备的VendorID,ProductID和要修正的功能.比如0x1000 0x0001 0x0004就表示:要忽略掉VendorID为0x1000,ProductID为0x0004的设备.(在代码中,有 #define HID_QUIRK_IGNORE 0x00000004的定义)
跟进usbhid_modify_dquirk()函数,代码如下:
int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct,
const u32 quirks)
{
struct quirks_list_struct *q_new, *q;
int list_edited = 0;
if (!idVendor) {
dbg_hid("Cannot add a quirk with idVendor = 0/n");
return -EINVAL;
}
q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL);
if (!q_new) {
dbg_hid("Could not allocate quirks_list_struct/n");
return -ENOMEM;
}
q_new->hid_bl_item.idVendor = idVendor;
q_new->hid_bl_item.idProduct = idProduct;
q_new->hid_bl_item.quirks = quirks;
down_write(&dquirks_rwsem);
list_for_each_entry(q, &dquirks_list, node) {
if (q->hid_bl_item.idVendor == idVendor &&
q->hid_bl_item.idProduct == idProduct) {
list_replace(&q->node, &q_new->node);
kfree(q);
list_edited = 1;
break;
}
}
if (!list_edited)
list_add_tail(&q_new->node, &dquirks_list);
up_write(&dquirks_rwsem);
return 0;
}
这个函数比较简单,就把quirks_param数组项中的三个部份存入一个封装结构.然后将其结构挂载到dquirks_list表.如果dquirks_list有重复的VendorId和ProductID就更新其quirks信息.
经过usbhid_quirks_init()之后,所有要修正的设备的相关操作都会存放在dquirks_list中.
返回到hid_init(),继续往下面分析.
hiddev_init()是一个无关的操作,不会影响到后面的操作.忽略
后面就是我们今天要分析的重点了,如下:
retval = usb_register(&hid_driver);
通过前面对HUB的驱动分析,相信对usb_redister()应该很熟悉了.hid_driver定义如下:
static struct usb_driver hid_driver = {
.name = "usbhid",
.probe = hid_probe,
.disconnect = hid_disconnect,
.suspend = hid_suspend,
.resume = hid_resume,
.reset_resume = hid_post_reset,
.pre_reset = hid_pre_reset,
.post_reset = hid_post_reset,
.id_table = hid_usb_ids,
.supports_autosuspend = 1,
};
其中,id_table的结构为hid_usb_ids.定义如下:
static struct usb_device_id hid_usb_ids [] = {
{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
.bInterfaceClass = USB_INTERFACE_CLASS_HID },
{ } /* Terminating entry */
};
也就是说,该驱动会匹配interface的ClassID,所有ClassID为USB_INTERFACE_CLASS_HID的设备都会被这个驱动所匹配.所以,所有USB HID设备都会由这个module来驱动.
三:HID驱动的probe过程
从上面的分析可看到,probe接口为hid_probe().定义如下:
static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct hid_device *hid;
char path[64];
int i;
char *c;
dbg_hid("HID probe called for ifnum %d/n",
intf->altsetting->desc.bInterfaceNumber);
//config the hid device
if (!(hid = usb_hid_configure(intf)))
return -ENODEV;
usbhid_init_reports(hid);
hid_dump_device(hid);
if (hid->quirks & HID_QUIRK_RESET_LEDS)
usbhid_set_leds(hid);
if (!hidinput_connect(hid))
hid->claimed |= HID_CLAIMED_INPUT;
if (!hiddev_connect(hid))
hid->claimed |= HID_CLAIMED_HIDDEV;
if (!hidraw_connect(hid))
hid->claimed |= HID_CLAIMED_HIDRAW;
usb_set_intfdata(intf, hid);
if (!hid->claimed) {
printk ("HID device claimed by neither input, hiddev nor hidraw/n");
hid_disconnect(intf);
return -ENODEV;
}
if ((hid->claimed & HID_CLAIMED_INPUT))
hid_ff_init(hid);
if (hid->quirks & HID_QUIRK_SONY_PS3_CONTROLLER)
hid_fixup_sony_ps3_controller(interface_to_usbdev(intf),
intf->cur_altsetting->desc.bInterfaceNumber);
printk(KERN_INFO);
if (hid->claimed & HID_CLAIMED_INPUT)
printk("input");
if ((hid->claimed & HID_CLAIMED_INPUT) && ((hid->claimed & HID_CLAIMED_HIDDEV) ||
hid->claimed & HID_CLAIMED_HIDRAW))
printk(",");
if (hid->claimed & HID_CLAIMED_HIDDEV)
printk("hiddev%d", hid->minor);
if ((hid->claimed & HID_CLAIMED_INPUT) && (hid->claimed & HID_CLAIMED_HIDDEV) &&
(hid->claimed & HID_CLAIMED_HIDRAW))
printk(",");
if (hid->claimed & HID_CLAIMED_HIDRAW)
printk("hidraw%d", ((struct hidraw*)hid->hidraw)->minor);
c = "Device";
for (i = 0; i < hid->maxcollection; i++) {
if (hid->collection[i].type == HID_COLLECTION_APPLICATION &&
(hid->collection[i].usage & HID_USAGE_PAGE) == HID_UP_GENDESK &&
(hid->collection[i].usage & 0xffff) < ARRAY_SIZE(hid_types)) {
c = hid_types[hid->collection[i].usage & 0xffff];
break;
}
}
usb_make_path(interface_to_usbdev(intf), path, 63);
printk(": USB HID v%x.%02x %s [%s] on %s/n",
hid->version >> 8, hid->version & 0xff, c, hid->name, path);
return 0;
}
这个函数看起来是不是让人心慌慌?其实这个函数的最后一部份就是打印出一个Debug信息,我们根本就不需要去看. hiddev_connect()和hidraw_connect()是一个选择编译的操作,也不可以不要去理会.然后,剩下的就没多少了.
3.1:usb_hid_configure()函数分析
先来看usb_hid_configure().顾名思义,该接口用来配置hid设备.怎么配置呢?还是深入到代码来分析,该函数有一点长,分段分析如下:
static struct hid_device *usb_hid_configure(struct usb_interface *intf)
{
struct usb_host_interface *interface = intf->cur_altsetting;
struct usb_device *dev = interface_to_usbdev (intf);
struct hid_descriptor *hdesc;
struct hid_device *hid;
u32 quirks = 0;
unsigned rsize = 0;
char *rdesc;
int n, len, insize = 0;
struct usbhid_device *usbhid;
quirks = usbhid_lookup_quirk(le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
/* Many keyboards and mice don't like to be polled for reports,
* so we will always set the HID_QUIRK_NOGET flag for them. */
//如果是boot设备,跳出.不由此驱动处理
if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) {
if (interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD ||
interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE)
quirks |= HID_QUIRK_NOGET;
}
//如果是要忽略的
if (quirks & HID_QUIRK_IGNORE)
return NULL;
if ((quirks & HID_QUIRK_IGNORE_MOUSE) &&
(interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE))
return NULL;
首先找到该接口需要修正的操作,也就是上面代码中的quirks值,如果没有修正操作,则quirks为0.另外,根据usb hid spec中的定义,subclass如果为1,则说明该设备是一个boot阶段使用的hid设备,然后Protocol Code为1和2时分别代表Keyboard和Mouse. 如果是boot阶段的Keyboard和Mouse是不会由这个驱动进行处理的.另外,quirks为HID_QUIRK_IGNORE表示忽略这个设备,为HID_QUIRK_IGNORE_MOUSE,表示,如果该设备是一个鼠标设备,则忽略.
//get hid descriptors
if (usb_get_extra_descriptor(interface, HID_DT_HID, &hdesc) &&
(!interface->desc.bNumEndpoints ||
usb_get_extra_descriptor(&interface->endpoint[0], HID_DT_HID, &hdesc))) {
dbg_hid("class descriptor not present/n");
return NULL;
}
//bNumDescriptors:支持的附属描述符数目
for (n = 0; n < hdesc->bNumDescriptors; n++)
if (hdesc->desc[n].bDescriptorType == HID_DT_REPORT)
rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength);
//如果Report_Descriptors长度不合法
if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
dbg_hid("weird size of report descriptor (%u)/n", rsize);
return NULL;
}
if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) {
dbg_hid("couldn't allocate rdesc memory/n");
return NULL;
}
//Set idle_time = 0
hid_set_idle(dev, interface->desc.bInterfaceNumber, 0, 0);
//Get Report_Descriptors
if ((n = hid_get_class_descriptor(dev, interface->desc.bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) {
dbg_hid("reading report descriptor failed/n");
kfree(rdesc);
return NULL;
}
//是否属于fixup?
usbhid_fixup_report_descriptor(le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct), rdesc,
rsize, rdesc_quirks_param);
dbg_hid("report descriptor (size %u, read %d) = ", rsize, n);
for (n = 0; n < rsize; n++)
dbg_hid_line(" %02x", (unsigned char) rdesc[n]);
dbg_hid_line("/n");
对于HID设备来说,在interface description之后会附加一个hid description, hid description中的最后部份包含有Report description或者Physical Descriptors的长度.
在上面的代码中,首先取得附加在interface description之后的hid description,然后,再从hid description中取得report description的长度.最后,取得report description的详细信息.
在这里,还会将idle时间设备为0,表示无限时,即,从上一次报表传输后,只有在报表发生改变时,才会传送此报表内容,否则,传送NAK.
这段代码的最后一部份是相关的fixup操作,不做详细分析.
//pasrse the report_descriptor
if (!(hid = hid_parse_report(rdesc, n))) {
dbg_hid("parsing report descriptor failed/n");
kfree(rdesc);
return NULL;
}
kfree(rdesc);
hid->quirks = quirks;
if (!(usbhid = kzalloc(sizeof(struct usbhid_device), GFP_KERNEL)))
goto fail_no_usbhid;
hid->driver_data = usbhid;
usbhid->hid = hid;
解析获得的report description,解析之后的信息,存放在hid_device->collection和hid_device->report_enum[ ]中,这个解析过程之后会做详细分析.然后,初始化一个usbhid_device结构,使usbhid_device->hid指向刚解析report description获得的hid_device.同样,hid_device->driver_data关联到usbhid_device.
usbhid->bufsize = HID_MIN_BUFFER_SIZE;
//计算各传输方向的最大buffer
hid_find_max_report(hid, HID_INPUT_REPORT, &usbhid->bufsize);
hid_find_max_report(hid, HID_OUTPUT_REPORT, &usbhid->bufsize);
hid_find_max_report(hid, HID_FEATURE_REPORT, &usbhid->bufsize);
if (usbhid->bufsize > HID_MAX_BUFFER_SIZE)
usbhid->bufsize = HID_MAX_BUFFER_SIZE;
//in方向的传输最大值
hid_find_max_report(hid, HID_INPUT_REPORT, &insize);
if (insize > HID_MAX_BUFFER_SIZE)
insize = HID_MAX_BUFFER_SIZE;
if (hid_alloc_buffers(dev, hid)) {
hid_free_buffers(dev, hid);
goto fail;
}
计算传输数据的最大缓存区,并以这个大小为了hid设备的urb传输分配空间.另外,这里有一个最小值限制即代码中所看到的HID_MIN_BUFFER_SIZE,为64, 即一个高速设备的一个端点一次传输的数据量.在这里定义最小值为64是为了照顾低速/全速/高速三种类型的端点传输数据量.
然后,调用hid_alloc_buffers()为hid的urb传输初始化传输缓冲区.
另外,需要注意的是,insize为INPUT方向的最大数据传输量.
// 初始化usbhid->urbin和usbhid->usbout
for (n = 0; n < interface->desc.bNumEndpoints; n++) {
struct usb_endpoint_descriptor *endpoint;
int pipe;
int interval;
endpoint = &interface->endpoint[n].desc;
//不是中断传输 退出
if ((endpoint->bmAttributes & 3) != 3) /* Not an interrupt endpoint */
continue;
interval = endpoint->bInterval;
/* Change the polling interval of mice. */
//修正鼠标的双击时间
if (hid->collection->usage == HID_GD_MOUSE && hid_mousepoll_interval > 0)
interval = hid_mousepoll_interval;
if (usb_endpoint_dir_in(endpoint)) {
if (usbhid->urbin)
- Linux设备驱动之HID驱动
- Linux设备驱动之HID驱动
- Linux设备驱动之HID驱动
- Linux设备驱动之HID驱动
- Linux设备驱动之HID驱动
- Linux设备驱动之HID驱动
- Linux设备驱动之HID驱动
- Linux设备驱动之HID驱动
- Linux设备驱动之HID驱动---非常全面而且深刻
- linux HID驱动分析
- linux HID驱动分析 .
- linux HID驱动分析
- linux HID驱动分析
- android添加hid设备驱动
- Linux设备驱动之《字符设备驱动》
- linux设备驱动之总线、设备、驱动
- Linux设备驱动之块设备驱动
- linux设备驱动之总线,设备,驱动
- 济南昱胜咨询sb老总的剥削公式(抄袭)
- [转]TeX Live 2008 安装 & 配置中文支持
- Android手机操作系统(转)
- 基于Web的系统测试
- 6个Symfony中文学习资源
- Linux设备驱动之HID驱动
- 详解spring事务属性
- How to Enable Creation of Employee Type Suppliers
- eclipse debug (调试)
- java技术体系
- CC Note: Measure Twice, Cut Once: Upstream Prerequisites
- 开发人员应该选择什么Android手机?
- 手机被偷了。。。
- js windows对象