暂时先告别媒人,我们去分析各自的生命旅程,最后还会回到usb_device_match函数。

首先当你将usb设备连接在hub的某个端口上,hub检测到有设备连接了进来,它会为设备分配一个struct usb_device结构的对象并初始化,并调用设备模型提供的接口将设备添加到usb总线的设备列表里,然后usb总线会遍历驱动列表里的每个驱动,调用自己的match函数看它们和你的设备或接口是否匹配。hub检测到自己的某个端口有设备连接了进来后,它会调用core里的usb_alloc_dev函数为struct usb_device结构的对象申请内存,这个函数在drivers/usb/core/usb.c文件里定义。

/**
* usb_alloc_dev - usb device constructor (usbcore-internal)
* @parent: hub to which device is connected; null to allocate a root hub
* @bus: bus used to access the device
* @port1: one-based index of port; ignored for root hubs
* Context: !in_interrupt()
*
* Only hub drivers (including virtual root hub drivers for host
* controllers) should ever call this.
*
* This call may not be used in a non-sleeping context.
*/
struct usb_device *
usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
{
struct usb_device *dev; dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return NULL; if (!usb_get_hcd(bus_to_hcd(bus))) {
kfree(dev);
return NULL;
} device_initialize(&dev->dev);
dev->dev.bus = &usb_bus_type;
dev->dev.type = &usb_device_type;
dev->dev.dma_mask = bus->controller->dma_mask;
dev->state = USB_STATE_ATTACHED; INIT_LIST_HEAD(&dev->ep0.urb_list);
dev->ep0.desc.bLength = USB_DT_ENDPOINT_SIZE;
dev->ep0.desc.bDescriptorType = USB_DT_ENDPOINT;
/* ep0 maxpacket comes later, from device descriptor */
dev->ep_in[0] = dev->ep_out[0] = &dev->ep0; /* Save readable and stable topology id, distinguishing devices
* by location for diagnostics, tools, driver model, etc. The
* string is a path along hub ports, from the root. Each device's
* dev->devpath will be stable until USB is re-cabled, and hubs
* are often labeled with these port numbers. The bus_id isn't
* as stable: bus->busnum changes easily from modprobe order,
* cardbus or pci hotplugging, and so on.
*/
if (unlikely(!parent)) {
dev->devpath[0] = '0'; dev->dev.parent = bus->controller;
sprintf(&dev->dev.bus_id[0], "usb%d", bus->busnum);
} else {
/* match any labeling on the hubs; it's one-based */
if (parent->devpath[0] == '0')
snprintf(dev->devpath, sizeof dev->devpath,
"%d", port1);
else
snprintf(dev->devpath, sizeof dev->devpath,
"%s.%d", parent->devpath, port1); dev->dev.parent = &parent->dev;
sprintf(&dev->dev.bus_id[0], "%d-%s",
bus->busnum, dev->devpath); /* hub driver sets up TT records */
} dev->portnum = port1;
dev->bus = bus;
dev->parent = parent;
INIT_LIST_HEAD(&dev->filelist); #ifdef CONFIG_PM
mutex_init(&dev->pm_mutex);
INIT_DELAYED_WORK(&dev->autosuspend, usb_autosuspend_work);
dev->autosuspend_delay = usb_autosuspend_delay * HZ;
#endif
return dev;
}

usb_alloc_dev函数就相当于usb设备的构造函数,参数里边儿,parent是设备连接的那个hub,bus是设备连接的那条总线,port1就是设备连接在hub上的那个端口。

kzalloc为一个struct usb_device结构的对象申请内存并初始化为0。其实就是用kmalloc加memset这对儿最佳拍档来申请内存和初始化,kzalloc直接取代了kmalloc/memset,一个函数起到了两个函数的作用。然后是判断内存申请成功了没,不成功就不用往下走了。那么通过这么几行,咱们应该记住两个凡是,凡是你想用kmalloc/memset组合申请内存的时候,就使用kzalloc代替吧,凡是申请内存的,不要忘了判断是不是申请成功了。我们的心里应该把这两个凡是提高到和四项基本原则一样的地位。

