LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
时间:2009-03-21 来源:superfight
目标当然是hid,当然在了解驱动初始化过程之前,让我们先看看一下hid协议
我对hid协议的理解是建立在鼠标上的,所以如果有理解不当的地方,请务必请大家指出
我们先来看鼠标的结构,鼠标有左键,右键,中键,滚轮,X轴和Y轴这6个量
其中左键,右键和中键的点击可以用0和1两个数值来表达,呢么就占1bit
然后是X轴,Y轴和滚轮,我们假设他们的相对数值变化范围为-127到127,呢么就是255个数,用8个bit也就是1个字节来表示
如下图
500)this.width=500;" border=0> 左键,右键和中键属于按键而X轴,Y轴,滚轮属于量
在hid中不同两种类别的数据需要用字节来间隔,也就是说左键,右键和中键需要占用1个字节,呢么就变成下图
500)this.width=500;" border=0> 好,现在用4个字节来描述鼠标的所有动作
呢么如何把这4个字节写成hid所要使用的报告描述符呢?
在以往的方法中是从鼠标开始分析到属性,我现在给大家讲个通俗易懂的,从属性开始分析,最后才到鼠标
首先是左键,右键和中键,这3个按键,3怎么表示呢?是不是从1开始到3结束
呢么头两个hid域就是有n个事件,从1开始到3结束,如下图
500)this.width=500;" border=0> 然后这个事件的最小值为0,最大值为1,呢么如下图 500)this.width=500;" border=0> 然后每个事件需要的位数为1,一共有3个这样的事件,呢么如下图 500)this.width=500;" border=0> 这些事件具体是啥呢?~ 是指针,并添加这些事件到hid协议中,如下图 500)this.width=500;" border=0> 这样3个按键的描述就完成了
然后到用于占位的呢5个bit
由于这些bit不代表任何时间,所以只需要填写大小和数量即可,如下图
500)this.width=500;" border=0> 最后到X轴,Y轴和滚轮了,和左键,右键,中键3键的配置过程基本一致,就是X轴,Y轴和滚轮不能一起描述,需要分开,所以不能像按键一样用从1开始,到3结束的方法一次描述完,如下图 500)this.width=500;" border=0> 现在把这3个描述域组合起来,如下图 500)this.width=500;" border=0> 然后这个大的描述域属于物理项目的集合,添加相应的描述符 这个物理收集属于指针集合,继续添加指针集合类别 然后这个指针集合属于应用项目的集合,继续添加 这个应用又属于鼠标,添加....... 鼠标又属于桌面类,添加最后一个描述符 添加完成后的结构如下 500)this.width=500;" border=0> 现在一个鼠标的hid报告描述符就做好了
看看computer00写得鼠标报告描述符
//USB报告描述符的定义
code uint8 ReportDescriptor[]=
{
//每行开始的第一字节为该条目的前缀,前缀的格式为:
//D7~D4:bTag。D3~D2:bType;D1~D0:bSize。以下分别对每个条目注释。
//这是一个全局(bType为1)条目,选择用途页为普通桌面Generic Desktop Page(0x01)
//后面跟一字节数据(bSize为1),后面的字节数就不注释了,
//自己根据bSize来判断。
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//这是一个局部(bType为2)条目,说明接下来的应用集合用途用于鼠标
0x09, 0x02, // USAGE (Mouse)
//这是一个主条目(bType为0)条目,开集合,后面跟的数据0x01表示
//该集合是一个应用集合。它的性质在前面由用途页和用途定义为
//普通桌面用的鼠标。
0xa1, 0x01, // COLLECTION (Application)
//这是一个局部条目。说明用途为指针集合
0x09, 0x01, // USAGE (Pointer)
//这是一个主条目,开集合,后面跟的数据0x00表示该集合是一个
//物理集合,用途由前面的局部条目定义为指针集合。
0xa1, 0x00, // COLLECTION (Physical)
//这是一个全局条目,选择用途页为按键(Button Page(0x09))
0x05, 0x09, // USAGE_PAGE (Button)
//这是一个局部条目,说明用途的最小值为1。实际上是鼠标左键。
0x19, 0x01, // USAGE_MINIMUM (Button 1)
//这是一个局部条目,说明用途的最大值为3。实际上是鼠标中键。
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
//这是一个全局条目,说明返回的数据的逻辑值(就是我们返回的数据域的值啦)
//最小为0。因为我们这里用Bit来表示一个数据域,因此最小为0,最大为1。
0x15, 0x00, // LOGICAL_MINIMUM (0)
//这是一个全局条目,说明逻辑值最大为1。
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//这是一个全局条目,说明数据域的数量为三个。
0x95, 0x03, // REPORT_COUNT (3)
//这是一个全局条目,说明每个数据域的长度为1个bit。
0x75, 0x01, // REPORT_SIZE (1)
//这是一个主条目,说明有3个长度为1bit的数据域(数量和长度
//由前面的两个全局条目所定义)用来做为输入,
//属性为:Data,Var,Abs。Data表示这些数据可以变动,Var表示
//这些数据域是独立的,每个域表示一个意思。Abs表示绝对值。
//这样定义的结果就是,第一个数据域bit0表示按键1(左键)是否按下,
//第二个数据域bit1表示按键2(右键)是否按下,第三个数据域bit2表示
//按键3(中键)是否按下。
0x81, 0x02, // INPUT (Data,Var,Abs)
//这是一个全局条目,说明数据域数量为1个
0x95, 0x01, // REPORT_COUNT (1)
//这是一个全局条目,说明每个数据域的长度为5bit。
0x75, 0x05, // REPORT_SIZE (5)
//这是一个主条目,输入用,由前面两个全局条目可知,长度为5bit,
//数量为1个。它的属性为常量(即返回的数据一直是0)。
//这个只是为了凑齐一个字节(前面用了3个bit)而填充的一些数据
//而已,所以它是没有实际用途的。
0x81, 0x03, // INPUT (Cnst,Var,Abs)
//这是一个全局条目,选择用途页为普通桌面Generic Desktop Page(0x01)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//这是一个局部条目,说明用途为X轴
0x09, 0x30, // USAGE (X)
//这是一个局部条目,说明用途为Y轴
0x09, 0x31, // USAGE (Y)
//这是一个局部条目,说明用途为滚轮
0x09, 0x38, // USAGE (Wheel)
//下面两个为全局条目,说明返回的逻辑最小和最大值。
//因为鼠标指针移动时,通常是用相对值来表示的,
//相对值的意思就是,当指针移动时,只发送移动量。
//往右移动时,X值为正;往下移动时,Y值为正。
//对于滚轮,当滚轮往上滚时,值为正。
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
//这是一个全局条目,说明数据域的长度为8bit。
0x75, 0x08, // REPORT_SIZE (8)
//这是一个全局条目,说明数据域的个数为3个。
0x95, 0x03, // REPORT_COUNT (3)
//这是一个主条目。它说明这三个8bit的数据域是输入用的,
//属性为:Data,Var,Rel。Data说明数据是可以变的,Var说明
//这些数据域是独立的,即第一个8bit表示X轴,第二个8bit表示
//Y轴,第三个8bit表示滚轮。Rel表示这些值是相对值。
0x81, 0x06, // INPUT (Data,Var,Rel)
//下面这两个主条目用来关闭前面的集合用。
//我们开了两个集合,所以要关两次。bSize为0,所以后面没数据。
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
}; 是不是有豁然开朗的感觉呢?~
= 3= 下面就来看看在LINUX中如何为usb鼠标解析这个报告描述符
如何加载usbhid模块就不说了~
从usbhid模块的初始化开始分析吧
入口是hid_init
hid_init在/drivers/hid/usbhid/hid-core.c中
static int __init hid_init(void) |
hiddev_driver的probe函数为空,所以这个驱动是没有作用的,可以跳开它了
接下来是retval = usb_register(&hid_driver);
这个就是我们的目标了,usb驱动的注册在uhci中已经分析了,这里直接进入
hid_driver匹配设备后的初始化函数hid_probe
hid_probe在/drivers/hid/usbhid/hid-core.c中
static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id) |
先说一下, hid_ff_init(hid); 我分析不出这段代码的用途,哪位兄弟能给点思路嘛 = 3=
首先是usb_hid_configure,他负责hid报告描述符数据结构的搭建
usb_hid_configure在/drivers/hid/usbhid/hid-core.c中
static struct hid_device *usb_hid_configure(struct usb_interface *intf) |
hid_parse_report在/drivers/hid/hid-core.c中
struct hid_device *hid_parse_report(__u8 *start, unsigned size) |
在进入while循环前,我们先看看这里搭建好的数据结构,如下
500)this.width=500;" border=0> 之后的分析会经常和这些数据结构打交道现在来到fetch_item中,这个函数负责解析报告描述符中的字段,为hid协议的解析提供基础,我将会详细的介绍解析的过程
fetch_item在/drivers/hid/hid-core.c中
static u8 *fetch_item(__u8 *start, __u8 *end, struct hid_item *item) |
这个数组根据报告描述符中类型的字段值选择相应的解析函数进行解析
现在先说一下报告描述符中字段的格式
在hid协议中规定报告描述符的基本字段如下
500)this.width=500;" border=0> size描述的是之后跟随多少字节的数据
有0 1 2 3,分别代表0字节,1字节,2字节和4字节4种长度的数据
type表示的是域的类型
有0 1 2 3分别代表主区域,全局区域和局部区域3种
tag描述的是域的详细类型
详细的tag看hid手册吧......
不过要注意的是tag全为1是为不满足4字节长度的用户设计的,
当tag全为1的时候之后会再跟随2个字节的数据用于描述这样大型的数据
这里我们不会接粗话到tag全为1的情况,就不讨论了
现在来看报告描述符中的第一个字节
0x05,换成bit就是0000 0101,也就是说明了后跟1字节的数据,域类型为全局,
详细域类型为0000,
看看后面一字节的数据是啥, 0x01,呢么数据就是0x01了
之后每个字节的详细分析就留给大家自己进行了 首先看这第一组0x05, 0x01
这是一个全局项目,进入到hid_parser_global中
hid_parser_global在/drivers/hid/hid-core.c中
static int hid_parser_global(struct hid_parser *parser, struct hid_item *item) |
我们的目的地是这里
parser->global.usage_page = item_udata(item);
然后到第二组
0x09, 0x02
这是一个局域项目,进入到局部域解析函数hid_parser_local
hid_parser_local在/drivers/hid/hid-core.c中
static int hid_parser_local(struct hid_parser *parser, struct hid_item *item) |
我们的目标为
case HID_LOCAL_ITEM_TAG_USAGE:
if (parser->local.delimiter_branch > 1) {
dbg_hid("alternative usage ignored\n");
return 0;
}
//检测数据的大小是否小于或者等于2字节
if (item->size <= 2)
//加上作用标记
data = (parser->global.usage_page << 16) + data;
return hid_add_usage(parser, data);
接着到hid_add_usage中
hid_add_usage负责增加相应的项目
static int hid_add_usage(struct hid_parser *parser, unsigned usage) |
0xa1, 0x01
这是一个主区域,用于应用集合收集的开始
进入到主区域解析函数hid_parser_main
hid_parser_main在/drivers/hid/hid-core.c中
static int hid_parser_main(struct hid_parser *parser, struct hid_item *item) |
这会清空局部解析中的数据
我们的目标为
case HID_MAIN_ITEM_TAG_BEGIN_COLLECTION:
ret = open_collection(parser, data & 0xff);
break;
进入到open_collection
open_collection在/drivers/hid/hid-core.c中
static int open_collection(struct hid_parser *parser, unsigned type) |