科创园

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

usb驱动开发17之设备生命线

Posted on 2014-04-01 10:39  科创园  阅读(1599)  评论(0编辑  收藏  举报

拜会完了山头的几位大哥,还记得我们从哪里来要到哪里去吗?时刻不能忘记自身的使命啊。我们是从usb_submit_urb()最后的那个遗留问题usb_hcd_submit_urb()函数一路走来,现在就要去分析usb_hcd_submit_urb()里面的内容。

 

usb_hcd_submit_urb目标就是将提交过来的urb指派给合适的主机控制器驱动程序。

看第一个函数,bus_to_hcd是用来获得struct usb_bus结构体对应的struct usb_hcd结构体,urb要去的那个设备所在的总线是在设备生命线的开头儿就初始化好了的,忘了可以再蓦然回首一下。

接下来就是一些必备的常规检验,如果usb_hcd都还是空的,返回吧。

usbmon_urb_submit就是与前面的usb Monitor有关的,如果你编译内核的时候没有配置上CONFIG_USB_MON,它就啥也不是,一个空函数。

spin_lock_irqsave去获得一把锁,这把锁在hcd.c的开头儿就已经初始化好了,所以说是把全局锁

* used when updating hcd data */

static DEFINE_SPINLOCK(hcd_data_lock);

现在简单介绍一下自旋锁,懂了的就请飘过。它和信号量,还有前面提到的completion一样都是linux里用来进行代码同步(或者同步资源,互斥访问,随你怎么叫),为什么要进行同步?你要知道在linux这个庞大负责的世界里,可能同时有多个线程,那么只要他们互相之间有一定的共享,就必须要保证一个人操作这个共享的时候让其它人知道。举个例子,你和另外一个人带领两只队伍去打土匪窝,结果你方武力值,智力值比较高,先占领了山头儿,你得插把旗子表示你已经占领了,让友军不要再打了,过来享受胜利果实吧,里边土匪太太,美酒美食一堆一堆的,如果你什么都不说,没插旗子也没其它什么暗号表示一下,只顾自个儿享受了,那边儿正打着过瘾谁知道在匪窝儿里的是你还是土匪啊。所以说同步是多重要啊,保持共享代码状态一致是多么重要啊。自旋锁身为同步机制的一种,自然也有它独特的本事,它可以用在中断上下文或者说原子上下文使用。上下文就是你代码运行的环境,linux的这个环境使用两种,能睡觉的环境和不能睡觉的环境。像信号量和completion就只能用在可以睡觉的环境,而自旋锁就用在不能睡觉的环境里。而咱们的usb_submit_urb还有usb_hcd_submit_urb必须得在两种环境里都能够使用,所以使用的是自旋锁,那在什么时候都不能睡觉了还有心情去调用它们那?想想urb的那个结束处理函数,它就是不能睡觉的,但它里面必须得能够重新提交urb。

那hcd_data_lock这把锁究竟是用来保护些什么的,为什么要使用它?主机控制器的struct usb_hcd结构体在它的驱动里早就初始化好了,就那么一个,但同一时刻是可能有多个urb向同一主机控制器申请进行传输,可能有多个地方都希望访问它里面的内容。

所以hcd_data_lock这把锁就是专门用来保护主机控制器的这个结构体的。

接着自旋锁往下看,知道了pipe,就可以从struct usb_device里的两个数组ep_in和ep_out里拿出对应端点的struct usb_host_endpoint结构体。

然后判断检验错误,如果一切正常,则继续往下走。

检验都通过了,usb_get_urb行就是增加urb的引用计数了。

下一行同时将urb的use_count也增加1,表示urb已经被HCD接受了,正在被处理着。你如果对这两个引用计数什么差别还有疑问,再蓦然回首一下看看说struct urb时讲的。

然后判断这个urb是不是流向root hub的,如果是,它就走向了root hub的生命线。如果这个主机控制器支持DMA,可你却没有告诉它URB_NO_SETUP_DMA_MAP或URB_NO_TRANSFER_DMA_MAP这两个标志,它就会认为你在urb里没有提供DMA的缓冲区,就会调用dma_map_single将setup_packet或transfer_buffer映射为DMA缓冲区。

hcd->driver->urb_enqueue (hcd, ep, urb, mem_flags)行将urb扔给具体的主机控制器驱动程序了。下面的路就让urb去走吧

咱们说到这里也该回头看看,始终都不能忘怀自己是从设置设备地址,发送SET_ADDRESS请求给主机控制器开始,一路走到现在。如今设备已经可以进入Address状态,这桩心愿已了,该继续看设备的那条生命线了。

回顾已经分析的设备生命线过程,从Attached走到Powered只是弹指一挥间,从Powered再到Default虽说要复位一下,也算是三下五除二了,再从Default走到Address。接下来设备的目标当然就是Configured了。

要进入Configured状态,你得去配置设备,当然不能是盲目的去配置,要知道设备是可能有多个配置的,所以你要有选择有目的有步骤有计划的去配置。就要先去获得设备的设备描述符,message.c中的usb_get_device_descriptor()就是core里专门干这个的。这个函数比较流程大概是准备了一个struct usb_device_descriptor结构体,然后就用它去调用message.c里的usb_get_descriptor()获得设备描述符,获得之后再把得到的描述符复制到设备struct usb_device结构体的descriptor成员里。因此,这个函数成功与否的关键就在usb_get_descriptor()。其实对于写驱动的来说,眼里是只有usb_get_descriptor()没有usb_get_device_descriptor()的,不管你想获得哪种描述符都是要通过usb_get_descriptor(),而usb_get_device_descriptor()是专属内核用的接口。

 

参数type就是用来区分不同的描述符的,协议里说了,GET_DESCRIPTOR请求主要就是适用于三种描述符,设备描述符,配置描述符和字符串描述符。参数index是要获得的描述符的序号,如果希望得到的这种描述符设备里可以有多个,你需要指定获得其中的哪个,比如配置描述符就可以有多个,不过对于设备描述符来说,是只有一个的,所以这里的index应该为0。参数buf和size就是描述你用来放置获得的描述符的缓冲区的。

这个函数主要就是调用了一个usb_control_msg(),现在仔细说说这个函数usb_control_msg()。

这里要说的第一个问题是它的一堆参数,这就需要认真了解一下spec 9.4.3里的这张表

clip_image002

GET_DESCRIPTOR请求的数据传输方向很明显是device-to-host的,而且还是协议里规定所有设备都要支持的标准请求,也不是针对端点或者接口什么的,而是针对设备的,所以bRequestType只能为0x80,就是上面表里的10000000B,也等于参数中的USB_DIR_IN。wValue的高位字节表示描述符的类型,低位字节表示描述符的序号,所以就有参数中的(type << 8) + index。wIndex对于字符串描述符应该设置为使用语言的ID,对于其它的描述符应该设置为0,所以参数中间的那个0。至于wLength,就是描述符的长度,对于设备描述符,一般来说你都会指定为USB_DT_DEVICE_SIZE吧。USB_CTRL_GET_TIMEOUT是定义在include/linux/usb.h里的一个宏,值为5000,表示有5s的超时时间。

第二个问题就是为什么会有3次循环。这个又要归咎于一些不守规矩的厂商了,搞出的设备古里古怪的,比如一些usb读卡器,一次请求还不定能成功,但是设备描述符拿不到接下来就没法子走了,所以这里多试几次,再不成功,就成鬼了。If判断的代码都是判断是不是成功得到请求的描述符,这个版本的内核这里的判断还比较混乱,就不多说了,你只要知道((u8 *)buf)[1] != type是用来判断获得描述符是不是请求的类型就可以了。

现在设备描述符已经有了,但是只有设备描述符是远远不够的,你从设备描述符里只能知道它一共支持几个配置,具体每个配置是是什么?所以接下来就要获得各个配置的配置描述符,并且拿结果去充实struct usb_device的config、rawdescriptors等相关元素。core内部并不直接调用上面的usb_get_descriptor()去完成这个任务,而是调用config.c里的usb_get_configuration()。

看这个函数代码前,先说点理论垫底。前面经过那么多努力,要想得到配置描述符,最终都不可避免的要向设备发送GET_DESCRIPTOR请求,这就需要以USB_DT_CONFIG为参数调用usb_get_descriptor函数,也就需要知道该为获得的描述符准备多大的一个缓冲区,本来这个长度应该很明确的为USB_DT_CONFIG_SIZE,它表示的就是配置描述符的大小,但是实际上不是这么回事儿,USB_DT_CONFIG_SIZE只表示配置描述符本身的大小,并不表示GET_DESCRIPTOR请求返回结果的大小。因为向设备发送GET_DESCRIPTOR请求时,设备并不单单返回一个配置描述符了事,而是一股脑儿的将这个配置下面的所有接口描述符,端点描述,还有class-或vendor-specific描述符都返回了给你(协议里这么说的,不是我胡诌哦)。那么这个总长度如何得到那?配置描述符里有这样一个神秘的字段wTotalLength,它里面记录的就是这个总长度,那么问题就简单了,可以首先发送USB_DT_CONFIG_SIZE个字节的请求过去,获得这个配置描述符的内容,从而获得那个总长度,然后以这个长度再请求一次,这样就可以获得一个配置下面所有的描述符内容了。上面的usb_get_configuration()采用的就是这个处理方法。

 

在获得设备描述符的数目后,迎来了常规检查。飘过吧

宏USB_MAXCONFIG是config.c理定义的,限制了一个设备最多只能支持8种配置拥有8个配置描述符,如果超出了这个限制,就强制它为这个最大值。不过如果设备里没有任何一个配置描述符,什么配置都没有就给你返回错误。

struct usb_device里的config表示的是设备拥有的所有配置,你设备有多少个配置就为它准备多大的空间。

rawdescriptors这是个字符指针数组里的每一项都指向一个使用GET_DESCRIPTOR请求去获取配置描述符时所得到的结果。

然后准备一个大小为USB_DT_CONFIG_SIZE的缓冲区,第一次发送GET_DESCRIPTOR请求要用的。

剩下的主要就是这个for循环了,获取每一个配置的那些描述符。

如同上面理论所说那样,首先发送USB_DT_CONFIG_SIZE个字节请求,获得配置描述符的内容。然后对返回的结果进行检验,知道为什么会判断结果是不是小于4么?答案尽在配置描述符中,里面的3,4字节就是wTotalLength,只要得到前4个字节,就已经完成任务能够获得总长度了。

既然总长度已经有了,那么这里就为接下来的GET_DESCRIPTOR请求准备一个大点的缓冲区。现在可以获得这个配置相关的所有描述符了。然后是对返回结果的检验,再然后就是将得到的那一堆数据的地址赋给rawdescriptors数组里的指针。

最后usb_parse_configuration函数将对前面GET_DESCRIPTOR请求获得的那堆数据做处理。

且让我们稍作休息,一根烟时间后再见,你觉得休息不够那就两支烟时间,如果还觉得不够,你就不要回来了。