usb_get_hcd(bus_to_hcd(bus) 这里的两个函数是和主机控制器驱动相关的,具体不分析了。只要知道usb的世界里一个主机控制器对应着一条usb总线,主机控制器驱动用struct usb_hcd结构表示,一条总线用struct usb_bus结构表示,函数bus_to_hcd是为了获得总线对应的主机控制器驱动,也就是struct usb_hcd结构对象,函数usb_get_hcd只是将得到的这个usb_hcd结构对象的引用计数加1,为什么?因为总线上多了一个设备,设备在主机控制器的数据结构就得在,当然得为它增加引用计数。如果这俩函数没有很好的完成自己的任务,那整个usb_alloc_dev函数也就没有继续执行下去的必要了,将之前为struct usb_device结构对象申请的内存释放掉。

device_initialize是设备模型里的函数,这里就是将struct usb_device结构里嵌入的那个struct device结构体初始化。

然后把设备所在的总线类型设置为usb_bus_type。usb_bus_type在usb子系统的初始化函数usb_init里就把它给注册掉了,还记得聊到模型的时候说过的那个著名的三角关系不,这里就是把设备和总线这条边儿给搭上了。接着,将设备的设备类型初始化为usb_device_type,还记得在讨论usb_device_match函数一节里留下来的疑问吗?,走设备那条路,使用is_usb_device判断是不是usb设备时,在这儿把设备的类型给初始化成usb_device_type了。

dev->dev.dma_mask这个就是与DMA传输相关的了,设备能不能进行dma传输,得看主机控制是否支持。所以这里dma_mask被设置为host controller的dma_mask。

dev->state将usb设备的状态设置为Attached,表示设备已经连接到usb接口上了,是hub检测到设备时的初始状态。

接下来是端点0,她实在是太太太特殊了,这个咱们是一而再再而三的感叹,struct usb_device里直接就有这么一个成员ep0,这行就将ep0的urb_list给初始化掉。因为接下来遇到的那些主要角色的成员前面已经集中都说过了,咱们就不再说它们是嘛意思了,忘记了可以回头再看。然后分别初始化了端点0的描述符长度和描述符类型,并且使能端点进行usb数据传输。

unlikely(x)就是告诉编译器条件x发生的可能性不大,那么这个条件块儿里语句的目标码可能就会被放在一个比较远的为止,以保证经常执行的目标码更紧凑。likely则相反。整个条件分支的代码大概意思是:首先判断你的设备是不是直接连到root hub上的,如果是,将dev->devpath[0]赋值为‘0’,以示特殊,然后父设备设为controller,同时把dev->bus_id[]设置为像usb1/usb2/usb3/usb4这样的字符串。如果你的设备不是直接连到root hub上的,有两种情况,如果你的设备连接的那个hub是直接连到root hub上的,则dev->devpath就等于端口号,否则dev->devpath就等于在父hub的devpath基础上加一个‘.’再加一个端口号,最后把bus_id[]设置成1-/2-/3-/4-这样的字符串后面连接上devpath。

剩下的还有一些初始化,和usbfs、电源管理有关的,暂时忽略吧。

现在设备的struct usb_device结构体通过usb_alloc_dev函数已经准备好了,只是做了简单的美容(初始化),而接下来hub会继续给它做做美容。比如:将设备的状态设置为Powered,也就是加电状态;因为此时还不知道设备支持的速度,于是将设备的speed成员暂时先设置为USB_SPEED_UNKNOWN;设备的级别level当然会被设置为hub的level加上1了;还有为设备能够从hub那里获得的电流赋值;为了保证通信畅通,hub还会为设备在总上选择一个独一无二的地址。

到目前为止,usb_device结构体里成员的状况总结如下:其中taken只是表示赋过值了。那赋了什么值?没必要知道的那么详细。

设备现在已经处在了Powered状态。设备要想从Powered状态发展到下一个状态Default,必须收到一个复位信号并成功复位。那hub接下来的动作就很明显了,复位设备,复位成功后,设备就会进入Default状态。设备成功复位后进入Default状态,同时,hub也会获得设备真正的速度,低速、全速也好,高速也罢,总算是浮出水面了,speed也终于知道了自己的真正身份,不用再是UNKNOWN了。那根据这个速度,咱们能知道些什么?起码能够知道端点0一次能够处理的最大数据长度啊,协议里说,对于高速设备,这个值为为64字节,对于低速设备为8字节,而对于全速设备可能为8,16,32,64其中的一个。遇到这种模棱两可的答案,写代码的哥们儿不会满足,咱们也不会满足,所以hub还要通过一个蜿蜒曲折的过程去获得这个确定的值,至于怎么个曲折法儿,此处省略xxxxx。Hub进入Default状态辛苦了那么久后,终于要把设备带入Address状态。这在内核里的实现就很简单了,只要hub使用core里定义的一个函数usb_control_msg,发送SET_ADDRESS请求给设备,设备就兴高采烈的迈进Address了。那么设备的这个address是什么,就是上面的devnum啊。

上面理论说了一大堆,不知你看的可否明白?那现在咱就来说说这个usb_control_msg函数,它在drivers/usb/core/message.c里定义。

/**
* usb_control_msg - Builds a control urb, sends it off and waits for completion
* @dev: pointer to the usb device to send the message to
* @pipe: endpoint "pipe" to send the message to
* @request: USB message request value
* @requesttype: USB message request type value
* @value: USB message value
* @index: USB message index value
* @data: pointer to the data to send
* @size: length in bytes of the data to send
* @timeout: time in msecs to wait for the message to complete before timing
* out (if 0 the wait is forever)
*
* Context: !in_interrupt ()
*
* This function sends a simple control message to a specified endpoint and
* waits for the message to complete, or timeout.
*
* If successful, it returns the number of bytes transferred, otherwise a
* negative error number.
*
* Don't use this function from within an interrupt context, like a bottom half
* handler. If you need an asynchronous message, or need to send a message
* from within interrupt context, use usb_submit_urb().
* If a thread in your driver uses this call, make sure your disconnect()
* method can wait for it to complete. Since you don't have a handle on the
* URB used, you can't cancel the request.
*/
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
__u8 requesttype, __u16 value, __u16 index, void *data,
__u16 size, int timeout)
{
struct usb_ctrlrequest *dr;
int ret; dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_NOIO);
if (!dr)
return -ENOMEM; dr->bRequestType = requesttype;
dr->bRequest = request;
dr->wValue = cpu_to_le16(value);
dr->wIndex = cpu_to_le16(index);
dr->wLength = cpu_to_le16(size); /* dbg("usb_control_msg"); */ ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout); kfree(dr); return ret;
}

