分类: LINUX linux urb处理 谨以此文纪念过往的岁月 一.前言 在前文中讲述了urb的申请以及提交,在更前之时讲述过hcd的驱动,那么在该文中将这两者结合起来,具体来讲述urb是如何实现的。 二.OHCI简介 在usb系统中可以分为四层1.客户端软件或usb驱动2.主机驱动3.主机控制器4.usb设备。其中第一和第二层属于软件,而第三和第四属于硬件,其中OHCI就是将第二层和第三层联系起来,其定义了一组寄存器和一系列的操作集。关于usb传输的数据类型就不多说了。 OHCI有两个传输通道一个就是位于HC中的一系列寄存器,而第二个就是HCCA,其包括指向中断ed链表的指针。第一个通道用于bulk和control传输,而HCCA则用于interrupt和iso传输。为什么要这样区分呢,因为在usb传输的数据中可以分为周期传输和非周期传输,其中bulk和control属于非周期传输,而int和iso则属于周期传输。也许大家会疑惑这两者是具体如何传输的。下图比较好的看出一帧时间是如何分配的。 在一帧数据时间一般为1ms中,在HC发送SOF同步usb bus后,会传输非周期性数据包,那在什么时候会传输周期性数据呢,在frame interval计数器达到HCD设定的值后就会发送周期性数据。在周期性数据发送完成后,继续发送非周期性数据。关于其中周期性数据发送的起始时间由HcPeriodicStart寄存器来决定,一般是保留整个frame的90%。如果在发送周期性数据完成后还是时间,则继续发送非周期数据。 在HcPeriodicStart的寄存器说明中说明,当HcFmRemaining与HcPeriodicStart值相同时,周期性链表的处理优先级大于非周期性链表的发送。其中HcFmRemaining是从HcFmInterval值开始减少的。这个就可以理解为什么在程序HcPeriodicStart的值等于90%的HcFmInterval。那关于周期性链表是如何链接的以及不同的轮询时间的端点是如何工作的,在具体看源码时涉及。 在OHCI的处理中需要理解ed和td的含义,以及具体的处理办法。关于ed和td的概图如下: ed是endpoint descriptor简称,对于每一个端点都会有一个ed用于描述。其通用的数据格式如下: 32111111100000000 Dword 0—MPSFKSDENFA Dword 1TD Queue Tail Pointer (TailP)— Dword 2TD Queue Head Pointer (HeadP)0CH Dword 3Next Endpoint Descriptor (NextED)— td是transfer descriptor简称。其通用的数据格式如下: 3222222221100 1876543109830 Dword 0CCECTDIDPR— Dword 1Current Buffer Pointer (CBP) Dword 2Next TD (NextTD)0 Dword 3Buffer End (BE) 以上两个数据结构的具体含义见OHCI协议。 三.OHCI的实现 上文看到urb入队后就没有继续看,在本文中继续来urb是如何华丽的转身成为td和ed的。在OHCI中是调用ohci_urb_enqueue实现urb的入队。 { struct ohci_hcd*ohci = hcd_to_ohci (hcd); struct ed*ed; urb_priv_t*urb_priv; unsigned intpipe = urb->pipe; inti, size = 0; unsigned longflags; intretval = 0; if (! (ed = ed_get (ohci, urb->ep, urb->dev, pipe, urb->interval))) --获取每一个端点的ed,如果该端点还没有ed,则新建一个ed。 return -ENOMEM; switch (ed->type) { case PIPE_CONTROL: if (urb->transfer_buffer_length > 4096) –不支持控制类型的传输数据大于4096个字节 return -EMSGSIZE; size = 2;--一个setup td,一个ACK td再加上其他。 default: size += urb->transfer_buffer_length / 4096;--每一个td最大支持4096个字节,size最大到8k if ((urb->transfer_buffer_length % 4096) != 0) –剩余的数据长度 size++; if (size == 0)--创建一个空包 size++; else if ((urb->transfer_flags & URB_ZERO_PACKET) != 0 && (urb->transfer_buffer_length% usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe))) == 0) size++; break; case PIPE_ISOCHRONOUS: --size直接就为iso的包数 size = urb->number_of_packets; break; } urb_priv = kzalloc (sizeof (urb_priv_t) + size * sizeof (struct td *),mem_flags); --分配urb的私有数据空间 if (!urb_priv) return -ENOMEM; INIT_LIST_HEAD (&urb_priv->pending); urb_priv->length = size; urb_priv->ed = ed; for (i = 0; i < size; i++) { urb_priv->td [i] = td_alloc (ohci, mem_flags); --分配td if (!urb_priv->td [i]) { urb_priv->length = i; urb_free_priv (ohci, urb_priv); return -ENOMEM; } } spin_lock_irqsave (&lock, flags); if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, flags)) { --检测HC是否存在和运行 retval = -ENODEV; goto fail; } if (!HC_IS_RUNNING(hcd->state)) { retval = -ENODEV; goto fail; } if (retval) goto fail; if (ed->state == ED_IDLE) {--如果ed的状态是空闲的则调度 if (retval < 0) { usb_hcd_unlink_urb_from_ep(hcd, urb); goto fail; } if (ed->type == PIPE_ISOCHRONOUS) { u16frame = ohci_frame_no(ohci); frame += max_t (u16, 8, ed->interval); --在第一个td推迟几个frame frame &= ~(ed->interval - 1); frame |= ed->branch; urb->start_frame = frame; } } else if (ed->type == PIPE_ISOCHRONOUS) urb->start_frame = ed->last_iso + ed->interval; urb->hcpriv = urb_priv; td_submit_urb(ohci, urb); fail: if (retval) urb_free_priv (ohci, urb_priv); spin_unlock_irqrestore (&lock, flags); return retval; } 在上述的函数中会涉及几个重要的函数如ed_get, usb_hcd_link_urb_to_ep, ed_schedule和td_submit_urb。关于ed和td的处理即将在这几个函数中实现。下面还是一个一个来看这些函数,不过单独去看某一个函数是不现实的在其中还会掺杂其他内容。 static struct ed *ed_get (struct ohci_hcd *ohci,struct usb_host_endpoint *ep, struct usb_device*udev,unsigned intpipe,intinterval) { struct ed*ed; unsigned longflags; spin_lock_irqsave (&lock, flags); if (!(ed = ep->hcpriv)) {--每一个endpoint的ed存储在hcpriv中 struct td*td; intis_out; u32info; ed = ed_alloc (ohci, GFP_ATOMIC);--从数据池中开辟一个ed。 if (!ed) { goto done; } td = td_alloc (ohci, GFP_ATOMIC); --开辟一个空闲的td,用于挂载 if (!td) { ed_free (ohci, ed); ed = NULL; goto done; } ed->dummy = td;--这个成员函数名不副实,从函数名上看这个成员没有什么用,其实是下一个需要的运行的td ed->hwTailP = cpu_to_hc32 (ohci, td->td_dma); ed->hwHeadP = ed->hwTailP; ed->state = ED_IDLE; is_out = !(ep->desc.bEndpointAddress & USB_DIR_IN); info = usb_pipedevice (pipe);--info存储ep的种种信息 ed->type = usb_pipetype(pipe); info |= (ep->desc.bEndpointAddress & ~USB_DIR_IN) << 7; info |= le16_to_cpu(ep->desc.wMaxPacketSize) << 16; if (udev->speed == USB_SPEED_LOW) info |= ED_LOWSPEED; if (ed->type != PIPE_CONTROL) { --control类型只有OUT没有IN info |= is_out ? ED_OUT : ED_IN; if (ed->type != PIPE_BULK) { --唯有int和iso需要interval即轮询时间 if (ed->type == PIPE_ISOCHRONOUS) info |= ED_ISO; else if (interval > 32)— iso可以更长,而中断的int最大则为32ms,所以即使在填充urb时interval为100,其实最终还是变成了32ms interval = 32; ed->interval = interval; udev->speed, !is_out, ed->type == PIPE_ISOCHRONOUS, le16_to_cpu(ep->desc.wMaxPacketSize))/ 1000; } } ed->hwINFO = cpu_to_hc32(ohci, info); ep->hcpriv = ed; } done: spin_unlock_irqrestore (&lock, flags); return ed; } 在上面的函数中先挖个坑,就是usb_calc_bus_time,这个函数咱们并没有说其干什么用的。此处不表,留待下文分解 int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb) { intrc = 0; spin_lock(&hcd_urb_list_lock); switch (hcd->state) { case HC_STATE_RUNNING: case HC_STATE_RESUMING: urb->unlinked = 0; list_add_tail(urb_list, ep->urb_list); --将urb添加到ep的urb链表中。关于此物何用留待以后肥了杀就是urb完成使命后删除用。 break; default: rc = -ESHUTDOWN; goto done; } done: spin_unlock(&hcd_urb_list_lock); return rc; } 当ed空闲时,咱应该让他松松筋骨活动活动,就是ed_schedule就来了。 { intbranch; ed->state = ED_OPER; --改变ed状态 ed->ed_prev = NULL; ed->ed_next = NULL; ed->hwNextED = 0; switch (ed->type) { case PIPE_CONTROL: --控制传输 if (ohci->ed_controltail == NULL) { ohci_writel (ohci, ed->dma,?s->ed_controlhead); --将当前的ed物理地址赋给ed_controlhead的寄存器,也许大家就好奇了这个寄存器有什么用,不急不急后文道来。 } else { ohci->ed_controltail->hwNextED = cpu_to_hc32 (ohci,ed->dma); } ed->ed_prev = ohci->ed_controltail; --记住ed是一个双向链表,如果这里有人问什么是双向链表,那这儿我就建议您可以就此打住,先回去看看其他的吧。 if (!ohci->ed_controltail && !ohci->ed_rm_list) { --如果control链表和移除链表都是空的,ok,那就是control传输并没有使能,那咱先使能control传输。 wmb(); ohci->hc_control |= OHCI_CTRL_CLE; ohci_writel (ohci, 0, ?s->ed_controlcurrent); ohci_writel (ohci, ohci->hc_control,?s->control); } ohci->ed_controltail = ed; --将tail向后移 break; case PIPE_BULK: --块传输,其实现原理同控制传输。 if (ohci->ed_bulktail == NULL) { ohci_writel (ohci, ed->dma, ?s->ed_bulkhead); } else { ohci->ed_bulktail->ed_next = ed; ohci->ed_bulktail->hwNextED = cpu_to_hc32 (ohci,ed->dma); } ed->ed_prev = ohci->ed_bulktail; if (!ohci->ed_bulktail && !ohci->ed_rm_list) { wmb(); ohci->hc_control |= OHCI_CTRL_BLE; ohci_writel (ohci, 0, ?s->ed_bulkcurrent); ohci_writel (ohci, ohci->hc_control,?s->control); } ohci->ed_bulktail = ed; break; default:--针对中断传输和等时传输,其传输机制与以上两个不同。 branch = balance (ohci, ed->interval, ed->load); if (branch < 0) { return branch; } ed->branch = branch; periodic_link (ohci, ed); } return 0; } 到此先打住一下,我们到此不得不去面对一个问题就是四种传输的区别。对于非周期性传输即控制和块传输是利用每一帧的空闲时间传输。而对于周期性传输,这里面就有讲究了,因为不同的urb就会有不同的轮询时间。那是如何分配带宽的呢? 周期性传输时采用的第二中通道就是HCCA的方式。对于周期性传输的周期有1,2,4,8,16,32ms这几种,其中iso传输一般是1ms,而中断传输则有前面的几种周期。也许大家都看过下面的这张图,当时对于这张图的实现很困惑。 上述图仅仅是一个示意图,而且这张图也比较难懂。其实如果大家不看这张图而是看HCCA中HccaInterrruptTable的说明则会好理解很多。我们来参照linux中关于HCCA的定义就更好理解了,在OHCI中还有一个periodic成员是int_table的影子,其也是一个大小为32的数组。 #define NUM_INTS 32 struct ohci_hcca { __hc32frame_no;--当前的frame number __hc32done_head; u8reserved_for_hc [116]; u8what [4]; } __attribute__ ((aligned(256))); 其中我们更加关注int_table,记住int_table的大小是32个。简单讲吧,在OHCI的周期性传输中以32ms为一个大周期,对于以1ms为轮询周期的ed,则会挂载在32个peridic中,就是在32ms的大周期中,发送32次,实现1ms的轮询周期,而2ms的则会挂载到16个的periodic中,就是每隔1ms就发送一次,在32ms中发送16次,实现2ms的轮询时间。而对于32ms的,则就就挂载在peridic中的某一个,在32ms中发送一次。这样就可以理解为什么轮询时间只有1ms,2ms,4ms,8ms,16ms,32ms了。关于其具体的实现咱们来看源码。 Balance函数从函数名上来看就是平衡的意思,那什么是平衡呢,咱们来想想比如32ms轮询一次的周期性传输,那这个ed要挂到那个链表下呢,这个就涉及到带宽的分配的,比如第一二的1ms周期的带宽几乎都快分完了,而第31,32的1ms周期的带宽几乎还没有怎么分配,那我总不成还死皮赖脸的把这个ed挂载第一二的1ms周期下吧,毕竟人家的资源都快完了。咱要合理的利用资源,咱就挂到第31,32的1ms周期下啊!ohci->load嘛就是存储了资源的分配情况,也是一个大小32的数组。其实在32ms的大周期中每一个小周期都会有load对应来表明有多少带宽被分配了,返回的是添加的其实端点。 static int balance (struct ohci_hcd *ohci, int interval, int load) { inti, branch = -ENOSPC; if (interval > NUM_INTS) interval = NUM_INTS; for (i = 0; i < interval ; i++) { if (branch < 0 || ohci->load [branch] > ohci->load [i]) { intj; for (j = i; j < NUM_INTS; j += interval) { break; } if (j < NUM_INTS) continue; branch = i; } } return branch; } 在periodic_link中就是将ed连接进链表中,实现比较简单就是链表的添加,这里就不再讲了。 那下面就来看td的传输。 static void td_submit_urb (struct ohci_hcd*ohci,struct urb*urb) { struct urb_priv*urb_priv = urb->hcpriv; dma_addr_tdata; intdata_len = urb->transfer_buffer_length; intcnt = 0; u32info = 0; intis_out = usb_pipeout (urb->pipe); intperiodic = 0; if (!usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), is_out)) { usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe),is_out, 1); urb_priv->ed->hwHeadP &= ~cpu_to_hc32 (ohci, ED_C); } urb_priv->td_cnt = 0; list_add (&urb_priv->pending, &pending); if (data_len) data = urb->transfer_dma; else data = 0; switch (urb_priv->ed->type) { case PIPE_INTERRUPT: periodic = ohci_to_hcd(ohci)->self.bandwidth_int_reqs++ == 0 && ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0; case PIPE_BULK: info = is_out? TD_T_TOGGLE | TD_CC | TD_DP_OUT : TD_T_TOGGLE | TD_CC | TD_DP_IN; while (data_len > 4096) { td_fill (ohci, info, data, 4096, urb, cnt);--填充td data += 4096; data_len -= 4096; cnt++; } if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) info |= TD_R; td_fill (ohci, info, data, data_len, urb, cnt); cnt++; if ((urb->transfer_flags & URB_ZERO_PACKET)&& urb_priv->length) { td_fill (ohci, info, 0, 0, urb, cnt); cnt++; } if (urb_priv->ed->type == PIPE_BULK) { wmb (); ohci_writel (ohci, OHCI_BLF, ?s->cmdstatus); } break; case PIPE_CONTROL: info = TD_CC | TD_DP_SETUP | TD_T_DATA0; td_fill (ohci, info, urb->setup_dma, 8, urb, cnt++); if (data_len > 0) { info = TD_CC | TD_R | TD_T_DATA1; info |= is_out ? TD_DP_OUT : TD_DP_IN; td_fill (ohci, info, data, data_len, urb, cnt++); } info = (is_out || data_len == 0)? TD_CC | TD_DP_IN | TD_T_DATA1 : TD_CC | TD_DP_OUT | TD_T_DATA1; td_fill (ohci, info, data, 0, urb, cnt++); wmb (); ohci_writel (ohci, OHCI_CLF, ?s->cmdstatus); break; case PIPE_ISOCHRONOUS: for (cnt = 0; cnt < urb->number_of_packets; cnt++) { intframe = urb->start_frame; frame += cnt * urb->interval; frame & td_fill (ohci, TD_CC | TD_ISO | frame, data + urb->iso_frame_desc [cnt].offset, urb->iso_frame_desc [cnt].length, urb, cnt); } if (ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0 && quirk_amdiso(ohci)) quirk_amd_pll(0); periodic = ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs++ == 0 && ohci_to_hcd(ohci)->self.bandwidth_int_reqs == 0; break; } if (periodic) { wmb (); ohci->hc_control |= OHCI_CTRL_PLE|OHCI_CTRL_IE; ohci_writel (ohci, ohci->hc_control, ?s->control); } } 上面的函数会涉及到具体的硬件操作,不过其中有一个函数很重要就是td_fill,填充td。关于其具体的实现各位可以去看源码,在此不再讲述。 三.总结 在本文中主要讲述urb到ed和td的转换还有OHCI的调度。 阅读(2017) | 评论(2) | 转发(2) | 0 上一篇:
|
|