科创园

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

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

Posted on 2014-03-31 18:22  科创园  阅读(2439)  评论(0编辑  收藏  举报

直接看代码吧。

 

这个函数的开头儿就要履行一下常规的检验,判断urb为空? Hcpriv是留给HCD用的, complete,每个urb结束了都必须的调用一次complete代表的函数。完成对urb本身的检验,接着对urb的目的地usb设备的检验。设备所属于的那条总线不存在,或者设备本身不存在,或者设备甚至还没进入USB_STATE_DEFAULT状态,管道的另一端还都是堵着的怎么过去,早先强调多少回了,要想让设备回应你,它起码得达到Default状态。设备编号devnum肯定是不能为负的了,那0为什么也不行那?你应该还记得Token包的地址域里有7位是表示设备地址的,也就是说总共可以有128个地址来分配给设备,但是其中0号地址是被保留作为缺省地址用的,任何一个设备处于Default状态还没有进入Address状态的时候都需要通过这个缺省地址来响应主机的请求,所以0号地址不能分配给任何一个设备,hub为设备选择一个地址的时候,只有选择到一个大于0的地址,设备的生命线才会继续,才会走到这里,因此说这里的devnum是不可能也不应该为0的。咱们看到这里是因为要设置设备的地址,让设备进入Address状态,所以针对SET_ADDRESS请求再看看这个devnum。主机向设备发送SET_ADDRESS请求时,如果设备处于Default状态,就是它现在处的状态,指定一个非0值时,设备将进入Adress状态,指定0值时,设备仍然会处于Default状态,所以说即使从这个角度看,这里的devnum也是不能为0的。如果设备已经处于Adress状态,指定一个非0值时,设备仍然会处于Address状态,只是将使用新分配的地址,一个设备只能占用一个地址。如果指定了一个0值,则设备将离开Address状态退回到Default状态。

power,power_state,event,还有PM_EVENT_ON都是电源管理核心里的东西,这里的目的是判断设备所在的那条总线的主机控制器有没有挂起,然后再判断设备本身是不是处于Suspended状态。

常规检查完,core和HCD已经认同了这个urb,就将它的状态设置为-EINPROGRESS,表示从现在开始urb的控制权就在core和HCD手里边儿了,驱动那里是看不到这个状态的。

actual_length这时还没开始传输,实际传输的数据长度当然为0了,这里初始化这么一下,也是为了防止以后哪里出错返回了,驱动里好检查。

接下来这几行获得管道的类型还有方向。

usb_pipecontrol函数,在设备进入Configured状态之前,主机只能使用控制传输,通过缺省管道,也就是管道0来和设备进行交流。

usb_maxpacket函数用来获得端点的wMaxPacketSize,这个函数不管从理论上还是实际上都是很简单的。咱们可以先问下自己,根据现有的信息如何获得一个端点的wMaxPacketSize?当然是必须得获得该端点的描述符,我们知道每个struct usb_device结构体里都有两个数组ep_out和ep_in保存了各个端点对应的struct usb_host_endpoint结构体,只要知道该端点对应了这两个数组里的哪个元素就可以获得它的描述符了,这就需要知道该端点的端点号和方向,而端点的方向就管道的方向,端点号也在pipe里保存有。你是不是会担心ep_out或ep_in数组都还空着,或者说没有保存对应的端点信息?倒不用担心它还是空的,即使是现在设备还刚从Powered走到Default,但是在使用usb_alloc_dev构造这个设备的struct usb_device的时候就把它里面端点0的struct usb_host_endpoint结构体ep0指定给ep_out[0]和ep_in[0]了,而且后来还对ep0的wMaxPacketSize指定了值。不过如果真的没有从它们里面找到想要的那个端点的信息,那肯定就是哪里出错了,指定了错误的端点号,或其它什么原因,也就不用再继续走了。

然而等时传输要做一些特别的处理,接下几行涉及到高速、高带宽端点(high speed,high bandwidth endpoint)。前面提到interval的时候,说过每一帧或微帧最多只能有一次等时传输,完成一次等时transaction,那时这么说主要是因为还没遇到高速高带宽的等时端点。高速高带宽等时端点每个微帧可以进行2到3次等时transaction,它和一般等时端点的主要区别也就在这儿,没必要专门为它搞个描述符类型,端点描述符wMaxPacketSize字段的bit 11~12就是用来指定可以额外有几次等时transaction的,00表示没有额外的transaction,01表示额外有1次,10表示额外有两次,11被保留。wMaxPacketSize字段的前10位就是实际的端点每次能够处理的最大字节数。所以这几行意思就是如果是高速等时端点,获得它允许的额外的等时transaction次数,和每次能够处理的最大字节数,再将它们相乘就得出了该等时端点每个微帧的所能传输的最大字节数。number_of_packets不大于0就表示这个等时urb没有指定任何一次等时传输,可以直接返回了。然后对等时urb里指定的各次等时传输分别做处理。如果它们预期传输的数据长度比上面算出来的max还要大,就return,同时将它们实际传输的数据长度先置为0,状态都先初始化为-EXDEV,表示这次等时传输仅仅部分完成了,实际到这里数据传输还没真正的开始。

判断transfer_buffer_length长度不能小于0。

#ifdef DEBUG,给人调试时用的。

跳过debug内容继续向下看。temp是上面计算出来的管道类型,那下面的各个case肯定是针对四种传输类型的了。不过我们可以发现,这里只case了等时传输和中断传输两种周期性的传输类型,原因很简单,我们需要对interval进行处理,所以就没控制传输和批量传输什么事儿了。必须保证等时和中断urb的interval是大于0的,然后代码中switch根据目的设备的速度去case三种。我们前面已经说过,不同的速度,urb->interval可以取不同的范围,不过你可能会发现那时说的最大值要比这里的限制要大一些,这是因为协议归协议,实现归实现,比如,对于UHCI来说,中断传输的interval不能比128更大,而协议规定的最大值为255。那么现在的问题是,这里的temp又做什么用?要注意urb->interval的单位是帧或者微帧,temp只是为了调整它的值为2的次幂,这点从下面的处理可以看出来。

while (temp > urb->interval)

temp >>= 1;

 

usb_hcd_submit_urb函数将urb扔给HCD,然后就进入HCD的片儿区了。

本来usb_submit_urb函数到此应该结束了,但是它对于写驱动的来说太重要了,驱动里做的所有铺垫就是为了使用usb_submit_urb提交一个合适的urb给设备,然后满怀期待的等待着设备回馈你需要的信息,再然后才有你接下来的处理,不然你的usb驱动只是一纸空谈毫无用处。

首先还是要再次强调一下,在调用usb_submit_urb提交你的urb之前,一定必须不得不要对它正确的初始化,对于控制/中断/批量传输,core都提供了usb_fill_control_urb的几个孪生兄弟供你初始化使用,对于等时传输要自己手工一步一步小心翼翼的对urb的相关元素逐个赋值。下层基础决定上层建筑,你的urb决定了你的整个usb驱动能否顺利运转。

第二,对于驱动来说,usb_submit_urb是异步的,也就是说不用等传输完全完成就返回了,只要usb_submit_urb的返回值表示为0,就表示已经提交成功了,你的urb已经被core和HCD认可了,接下来core和HCD怎么处理就是它们的事了。只要你提交成功了,不管是中间出了差错还是顺利完成,你指定的结束处理函数总是会调用,只有到这个时候,你才能够重新拿到urb的控制权,检查它是不是出错了,需要不需要释放或者是重新提交。

第三,什么时候需要在结束处理函数里重新提交这个urb?其实,我更想问的是对于中断/等时传输,是怎么实现让主机按一定周期去访问端点的?端点的描述符里已经指定了这个间隔时间是没错儿,urb里也有interval描述了这个间隔周期,更没错儿,可是咱们的urb一次只完成一次传输,即使等时传输也只完成有限次的传输,然后就在结束处理函数里返回了,urb的控制权就完全属于驱动了,接下来的周期访问是怎么做到的?难道脱离urb主机自己就去智能化的自动的与端点通信了?OK,即使是这样了,那通信的数据又在哪里,你又怎么去得到这些数据?事实上,你第一次提交一个中断或等时的urb的时候,HCD会根据interval判断一下自己是否能够满足你的需要,如果不能够安排足够的带宽来完成这种周期性的传输,它是不可能会批准你的请求的,如果它估量一下觉得自己可以满足,就会为你保留足够的带宽。HCD是为你保留带宽了,可是驱动得保证在对应端点要处理的那个urb队列里总是有urb,不能是空的,否则这个保留的带宽就会被cancel掉。那么问题就变成,对于中断/等时传输,如何保证对应端点的urb队列里总是会有urb?这就回到最开始的问题了,驱动需要在结束处理函数里重新初始化和提交刚刚完成的urb,友情提醒一下,这个时候你是不能够修改interval的值的,否则等待你的只能是错误。中断传输的例子可以去看看触摸屏驱动,等时传输的可以去看看摄像头驱动,看看它们在结束处理函数里都做了些什么,你就悟道了。

第四,对于控制/批量/中断传输,实际上很多时候你可以不用创建urb,不用对它初始化,不用调用usb_submit_urb来提交,core将这个过程分别封装在了usb_control_msg、usb_bulk_msg和usb_interrupt_msg这三个函数里,不同的是它们的实现是同步的,会去等待传输的完全结束

还记得咱们就是从usb_control_msg走过来的吗?有空可以去看看另外两个usb_bulk_msg和usb_interrupt_msg,它们都定义在drivers/usb/core/message.c