这个函数主要目的是创建一个控制urb,并把它发送给usb设备,然后等待它完成。urb是什么?忘了么,前面提到过的,你要想和你的usb通信,就得创建一个urb,并且为它赋好值,交给usb core,它会找到合适的host controller,从而进行具体的数据传输。

为一个struct usb_ctrlrequest结构体申请了内存。它在include/linux/usb/ch9.h文件里定义。

struct usb_ctrlrequest {

__u8 bRequestType;

__u8 bRequest;

__le16 wValue;

__le16 wIndex;

__le16 wLength;

} __attribute__ ((packed));

这个结构完全对应usb规范里面的Table 9-2,描述了主机通过控制传输发送给设备的请求(Device Requests)。主机向设备请求些信息必须得按照协议里规定好的格式,不然设备就会不明白主机是嘛意思,这个结构描述的request都在Setup包里发送(想想这句话对吗),Setup包是前面某处说到的Token PID类型中的一种。

这里细说一下控制传输底层的packet情况。控制传输最少要有两个阶段的transaction,SETUP和STATUS,SETUP和STATUS中间的那个DATA阶段是可有可无的。Transaction可以理解为主机和设备之间形成的一次完整的交流,usb的transaction可以包括一个Token包、一个Data包和一个Handshake包。

Token、Data和Handshake都属于四种PID类型中的,前面说到时提到的一个包里的那些部分,如SYNC、PID、地址域、DATA、CRC,并不是所有PID类型的包都会全部包括的。Token包只包括SYNC、PID、地址域、CRC,并没有DATA字段,它的名字起的很形象,就是用来标记所在transaction里接下来动作的,对于Out和Setup Token包,里面的地址域指明了接下来要接收Data包的端点,对于In Token包,地址域指明了接下来哪个端点要发送Data包。还有,只有主机才有权利发送Token包,协议里就这么规定的。与Token包相比,Data包里没了地址域,多了Data字段,这个Data字段对于低速设备最大为8字节,对于全速设备最大为1023字节,对于高速设备最大为1024字节。里里外外看过去,它就是躲在Token后边儿用来传输数据的。Handshake包的成分就非常的简单,除了SYNC,它就只包含了一个PID,通过PID取不同的值来报告一个transaction的状态,比如数据已经成功接收了等。控制传输的SETUP transaction一般来说也有三个阶段,就是主机向设备发送Setup Token包、然后发送Data0包,如果一切顺利,设备回应ACK Handshake包表示OK,为什么加上一般?如果中间的那个Data0包由于某种不可知因素被损坏了,设备就什么都不会回应,这时就成俩阶段了。SETUP transaction之后,接下来如果控制传输有DATA transaction的话,那就Data0、Data1这样交叉的发送数据包,前面说过这是为了实现data toggle。最后是STATUS transaction,向主机汇报前面SETUP和DATA阶段的结果,比如表示主机下达的命令已经完成了,或者主机下达的命令没有完成,或者设备正忙着那没功夫去理会主机的那些命令。这样经过SETUP、DATA、STATUS这三个transaction阶段,一个完整的控制传输完成了。主机接下来可以规划下一次的控制传输。这样看来,前面说requests都在Setup包里发送是有问题的,因为Setup包本身并没有数据字段,严格来说它们应该都是在SETUP transaction阶段里Setup包后的Data0包里发送的下面看一下彩图就明白了,是usb鼠标的一个传输例子。

