科创园

科创园地,分享技术知识,为科技助力发展,贡献一己之力。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

usb驱动开发9之设备描述符

Posted on 2014-03-30 22:39  科创园  阅读(4293)  评论(0编辑  收藏  举报

前面分析了usb的四大描述符之端点描述符,接口描述符(每一个接口对应一个功能,与之配备相应驱动),配置描述符,最后分析设备如何包括这些描述符。首先记住,在usb的世界里,设备大于配置,配置大于接口,接口包含多个设置,接口也可以理解为功能,所有端点的集合构成一个功能。废话少说,看一下usb设备结构体定义吧!

 

现在明白为什么要最后分析usb_device结构体了吗?好东西我们总是喜欢留在最后慢慢品尝。

devnum,设备的地址。这和咱们写程序时说的地址是不一样的,devnum只是usb设备在一条usb总线上的编号。你的usb设备插到hub上时,hub响应就会调用一个名叫choose_address的函数(在hub.c文件里),为你的设备选择一个地址。有人说我没有用hub,我的usb设备直接插到主机的usb接口上了。但是主机里还会有个叫root hub的东东,不管是一般的hub还是root hub,你的usb设备总要通过一个hub才能在usb的世界里生活。现在来认识一下usb子系统里面关于地址的游戏规则。在usb世界里,一条总线就是一棵大树,一个设备就是一片叶子。为了记录这棵树上的每一个叶子节点,每条总线设有一个地址映射表,即struct usb_bus结构体里有一个成员struct usb_devmap devmap。

/* USB device number allocation bitmap */

struct usb_devmap {

unsigned long devicemap[128 / (8*sizeof(unsigned long))];

};

什么是usb_bus?不是已经有了一个struct bus_type类型的usb_bus_type了么?没错,在usb子系统的初始化函数usb_init里已经注册了usb_bus_type(不信可以回头看看),但那是让系统知道有这么一个类型的总线。而一个总线类型和一条总线是两码子事儿。从硬件上来讲,一个host controller就会连出一条usb总线,而从软件上来讲,不管你有多少个host controller,或者说有多少条总线,它们通通属于usb_bus_type这么一个类型,只是每一条总线对应一个struct usb_bus结构体变量,这个变量在host controller的驱动程序中去申请。

上面的devmap地址映射表就表示了一条总线上设备连接的情况,假设unsigned long=4bytes,那么unsigned long devicemap[128/(8*sizeof(unsigned long)]]就等价于unsigned long devicemap[128/(8*4)],进而等价于unsigned long devicemap[4],因此这个数组最终表示的就是128bits。而这也对应于一条总线可以连接128个usb设备。之所以这里使用sizeof(unsigned long),就是为了跨平台应用,不管unsigned long到底是几,总之这个devicemap数组最终可以表示128位,也就是说每条总线上最多可以连上128个设备

devpath [16],它显然是用来记录一个字符串的,这个字符串啥意思?使用命令 ls /sys/bus/usb/devices/观察输出内容,它们都是啥?

[root@localhost test]# ls /sys/bus/usb/devices

1-0:1.0 2-0:1.0 usb1 usb2

usb1/usb2/表示计算机上接了2条usb总线,即2个usb主机控制器,事物多了自然就要编号,就跟我们中学或大学里面的学号一样,就是用于区分多个个体,而2-0:1.0表示什么?2表示是2号总线,或者说2号Root Hub,0就是这里我们说的devpath,1表示配置为1号,0表示接口号为0。也即是说,2号总线的0号端口的设备,使用的是1号配置,接口号为0。那么devpath是否就是端口号呢?显然不是,这里我列出来的这个例子是只有Root Hub没有级联Hub的情况,如果在Root Hub上又接了别的Hub,然后一级一级连下去,子又生孙,孙又生子,子又有子,子又有孙。如何在sysfs里面来表征这整个大家族呢?这就是devpath的作用,顶级的设备其devpath就是其连在Root Hub上的端口号,而次级的设备就是其父Hub的devpath后面加上其端口号,即如果2-0:1.0如果是一个Hub,那么它下面的1号端口的设备就可以是2-0.1:1.0,2号端口的设备就可以是2-0.2:1.0,3号端口就可以是4-0.3:1.0。总的来说,就是端口号一级一级往下加。

state,设备的状态。这是个枚举类型. 上面定义了9种状态,spec里只定义了6种,Attached,Powered,Default,Address,Configured,Suspended,对应于Table 9.1。USB设备从生到死都要按照这么几个状态,遵循这么一个过程,具体可以看协议解释。

speed,设备的速度,这也是个枚举变量。USB设备有三种速度,低速,全速,高速。不过现在出现usb3.0,那叫什么速度??忘了。

tt,第一眼就想到了xxoo,别想歪了。tt叫做transaction translator。你可以把它想成一块特殊的电路,是hub里面的电路,确切的说是高速hub中的电路,我们知道usb设备有三种速度的,分别是low speed,full speed,high speed。即所谓的低速/全速/高速,抗日战争那会儿,这个世界上只有low speed/full speed的设备,没有high speed的设备,后来解放后,国民生产力的大幅度提升催生了一种high speed的设备,包括主机控制器,以前只有两种接口的,OHCI/UHCI,这都是在usb spec 1.0的时候,后来2.0推出了EHCI,高速设备应运而生。Hub也有高速hub和过去的hub,但是这里就有一个兼容性问题了,高速的hub是否能够支持低速/全速的设备呢?一般来说是不支持的,于是有了一个叫做TT的电路,它就负责高速和低速/全速的数据转换,于是,如果一个高速设备里有这么一个TT,那么就可以连接低速/全速设备,如不然,那低速/全速设备没法用,只能连接到OHCI/UHCI那边出来的hub口里。

toggle[2],这个数组只有两个元素,分别对应IN和OUT端点,每一个端点占一位。这个数组存在的价值是什么?真是一言难尽。前面分析若要实现usb通信,需要创建一个urb,为它赋好值(也就是初始化),交给咱们的usb core就可以了。实际上,咱们提交的是urb,usb cable里流淌的却是一个一个的数据包(packet),所有的packets都从一个SYNC同步字段开始,SYNC是一个8位长的二进制串,只是用来同步用的,它的最后两位标志了SYNC的结束和PID(Packet Identifer)的开始。PID也是一个8位的二进制串,前四位用来区分不同的packet类型,后面四位只是前四位的反码,校验用的。packet的类型主要有四种,在spec的Table 8-1里有说明。通过PID来判断送过来的packet是不是自己所需要的,PID之后紧跟着的是地址字段,每个packet都需要知道自己要往哪里去,地址实际上包括两部分,7位表示了总线上连接的设备或接口的地址,4位表示端点的地址,这就是为什么前面说每条usb总线最多只能有128个设备,即使是高速设备每个接口除了0号端点也最多只能有15个in端点和15个out端点。地址字段再往后是11位的帧号(frame number),值达到7FFH时归零。这个帧号并不是每一个packet都会有,它只在每帧或微帧(Mircoframe)开始的SOF Token包里发送。帧是对于低速和全速模式来说的,一帧就是1ms,对于高速模式的称呼是微帧,一个微帧为125微妙,每帧或微帧当然不会只能传一个packet。帧号再往后就是千呼万唤始出来的Data字段了,它可以有0到1024个字节不等。最后还有CRC校验字段来做扫尾工作。

咱们要学习packet,但这里只看看Data类型的packet。前面的Table 8-1里显示,有四种类型的Data包,DATA0,DATA1,DATA2和MDATA。存在就是有价值的,这里分成4种数据包自然有里面的道理,其中DATA0和DATA1就可以用来实现data toggle同步,对于批量传输、控制传输和中断传输来说,数据包最开始都是被初始化为DATA0的,然后为了传输的正确性,就一次传DATA0,一次传DATA1,一旦哪次打破了这种平衡,主机就可以认为传输出错了。对于等时传输来说,data toggle并不被支持。所以我们的struct usb_device中的数组unsigned int toggle[2]就是为了支持这种简单的循环而存在的,它里面的每一位表示的就是每个端点当前发送或接收的数据包是DATA0还是DATA1。

parent,struct usb_device结构体的parent自然也是一个struct usb_device指针。对于Root Hub,前面说过,它是和Host Controller是绑定在一起的,它的parent指针在Host Controller的驱动程序中就已经赋了值,这个值就是NULL,换句话说,对于Root Hub,它不需要再有父指针了,这个父指针就是给从Root Hub连出来的节点用的。USB设备是从Root Hub开始,一个一个往外面连的,比如Root Hub有4个口,每个口连一个USB设备,比如其中有一个是Hub,那么这个Hub有可以继续有多个口,于是一级一级的往下连,最终连成了一棵树。

bus设备所在的那条总线。

ep0,端点0的特殊地位决定了她必将受到特殊的待遇,在struct usb_device对象产生的时候它就要初始化。

dev,嵌入到struct usb_device结构里的struct device结构。

desc,设备描述符。它在include/linux/usb/ch9.h里定义。和前面一样,我们最后来分析它。和前面分析一下,我们先略过usb_device_descriptor结构体的分析。

Config、actconfig,分别表示设备拥有的所有配置和当前激活的配置(也就是正在使用的配置)。

ep_in[16],ep_out[16],除了端点0,一个设备即使在高速模式下也最多只能再有15个IN端点和15个OUT端点,端点0太特殊了,对应的管道是Message管道,又能进又能出,所以这里的ep_in和ep_out数组都有16个值。

rawdescriptors,这是个字符指针数组,数组里的每一项都指向一个使用GET_DESCRIPTOR请求去获得配置描述符时所得到的结果。考虑下,为什么我只说得到的结果,而不直接说得到的配置描述符?不是请求的就是配置描述符么?这是因为你使用GET_DESCRIPTOR去请求配置描述符时,设备返回给你的不仅仅只有配置描述符,它把该配置所包括的所有接口的接口描述符,还有接口里端点的端点描述符一股脑的都塞给你了。第一个接口的接口描述符紧跟着这个配置描述符,然后是这个接口下面端点的端点描述符,如果有还有其它接口,它们的接口描述符和端点描述符也跟在后面,比如专门为一类设备定义的描述符和厂商定义的描述符就跟在它们对应的标准描述符后面。这里再次提到了GET_DESCRIPTOR请求,就顺便简单说一下USB的设备请求(device request)。协议里说了,所有的设备通过缺省的控制管道来响应主机的请求,既然使用的是控制管道,那当然就是控制传输了,这些请求的底层packet属于Setup类型,在Setup包里包括了请求的各种参数。协议里同时也定义了一些标准的设备请求,并规定所有的设备必须响应它们,即使它们还处于Default或Address状态。这些标准的设备请求里,GET_DESCRIPTOR就赫然在列。

bus_mA,这个值是在host controller的驱动程序中设置的,通常来讲,计算机的usb端口可以提供500mA的电流。

portnum,不管是root hub还是一般的hub,你的USB设备总归要插在一个hub的端口上才能用,portnum就是那个端口号。当然,对于root hub这个usb设备来说它本身没有portnum这么一个概念,因为它不插在别的Hub的任何一个口上。所以对于Root Hub来说,它的portnum在Host Controller的驱动程序里给设置成了0。

level,层次,也可以说是级别,表征usb设备树的级连关系。Root Hub的level当然就是0,其下面一层就是level 1,再下面一层就是level 2,依此类推。

can_submit URBs may be submitted。

discon_suspended,Disconnected while suspended。

have_langid,string_langid,usb设备里的字符串描述符使用的是UNICODE编码,可以支持多种语言,string_langid就是用来指定使用哪种语言的,have_langid用来判断string_langid是否有效。

product,manufacturer,serial,分别用来保存产品、厂商和序列号对应的字符串描述符信息。

struct list_head filelist;

#ifdef CONFIG_USB_DEVICE_CLASS

struct device *usb_classdev;

#endif

#ifdef CONFIG_USB_DEVICEFS

struct dentry *usbfs_dentry;

#endif

这些和usbfs相关的,以后可以再聊它们。

maxchild,hub的端口数,注意可不包括上行端口。其实hub可以接一共255个端口,不过实际上遇到的usb hub最多的也就是说自己支持10个端口的,所以31基本上够用了。

pm_usage_cnt在前面usb总线设备与接口一节中已经说过。

quirks,中文意思大概是“毛病”。总是有些厂商不太守规矩,拿出一些有点毛病的产品给我们用,你说它大毛病吧,也不是。不说远了,总之这里的quirk就是用来判断这些有毛病的产品啥毛病的。谁去判断?hub去判断,就不用咱费心了。

看到#ifdef CONFIG_PM这个标志,我们就知道从这里直到最后的那个#endif都是关于电源管理的。让我们先大胆的忽略它们。

最后看一下设备描述符结构体吧。

/* USB_DT_DEVICE: Device descriptor */

struct usb_device_descriptor {

__u8 bLength;

__u8 bDescriptorType;

__le16 bcdUSB;

__u8 bDeviceClass;

__u8 bDeviceSubClass;

__u8 bDeviceProtocol;

__u8 bMaxPacketSize0;

__le16 idVendor;

__le16 idProduct;

__le16 bcdDevice;

__u8 iManufacturer;

__u8 iProduct;

__u8 iSerialNumber;

__u8 bNumConfigurations;

} __attribute__ ((packed));

#define USB_DT_DEVICE_SIZE 18

bLength,描述符的长度,可以自己数数,或者看紧接着的定义USB_DT_DEVICE_SIZE。

bDescriptorType,这里对于设备描述符应该是USB_DT_DEVICE,0x01。

bcdUSB,USB spec的版本号,一个设备如果能够进行高速传输,那么它设备描述符里的bcdUSB这一项就应该为0200H。

bDeviceClass,bDeviceSubClass,bDeviceProtocol,和接口描述符的意义差不多,前面说了这里就不再罗唆了。

bMaxPacketSize0,端点0一次可以处理的最大字节数,端点0的属性却放到设备描述符里去了,更加彰显了它突出的江湖地位。前面说端点的时候说了端点0并没有一个专门的端点描述符,因为不需要,基本上它所有的特性都在spec里规定好了的,然而,别忘了这里说的是“基本上”,有一个特性则是不一样的,这叫做maximum packet size,每个端点都有这么一个特性,即告诉你该端点能够发送或者接收的包的最大值。对于通常的端点来说,这个值被保存在该端点描述符中的wMaxPacketSize这一个field,而对于端点0就不一样了,由于它自己没有一个描述符,而每个设备又都有这么一个端点,所以这个信息被保存在了设备描述符里,所以我们在设备描述符里可以看到这么一项,bMaxPacketSize0。而且spec还规定了,这个值只能是8,16,32或者64这四者之一,如果一个设备工作在高速模式,这个值还只能是64,如果是工作在低速模式,则只能是8,取别的值都不行。

idVendor,idProduct,分别是厂商和产品的ID。

bcdDevice,设备的版本号。

iManufacturer,iProduct,iSerialNumber,分别是厂商,产品和序列号对应的字符串描述符的索引值。

bNumConfigurations,设备当前速度模式下支持的配置数量。有的设备可以在多个速度模式下操作,这里包括的只是当前速度模式下的配置数目,不是总的配置数目。

这就是设备描述符,它和spec Table 9-8是一一对应的。

struct usb_device这个结构已经够让我们疲惫了,还是换换口味吧。