科创园

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

usb驱动开发22之驱动生命线

Posted on 2014-04-01 11:33  科创园  阅读(1592)  评论(0编辑  收藏  举报

我们总是很喜欢高潮,不是吗?那就好好对待她哦。我们来看一下linux中的高潮部分设备是怎么从Address进入Configured的。

usb_set_configuration函数的代码就不贴了,可以回顾内核去看。

usb_disable_device行函数主要意味着如果设备已经在Configured状态了,就得做些清理工作。都清理些什么怎么去清理?别着急,要想学会,得仔细研究下message.c里的usb_disable_device函数。

 

经过研究我们可以发现,usb_disable_device函数的清理工作主要有两部分,一是将设备里所有端点给disable掉,二是将设备当前配置使用的每个接口都从系统里给unregister掉,也就是将接口和它对应的驱动给分开。

先说下第二部分的工作,actconfig表示的是设备当前激活的配置,只有它不为空时才有接下来清理的必要。接下来的for循环就是将这个配置的每个接口从设备模型的体系中删除掉,将它们和对应的接口驱动分开,没有驱动了,这些接口也就丧失了能力,当然也就什么作用都发挥不了了,这也是名字里那个disable的真正含意所在。在最后一个for循环将actconfig的interface数组置为空。然后再将actconfig置为空,这里你可能会有的一个问题是,为什么只是置为空,既然要清理actconfig,为什么不直接将它占用的内存给释放掉?这个问题问的好,说明你足够细心,不过你应该注意到actconfig只是一个指针,一个地址,你应该首先弄清楚这个地址里保存的是什么东西再决定是不是将它给释放掉,那这个指针指向哪儿?它指向设备struct usb_device结构的config数组里的其中一项,当前被激活的是哪一个配置,它就指向config数组里的哪一项,你这里只是不想让设备当前激活任何一个配置而已,没必要将actconfig指向的那个配置给释放掉吧,前面在设备生命线那里走了那么久,历尽千辛万苦才将设备各个配置的内容给拿过来放到config数组里,你这里如果给释放掉,对得起谁啊,岂不要哭死。那这么说的话另一个问题就出来了,既然actconfig指向了config数组里的一项,那为什么要把那个配置的interface数组给置为空,这不是修改了那个配置的内容,从而也修改了config数组的内容么?你先别着急,俺帮你回忆一下,在设备生命线那里取配置描述符的,解析返回的那堆数据时,只是把每个配置里的cache数组,也就是intf_cache数组给初始化了,并没有为interface数组充实任何的内容,这里做清理工作的目的就是要恢复原状,当然要将它置为空了,那么配置的interface数组又在哪里被充实了那?usb_set_configuration函数里第二个高潮阶段之后不是还有个第三个阶段么,就在那里,你那时激活了哪个配置,就为哪个配置的interface数组动手术,填点东西。最后如果这个设备此时确实是在Configured状态,就让它回到Address。

现在回头来说说第一部分的清理工作。这个部分主要就是为每个端点调用了usb_disable_endpoint函数,将挂在它们上面的urb给取消掉。为什么要这么做?你想想,能调用到usb_disable_device这个函数,一般来说设备的状态要发生变化了,设备的状态都改变了,那设备的那些端点的状态要不要改变?还有挂在它们上面的那些urb需不需要给取消掉?这些都是很显然的事情,就拿现在让设备从Configured回到Address来说吧,在Address的时候,你只能通过缺省管道也就是端点0对应的管道与设备进行通信的,但是在Configured的时候,设备的所有端点都是能够使用的,它们上面可能已经挂了一些urb正在处理或者将要处理,那么你这时让设备要从Configured变到Address,是不是应该先将这些urb给取消掉?还有个问题是参数skip_ep0是嘛意思?这里for循环的i是从skip_ep0开始算起,也就是说skip_ep0为1的话,就不需要对端点0调用usb_disable_endpoint函数了,按常理来说,设备状态改变了,是需要把每个端点上面的urb给取消掉的,这里面当然也要包括端点0,但是写代码的哥们儿这里搞出个skip_ep0自然有他们的玄机,蓦然回首一下,usb_set_configuration()调用这个函数的时候参数skip_ep0的值是什么?是1,因为这时候是从Configured回到Address,这个过程中,其它端点是从能够使用变成了不能使用,但端点0却是一直都很强势,虽说是设备发生了状态的变化,但在这两个状态里它都是要正常使用的,所以就没必要disable它了。什么时候需要disable端点0?目前版本(linux2.6)的内核里俺只发现了两种情况,一是设备要断开的时候,二是设备从Default进化到Address的时候,虽说不管是Default还是Address,端点0都是需要能够正常使用的,但因为地址发生改变了,毫无疑问,你需要将挂在它上面的urb清除掉。俺当时讲设备生命线的时候,在设置完设备地址,设备进入Address后,第二种情况的这个步骤给有意无意的飘过了,主要是当时也不影响理解,现在既然遇到了,就把它给补上吧。在设备生命线的那个过程中,设置完设备地址,让设备进入Address状态后,立马就调用了hub.c里一个名叫ep0_reinit的函数。ep0_reinit函数里只对端点0调用了usb_disable_endpoint(),但是端点0接下来还是要使用的,不然你就取不到设备那些描述符了,所以接着重新将ep0赋给ep_in[0]和ep_out[0]。多说无益,还是回到第二阶段的第一部分usb_disable_endpoint()函数里面去看看吧。

 

这个函数先获得端点号和端点的方向,然后从ep_in或ep_out两个数组里取出端点的struct usb_host_endpoint结构体,并将数组里的对应项置为空,要注意的是这里同样不是释放掉数组里对应项的内存而是置为空。这两个数组里的ep_in[0]和ep_out[0]是早就被赋值了,至于剩下的那些项是在什么时候被赋值的,又是指向了什么东西,就是usb_set_configuration函数第三个阶段的事。最后调用了一个usb_hcd_endpoint_disable函数,主要的工作还得它来做,不过这已经深入HCD的腹地了,就不多说了。

飘回usb_disable_device()吧。在为每个端点都调用了usb_disable_endpoint()之后,还有一个小步骤要做,就是将设备struct usb_device结构体的toggle数组置为0。至于toggle数组干吗的,为啥要被初始化为0,你还是蓦然回首到设备那节去看吧。

接着飘回usb_set_configuration()了。

又一次与熟悉的陌生人usb_control_msg()相遇了,每当我们需要向设备发送请求的时候它就会适时的出现,usb_control_msg这次出现的目的当然是为了SET_CONFIGURATION请求,这里只说一下它的那堆参数,看一下spec 9.4.7的那张表。

clip_image002

SET_CONFIGURATION请求不需要DATA transaction,而且还是协议里规定所有设备都要支持的标准请求,也不是针对端点或者接口什么的,而是针对设备的,所以bRequestType只能为0x80,就是上面表里的00000000B,也就是参数中第一个0,wValue表示配置的bConfigurationValue,也就是参数中configuration。

然后将激活的那个配置的地址赋给actconfig。如果cp为空,重新设置设备的状态为Address,并将之前准备的那些struct usb_interface结构体和new_interfaces释放掉,然后返回。扫一下前面的代码,cp有三种可能为空,一是参数configuration为-1,一是参数configuration为0,且从设备的config数组里拿出来的就为空,一是SET_CONFIGURATION请求出现了问题。

usb_set_device_state(dev, USB_STATE_CONFIGURED);行代码,表示事情在这里发展达到了高潮的顶端,设置设备的状态为Configured。

设备自从有了Address,拿到了各种描述符,就在那儿看usb_generic_driver忙活了,不过还算没白忙,设备总算是幸福的进入Configured了。从设备这儿咱们应该学到点幸福生活的秘诀,就是找到你所喜欢的事,然后找到愿意为你来做这件事的人。

Address有点像你合几代人之力辛辛苦苦才弄到的一套新房子,如果不装修,它对你来说的意义也就是你的户口可以从人才中心挪过去了,可以对人说你的地址是哪儿哪儿了,可实际上你在里边儿什么也干不了,你还得想办法去装修它,Configured就像是已经装修好的,装修好了,你和你老婆才能够在里边儿想干做什么就干什么,下面咱就看看usb_generic_driver又对设备做了些什么。

这个for循环的nintf就是配置里接口的数目,那么这个for循环显然就是在对配置里的每个接口做处理。这里要明白的是,cp里的两个数组interface和intf_cache,一个是没有初始化的,一个是已经动过手术很饱满的。正像前面提到的,这里需要为interface动手术,拿intf_cache的内容去充实它。继续看这行usb_altnum_to_altsetting()函数代码,获得这个接口的0号设置。咱们已经知道,因为某些厂商有特殊的癖好,导致了struct usb_host_config结构里的数组interface并不一定是按照接口号的顺序存储的,你必须使用usb_ifnum_to_if来获得指定接口号对应的struct usb_interface结构体。现在咱们需要再多知道一点,接口里的altsetting数组也不一定是按照设置编号来顺序存储的,你必须使用usb_altnum_to_altsetting()来获得接口里的指定设置。它在usb.c里定义()

 

这个函数依靠一个for循环来解决问题,就是轮询接口里的每个设置,比较它们的编号与你指定的编号是不是一样。原理简单,操作也简单,有点不简单的是前面调用它的时候为什么指定编号0,也就是获得0号设置。这还要归咎于spec里说了这么一句,接口的默认设置总是0号设置,所以这里的目的就是获得接口的默认设置,如果没有拿到设置0,接下来就拿altsetting数组里的第一项来充数,alt = &intf->altsetting[0];就是把 &intf->altsetting[0]赋给了alt。

前边儿遇到过device和endpoint的disable函数,这里遇到个interface的enable函数,同样在message.c里定义。这个函数同样也是靠一个for循环来解决问题,轮询前面获得的那个接口设置使用到的每个端点,调用message.c里的usb_enable_endpoint()将它们统统enable。

 

usb_enable_endpoint这个函数的前边儿几行分别获得端点地址,端点号,还有是不是控制端点。稍微有那么点嚼头儿的是后面的两个if,它们分别根据端点的方向来初始化设备的ep_in和ep_out数组,还有就是调用了include/linux/usb.h里的一个叫usb_settoggle的宏。

/* The D0/D1 toggle bits ... USE WITH CAUTION (they're almost hcd-internal) */

#define usb_gettoggle(dev, ep, out) (((dev)->toggle[out] >> (ep)) & 1)

#define usb_dotoggle(dev, ep, out) ((dev)->toggle[out] ^= (1 << (ep)))

#define usb_settoggle(dev, ep, out, bit) \

((dev)->toggle[out] = ((dev)->toggle[out] & ~(1 << (ep))) | \

((bit) << (ep)))

这三个宏都是用来处理端点的toggle位的,也就是struct usb_device里的数组toggle[2]。toggle[0]对应的是IN端点,toggle[1]对应的是OUT端点,上面宏参数里的out用来指定端点是IN还是OUT,ep指的不是端点的结构体,而仅仅是端点号。usb_gettoggle用来得到端点对应的toggle值,usb_dotoggle用来将端点的toggle位取反,usb_settoggle看起来就要复杂点,意思是如果bit为0,则将ep 所对应的那个toggle 位reset 成0,如果bit为1,则reset为1。((dev)->toggle[out] & ~(1<<(ep)))就是把1 左移ep 位,比如ep 为3,那么就是得到了1000,然后取反,得到0111,(当然高位还有更多个1),然后(dev)->toggle[out]和0111 相与,这就是使得toggle[out]的第3 位清零而其它位都不变。咱们这里调用usb_settoggle的时候,bit传递的是0,用来将端点的toggle位清零,原因早先也提到过,就是对于批量传输、控制传输和中断传输来说,数据包最开始都是被初始化为DATA0的,然后才一次传DATA0,一次传DATA1。

现在看看为什么两个if语句里都出现有is_control。控制传输使用的是message管道,message管道必须对应两个相同号码的端点,一个用来in,一个用来out。这里使用两个if,而不是if-else组合,目的就是塞进去一个is_control,表示只要是控制端点,就将它的端点号对应的IN和OUT两个方向上的toggle位还有ep_int和ep_out都给初始化了。当然,所谓的控制端点一般也就是指端点0。

endpoint的enable函数要比disable函数清爽的多,disable的时候还要深入到HCD的腹地去撤销挂在它上面的各个urb,而enable的时候就是简单设置一下toggle位还有那两个数组就好了,要知道它的urb队列urb_list是早在从设备那里获取配置描述符并去解析那一大堆数据的时候就初始化好了的。enable之后,接口里的各个端点便都处于了欣欣向荣的可用状态,你就可以在驱动里向指定的端点提交urb了。当然,到目前这个时候接口还仍然是接口,驱动(接口驱动)还仍然是驱动,它们中间还缺少那根著名的红线。

再回头看usb_set_configuration函数里usb_enable_interface()后面的那几行,接口所属的总线类型仍然为usb_bus_type,设备类型变为usb_if_device_type,dma_mask被设置为你的设备的dma_mask,而你设备的dma_mask很早以前就被设置为了host controller的dma_mask。device_initialize在初始化设备struct usb_device结构体的时候遇到过一次,这里初始化接口的时候又遇到了。mark_quiesced将接口的is_active标志初始化为0,表示它还没有与任何驱动绑定,就是还没有找到另一半儿。

现在这个for循环结束了,new_interfaces的历史使命也就结束了。我想你应该会明白的是这里的kfree释放的只是new_interfaces指针数组的内存,而不包括它里面各个指针所指向的内存,至于那些数据,都已经在前面被赋给配置里的interface数组了。

然后usb_cache_string获得配置的字符串描述符,怎么获得?下一节单独说字符串描述符。

先把最后一个for循环结束,也就是usb_set_configuration()的第三个阶段。这个for循环将前面那个for循环准备好的每个接口送给设备模型,Linux设备模型会根据总线类型usb_bus_type将接口添加到usb总线的那条有名的设备链表里,然后去轮询usb总线的另外一条有名的驱动链表,针对每个找到的驱动去调用usb总线的match函数,也就是usb_device_match,去为接口寻找另一个匹配的半圆。你说这个时候设备和接口两条路它应该走哪条?它的类型已经设置成usb_if_device_type了,设备那条路把门儿的根本就不会让它进,所以它必须得去走接口那条路。