Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3044191
  • 博文数量: 396
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4209
  • 用 户 组: 普通用户
  • 注册时间: 2016-07-04 13:04
文章分类

全部博文(396)

文章存档

2022年(1)

2021年(2)

2020年(8)

2019年(24)

2018年(135)

2017年(158)

2016年(68)

我的朋友

分类: 嵌入式

2016-10-09 09:22:38

谨以此文纪念过往的岁月

一.  前言

在前文中讲述了urb的申请以及提交,在更前之时讲述过hcd的驱动,那么在该文中将这两者结合起来,具体来讲述urb是如何实现的。

二.OHCI简介

usb系统中可以分为四层1.客户端软件或usb驱动2.主机驱动3.主机控制器4.usb设备。其中第一和第二层属于软件,而第三和第四属于硬件,其中OHCI就是将第二层和第三层联系起来,其定义了一组寄存器和一系列的操作集。关于usb传输的数据类型就不多说了。

OHCI有两个传输通道一个就是位于HC中的一系列寄存器,而第二个就是HCCA,其包括指向中断ed链表的指针。第一个通道用于bulkcontrol传输,而HCCA则用于interruptiso传输。为什么要这样区分呢,因为在usb传输的数据中可以分为周期传输和非周期传输,其中bulkcontrol属于非周期传输,而intiso则属于周期传输。也许大家会疑惑这两者是具体如何传输的。下图比较好的看出一帧时间是如何分配的。

在一帧数据时间一般为1ms中,在HC发送SOF同步usb bus后,会传输非周期性数据包,那在什么时候会传输周期性数据呢,在frame interval计数器达到HCD设定的值后就会发送周期性数据。在周期性数据发送完成后,继续发送非周期性数据。关于其中周期性数据发送的起始时间由HcPeriodicStart寄存器来决定,一般是保留整个frame90%。如果在发送周期性数据完成后还是时间,则继续发送非周期数据。

HcPeriodicStart的寄存器说明中说明,当HcFmRemainingHcPeriodicStart值相同时,周期性链表的处理优先级大于非周期性链表的发送。其中HcFmRemaining是从HcFmInterval值开始减少的。这个就可以理解为什么在程序HcPeriodicStart的值等于90%HcFmInterval。那关于周期性链表是如何链接的以及不同的轮询时间的端点是如何工作的,在具体看源码时涉及。

OHCI的处理中需要理解edtd的含义,以及具体的处理办法。关于edtd的概图如下:

edendpoint descriptor简称,对于每一个端点都会有一个ed用于描述。其通用的数据格式如下:

 

3

 

 

 

2

 

 

 

 

 

 

 

 

 

1

1

1

1

1

1

1

 

0

0

0

0

0

0

0

0

Dword 0

MPS

F

K

S

D

EN

FA

Dword 1

TD Queue Tail Pointer (TailP)

Dword 2

TD Queue Head Pointer (HeadP)

0

C

H

Dword 3

Next Endpoint Descriptor (NextED)

tdtransfer descriptor简称。其通用的数据格式如下:

 

3

 

2

2

2

2

2

2

 

2

2

1

1

 

 

 

 

 

 

 

 

 

 

0

 

0

 

1

 

8

7

6

5

4

3

 

1

0

9

8

 

 

 

 

 

 

 

 

 

 

3

 

0

Dword 0

CC

EC

T

DI

DP

R

Dword 1

Current Buffer Pointer (CBP)

Dword 2

Next TD (NextTD)

0

Dword 3

Buffer End (BE)

以上两个数据结构的具体含义见OHCI协议。

.OHCI的实现

   上文看到urb入队后就没有继续看,在本文中继续来urb是如何华丽的转身成为tded的。在OHCI中是调用ohci_urb_enqueue实现urb的入队。