bRequestType,这个字段别看就一个字节,内容很丰富的,大道理往往都包含这种在小地方。它的bit7就表示了控制传输中DATA transaction阶段的方向,当然,如果有DATA阶段的话。bit5~6表示request的类型,是标准的,class-specific的还是vendor-specific的。bit0~4表示了这个请求针对的是设备,接口,还是端点。内核为它们专门量身定做了一批掩码,也在ch9.h文件里,就不说了,自己去看吧。

bRequest,表示具体是哪个request。

wValue,这个字段是request的参数,request不同,wValue就不同。

wIndex,也是request的参数,bRequestType指明request针对的是设备上的某个接口或端点的时候,wIndex就用来指明是哪个接口或端点。

wLength,控制传输中DATA transaction阶段的长度,方向已经在bRequestType那儿指明了。如果这个值为0,就表示没有DATA transaction阶段,bRequestType的方向位也就无效了。

回到usb_control_msg函数里。很明显要进行控制传输,得首先创建一个struct usb_ctrlrequest结构体,填上请求的内容。对于刚开始提到的SET_ADDRESS来说,bRequest的值就是USB_REQ_SET_ADDRESS,标准请求之一,ch9.h里定义有。因为SET_ADDRESS请求并不需要DATA阶段,所以wLength为0,而且这个请求是针对设备的,所以wIndex也为0。这么一来,bRequestType的值也只能为0了。因为是设置设备地址的,总得把要设置的地址发给设备,不然设备会比咱们还一头雾水不知道主机是嘛个意思,所以请求的参数wValue就是之前hub已经为设备指定好的devnum。其实SET_ADDRESS请求各个部分的值spec 9.4.6里都有规定。

先kfree(dr)行代码,走到这儿就表示成也好败也好,总之这次通信已经完成了,那么struct usb_ctrlrequest结构体也就没用了,没用的东西要好不犹豫的释放掉。

最后分析一下剩下的一行代码,它又能把我们带往黑暗之处。

最新文章

  1. jquery.fn.extend与jquery.extend--(初体验二)
  2. Javascript创建对象的学习和使用
  3. 采用httpclient提交数据到服务器
  4. php操作文件(读取写入文件)
  5. DirectX 总结和DirectX 9.0 学习笔记
  6. iOS 网络处理注意点
  7. 微软职位内部推荐-Senior Dev Lead
  8. jquery插件dataTables自增序号。
  9. Linux之shell编程基础
  10. 使用vs2010打开VS2013的工程文件
  11. cocos2d-x---开篇介绍
  12. JQuery 中$("input:eq(0)") eq 的意思
  13. Day5 Pyhton基础之编码与解码(四)
  14. Oracle RMAN备份与还原
  15. [转]jQuery中clone和clone(true)的区别
  16. [development][C] C语言标准
  17. mysql常用反斜杠命令
  18. Git创建新项目
  19. 《Android编程权威指南》
  20. Python运维开发基础04-语法基础

热门文章

  1. Sharepoint增加修改密码功能
  2. iOS 7中实现模糊效果
  3. Swift学习--微博的基础框架搭建
  4. JQuery EasyUI中datagrid的使用
  5. nginx入门(安装,启动,关闭,信号量控制)
  6. lucene入门
  7. eclipse插件本地扩展安装
  8. Hibernate案例-------基于xml配置,使用Hibernate实现对员工表的增、删、改、查功能
  9. 烂泥:【解决】word复制windows live writer没有图片
  10. ehcache的介绍和使用