static int ohci_urb_enqueue (struct usb_hcd      *hcd,struct urb    *urb,gfp_t mem_flags                      )

 {

       struct ohci_hcd   *ohci = hcd_to_ohci (hcd);

       struct ed       *ed;

       urb_priv_t    *urb_priv;

       unsigned int pipe = urb->pipe;

       int          i, size = 0;

       unsigned long      flags;

       int          retval = 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 (&ohci->lock, flags);

 

       if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { --检测HC是否存在和运行

              retval = -ENODEV;

              goto fail;

       }

       if (!HC_IS_RUNNING(hcd->state)) {

              retval = -ENODEV;

              goto fail;

       }

       retval = usb_hcd_link_urb_to_ep(hcd, urb); --urb连接到ep

       if (retval)

              goto fail;

       if (ed->state == ED_IDLE) {   --如果ed的状态是空闲的则调度

              retval = ed_schedule (ohci, ed);

              if (retval < 0) {

                     usb_hcd_unlink_urb_from_ep(hcd, urb);

                     goto fail;

              }

              if (ed->type == PIPE_ISOCHRONOUS) {

                     u16 frame = 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 (&ohci->lock, flags);

       return retval;

}

在上述的函数中会涉及几个重要的函数如ed_get, usb_hcd_link_urb_to_ep, ed_scheduletd_submit_urb。关于edtd的处理即将在这几个函数中实现。下面还是一个一个来看这些函数,不过单独去看某一个函数是不现实的在其中还会掺杂其他内容。

static struct ed *ed_get (struct ohci_hcd *ohci,struct usb_host_endpoint *ep,

                        struct usb_device *udev,unsigned int     pipe,int  interval)

{

       struct ed              *ed;

       unsigned long             flags;

 

       spin_lock_irqsave (&ohci->lock, flags);

 

       if (!(ed = ep->hcpriv)) {  --每一个endpointed存储在hcpriv

              struct td *td;

              int          is_out;

              u32        info;

 

              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) { --唯有intiso需要interval即轮询时间

                            if (ed->type == PIPE_ISOCHRONOUS)

                                   info |= ED_ISO;

                            else if (interval > 32)— iso可以更长,而中断的int最大则为32ms,所以即使在填充urbinterval100,其实最终还是变成了32ms

                                   interval = 32;

                            ed->interval = interval;

                            ed->load = usb_calc_bus_time (   --这个函数的定义在usb2.0中。计算一帧所需要的ns时间。

                                   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 (&ohci->lock, flags);

       return ed;

}

在上面的函数中先挖个坑,就是usb_calc_bus_time,这个函数咱们并没有说其干什么用的。此处不表,留待下文分解

int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb)

{

       int          rc = 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->urb_list, &urb->ep->urb_list); --urb添加到epurb链表中。关于此物何用留待以后肥了杀就是urb完成使命后删除用。

              break;

       default:

              rc = -ESHUTDOWN;

              goto done;

       }

 done:

       spin_unlock(&hcd_urb_list_lock);

       return rc;

}

ed空闲时,咱应该让他松松筋骨活动活动,就是ed_schedule就来了。

static int ed_schedule (struct ohci_hcd *ohci, struct ed *ed)

{

       int   branch;

 

       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,&ohci->regs->ed_controlhead); --将当前的ed物理地址赋给ed_controlhead的寄存器,也许大家就好奇了这个寄存器有什么用,不急不急后文道来。

              } else {

                     ohci->ed_controltail->ed_next = ed; --否则就是将ed添加到control链表的最后。

                     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, &ohci->regs->ed_controlcurrent);

                     ohci_writel (ohci, ohci->hc_control,&ohci->regs->control);

              }

              ohci->ed_controltail = ed; --tail向后移

              break;

 

       case PIPE_BULK: --块传输,其实现原理同控制传输。

              if (ohci->ed_bulktail == NULL) {

                     ohci_writel (ohci, ed->dma, &ohci->regs->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, &ohci->regs->ed_bulkcurrent);

                     ohci_writel (ohci, ohci->hc_control,&ohci->regs->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,而中断传输则有前面的几种周期。也许大家都看过下面的这张图,当时对于这张图的实现很困惑。

上述图仅仅是一个示意图,而且这张图也比较难懂。其实如果大家不看这张图而是看HCCAHccaInterrruptTable的说明则会好理解很多。我们来参照linux中关于HCCA的定义就更好理解了,OHCI中还有一个periodic成员是int_table的影子,其也是一个大小为32的数组。

#define NUM_INTS 32

struct ohci_hcca {

         __hc32     int_table [NUM_INTS];    --对应的ed的物理地址

         __hc32     frame_no;                      --当前的frame number

         __hc32     done_head;              

         u8     reserved_for_hc [116];

         u8     what [4];          

} __attribute__ ((aligned(256)));

其中我们更加关注int_table,记住int_table的大小是32个。简单讲吧,在OHCI的周期性传输中以32ms为一个大周期,对于以1ms为轮询周期的ed,则会挂载在32peridic中,就是在32ms的大周期中,发送32次,实现1ms的轮询周期,而2ms的则会挂载到16个的periodic中,就是每隔1ms就发送一次,在32ms中发送16次,实现2ms的轮询时间。而对于32ms的,则就就挂载在peridic中的某一个,在32ms中发送一次。这样就可以理解为什么轮询时间只有1ms2ms4ms8ms16ms32ms了。关于其具体的实现咱们来看源码。

Balance函数从函数名上来看就是平衡的意思,那什么是平衡呢,咱们来想想比如32ms轮询一次的周期性传输,那这个ed要挂到那个链表下呢,这个就涉及到带宽的分配的,比如第一二的1ms周期的带宽几乎都快分完了,而第31,321ms周期的带宽几乎还没有怎么分配,那我总不成还死皮赖脸的把这个ed挂载第一二的1ms周期下吧,毕竟人家的资源都快完了。咱要合理的利用资源,咱就挂到第31,321ms周期下啊!ohci->load嘛就是存储了资源的分配情况,也是一个大小32的数组。其实在32ms的大周期中每一个小周期都会有load对应来表明有多少带宽被分配了,返回的是添加的其实端点。

static int balance (struct ohci_hcd *ohci, int interval, int load)

{

         int    i, branch = -ENOSPC;

         if (interval > NUM_INTS)

                   interval = NUM_INTS;

         for (i = 0; i < interval ; i++) {

                   if (branch < 0 || ohci->load [branch] > ohci->load [i]) {

                            int    j;

                            for (j = i; j < NUM_INTS; j += interval) {

                                     if ((ohci->load [j] + load) > 900)  --usb1.1中,保留90%的带宽

                                               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_t data;

       int          data_len = urb->transfer_buffer_length;

       int          cnt = 0;

       u32        info = 0;

       int          is_out = usb_pipeout (urb->pipe);

       int          periodic = 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, &ohci->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)&& cnt < 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, &ohci->regs->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, &ohci->regs->cmdstatus);

              break;

       case PIPE_ISOCHRONOUS:

              for (cnt = 0; cnt < urb->number_of_packets; cnt++) {

                     int   frame = urb->start_frame;

                     frame += cnt * urb->interval;

                     frame &= 0xffff;

                     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, &ohci->regs->control);

       }

}

上面的函数会涉及到具体的硬件操作,不过其中有一个函数很重要就是td_fill,填充td。关于其具体的实现各位可以去看源码,在此不再讲述。

.总结

在本文中主要讲述urbedtd的转换还有OHCI的调度。
阅读(2310) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~