Chinaunix首页 | 论坛 | 博客
  • 博客访问: 271433
  • 博文数量: 46
  • 博客积分: 4125
  • 博客等级: 上校
  • 技术积分: 575
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-25 16:40
文章分类

全部博文(46)

文章存档

2011年(1)

2010年(4)

2009年(38)

2008年(3)

我的朋友

分类: LINUX

2009-03-14 16:51:04

在pci访问uhci寄存器需要使用io端口,0x0为uhci的控制命令寄存器,所有的详细寄存器在uhci的手册均有描述,我这里就不详细介绍了,想要的在下面的附录有提供
check_and_reset_hc检测uhci是否需要复位,并完成复位后的初始化工作
 
check_and_reset_hc在/drivers/usb/host/uhci-hcd.c
 

static void check_and_reset_hc(struct uhci_hcd *uhci)
{
 //检测是否需要复位
 if (uhci_check_and_reset_hc(to_pci_dev(uhci_dev(uhci)), uhci->io_addr))
  finish_reset(uhci);
}

uhci_check_and_reset_hc负责真正的检测工作,并执行需要的复位工作
uhci_check_and_reset_hc在/drivers/usb/host/pci-quirks.c
 

int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base)
{
 u16 legsup;
 unsigned int cmd, intr;
 /*
  * When restarting a suspended controller, we expect all the
  * settings to be the same as we left them:
  *
  * PIRQ and SMI disabled, no R/W bits set in USBLEGSUP;
  * Controller is stopped and configured with EGSM set;
  * No interrupts enabled except possibly Resume Detect.
  *
  * If any of these conditions are violated we do a complete reset.
  */

  
 //读取UHCI的UHCI_USBLEGSUP寄存器到legsup
 pci_read_config_word(pdev, UHCI_USBLEGSUP, &legsup);
 //检测UHCI_USBLEGSUP寄存器的0 1 2 3 4 5 7 13位是否为1
 //其中一位为1则跳到reset_needed
 //这些控制器和开机使用usb键盘鼠标有关,个人猜测
 if (legsup & ~(UHCI_USBLEGSUP_RO | UHCI_USBLEGSUP_RWC))
 {
  dev_dbg(&pdev->dev, "%s: legsup = 0x%04x\n",__func__, legsup);
  goto reset_needed;
 }
 //读取UHCI_USBCMD寄存器
 cmd = inw(base + UHCI_USBCMD);
 //检测UHCI_USBCMD寄存器的Run/Stop位是否为1
 //进入全局悬挂标志位 ,配置位是否为0
 if ((cmd & UHCI_USBCMD_RUN) || !(cmd & UHCI_USBCMD_CONFIGURE) || !(cmd & UHCI_USBCMD_EGSM))
 {
  dev_dbg(&pdev->dev, "%s: cmd = 0x%04x\n",__func__, cmd);
  goto reset_needed;
 }
 //读取UHCI_USBINTR寄存器
 intr = inw(base + UHCI_USBINTR);
 //检测UHCI_USBINTR寄存器的恢复中断位外的其它位是否为1
 //也就是短包中断,TD完成中断和超时/CRC中断
 if (intr & (~UHCI_USBINTR_RESUME))
 {
  dev_dbg(&pdev->dev, "%s: intr = 0x%04x\n",__func__, intr);
  goto reset_needed;
 }
 return 0;
reset_needed:
 dev_dbg(&pdev->dev, "Performing full reset\n");
 //执行复位
 uhci_reset_hc(pdev, base);
 return 1;
}
uhci_reset_hc执行复位操作
uhci_reset_hc在/drivers/usb/host/pci-quirks.c中
void uhci_reset_hc(struct pci_dev *pdev, unsigned long base)
{
 /* Turn off PIRQ enable and SMI enable. (This also turns off the
  * BIOS's USB Legacy Support.) Turn off all the R/WC bits too.
  */

 // 将UHCI_USBLEGSUP寄存器的8 9 10 11 15位置1
 pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_RWC);
 /* Reset the HC - this will force us to get a
  * new notification of any already connected
  * ports due to the virtual disconnect that it
  * implies.
  */

  //将UHCI_USBCMD寄存器的主机控制器复位位置1
 outw(UHCI_USBCMD_HCRESET, base + UHCI_USBCMD);
 mb();
 //等待5ms
 udelay(5);
 //读取UHCI_USBCMD,检测主机控制器复位位
 //为1则说明复位还没完成
 if (inw(base + UHCI_USBCMD) & UHCI_USBCMD_HCRESET)
  dev_warn(&pdev->dev, "HCRESET not completed yet!\n");
 /* Just to be safe, disable interrupt requests and
  * make sure the controller is stopped.
  */

  //将UHCI_USBINTR和UHCI_USBCMD寄存器清0
 outw(0, base + UHCI_USBINTR);
 outw(0, base + UHCI_USBCMD);
}
finish_reset执行复位完成后的初始化工作
finish_reset在/drivers/usb/host/uhci-hcd.c中
static void finish_reset(struct uhci_hcd *uhci)
{
 int port;
 /* HCRESET doesn't affect the Suspend, Reset, and Resume Detect
  * bits in the port status and control registers.
  * We have to clear them by hand.
  */

  //历遍所有端口
 for (port = 0; port < uhci->rh_numports; ++port)
  //清0端口寄存器
  outw(0, uhci->io_addr + USBPORTSC1 + (port * 2));
 //初始化端口悬挂和恢复标记组
 uhci->port_c_suspend = uhci->resuming_ports = 0;
 //设置状态为复位
 uhci->rh_state = UHCI_RH_RESET;
 //设置停止标志为真
 uhci->is_stopped = UHCI_IS_STOPPED;
 //设置主机控制器状态为停止
 uhci_to_hcd(uhci)->state = HC_STATE_HALT;
 uhci_to_hcd(uhci)->poll_rh = 0;
 uhci->dead = 0; /* Full reset resurrects the controller */
}

hcd->driver->start为uhci_start,这个函数负责初始化uhci的帧列表

uhci_start在/drivers/usb/host/uhci-hcd.c

static int uhci_start(struct usb_hcd *hcd)
{
 //取得uhci_hcd结构
 struct uhci_hcd *uhci = hcd_to_uhci(hcd);
 int retval = -EBUSY;
 int i;
 struct dentry *dentry;
 hcd->uses_new_polling = 1;
 spin_lock_init(&uhci->lock);
 //初始化另一个定时结构
 setup_timer(&uhci->fsbr_timer, uhci_fsbr_timeout, (unsigned long) uhci);
 INIT_LIST_HEAD(&uhci->idle_qh_list);
 init_waitqueue_head(&uhci->waitqh);
 //分配DMA内存池空间
 uhci->frame = dma_alloc_coherent(uhci_dev(uhci),
         UHCI_NUMFRAMES * sizeof(*uhci->frame),
         &uhci->frame_dma_handle,
         0);
 if (!uhci->frame)
 {
  dev_err(uhci_dev(uhci), "unable to allocate " "consistent memory for frame list\n");
  goto err_alloc_frame;
 }
 //用0初始化uhci->frame所指向的dma内存空间
 memset(uhci->frame, 0, UHCI_NUMFRAMES * sizeof(*uhci->frame));
 uhci->frame_cpu = kcalloc(UHCI_NUMFRAMES, sizeof(*uhci->frame_cpu),GFP_KERNEL);
 if (!uhci->frame_cpu)
 {
  dev_err(uhci_dev(uhci), "unable to allocate " "memory for frame pointers\n");
  goto err_alloc_frame_cpu;
 }
 //创建一个td描述符用的dma内存池
 uhci->td_pool = dma_pool_create("uhci_td", uhci_dev(uhci), sizeof(struct uhci_td), 16, 0);
 if (!uhci->td_pool)
 {
  dev_err(uhci_dev(uhci), "unable to create td dma_pool\n");
  goto err_create_td_pool;
 }
 //创建一个qh描述符用的dma内存池,qh是什么,等一下做说明
 uhci->qh_pool = dma_pool_create("uhci_qh", uhci_dev(uhci),sizeof(struct uhci_qh), 16, 0);
 if (!uhci->qh_pool)
 {
  dev_err(uhci_dev(uhci), "unable to create qh dma_pool\n");
  goto err_create_qh_pool;
 }
 //分配一个td用于结尾td
 uhci->term_td = uhci_alloc_td(uhci);
 //检测结尾td分配是否成功
 if (!uhci->term_td)
 {
  dev_err(uhci_dev(uhci), "unable to allocate terminating TD\n");
  goto err_alloc_term_td;
 }
 //分配11个龙骨qh
 for (i = 0; i < UHCI_NUM_SKELQH; i++)
 {
  //为龙骨qh数组分配qh
  uhci->skelqh[i] = uhci_alloc_qh(uhci, NULL, NULL);
  
  if (!uhci->skelqh[i])
  {
   dev_err(uhci_dev(uhci), "unable to allocate QH\n");
   goto err_alloc_skelqh;
  }
 }
 /*
  * 8 Interrupt queues; link all higher int queues to int1 = async
  */

 for (i = SKEL_ISO + 1; i < SKEL_ASYNC; ++i)
  //让2号到8号龙骨的link全部连接到9号龙骨上
  uhci->skelqh[i]->link = LINK_TO_QH(uhci->skel_async_qh);
 //#define skel_async_qh skelqh[SKEL_ASYNC]
//设置9号龙骨的link为无效
 uhci->skel_async_qh->link = UHCI_PTR_TERM;
 //#define skel_term_qh skelqh[SKEL_TERM]
//设置10号龙骨的link为自己,也就是自己连接自己
 uhci->skel_term_qh->link = LINK_TO_QH(uhci->skel_term_qh);
 /* This dummy TD is to work around a bug in Intel PIIX controllers */
 uhci_fill_td(uhci->term_td, 0, uhci_explen(0) | (0x7f << TD_TOKEN_DEVADDR_SHIFT) | USB_PID_IN, 0);
 //设置终结td的link为无效
 uhci->term_td->link = UHCI_PTR_TERM;
 //设置9号龙骨和10号龙骨的element为终结td
 uhci->skel_async_qh->element = uhci->skel_term_qh->element = LINK_TO_TD(uhci->term_td);
 /*
  * Fill the frame list: make all entries point to the proper
  * interrupt queue.
  */

//历遍帧队列
 for (i = 0; i < UHCI_NUMFRAMES; i++)
 {
  /* Only place we don't use the frame list routines */
  //设置帧连接的龙骨qh
  uhci->frame[i] = uhci_frame_skel_link(uhci, i);
 }
 /*
  * Some architectures require a full mb() to enforce completion of
  * the memory writes above before the I/O transfers in configure_hc().
  */

 mb();
 //配置主机控制器
 configure_hc(uhci);
 //设置初始化标志为1
 uhci->is_initialized = 1;
 //启动根集线器
 start_rh(uhci);
 return 0;
}

现在不得不谈一下qh了~ qh是queue head的缩写,我们已经知道帧和td的用途了,呢qh是干嘛的呢?
简单来说qh是td的一个集合,先看看qh的数据结构,如下图

 
很简单,只有2个字段,从上到下为Link字段和Element字段,这两个字段都为连接字段,用于连接qh或者td,这两个字段的T字段用于标记结束,没了,这是最后一个连接,无论连接存不存在都是无效的,Q为qh/td标识,为1则标识连接对象为qh,0则为td
下图描述了使用qh结构的队列
 
 
左边qh的link指向右边的link,element指向下面的td
右边的qh的link为无效,element指向下面的td
执行顺序为从上到下先执行左边qh的td,再执行右边qh的td
呢什么是龙骨qh呢?~ 在断传输,有一个时间片的概念,有些设备要求主机每间隔5ms提取一次数据,有些设备要求10ms,为了满足这样的时间间隔,就诞生了龙骨qh,uhci有1024个帧,每个帧的执行时间为1ms,呢么历遍一次所有的帧就是1024ms,在断传输分为,1ms,2ms,4ms,8ms,16ms,32ms,64ms,128ms,我们按照fudan_abc的命名方法,称为int1,int2,.....
怎么满足这样的间隔要求呢?聪明的朋友应该猜到了吧,就是把所有的1024帧连接到同一个qh,不就是1ms访问一次了么,或者把其一半的帧按间隔分开,512帧连接到同一个qh,不就是2ms了么
对的,龙骨qh干的就是这事情,请看下图
 
 
 
可以看到2号龙骨有8帧,呢么就是1024/8=int512,3号龙骨有16帧,呢么就是1024/16=int256,以此类推,
最后一个有点特别,看以看见,2号到8号龙骨qh的link都连接到了9号龙骨,呢么9号龙骨间接使用了这些帧,所以9号龙骨有所有的1024帧,呢么就是int1
负责分配连接这些帧的函数为uhci_frame_skel_link

uhci_frame_skel_link在drivers/usb/host/uhci-hcd.c

static __le32 uhci_frame_skel_link(struct uhci_hcd *uhci, int frame)
{
 int skelnum;
 /*
  * The interrupt queues will be interleaved as evenly as possible.
  * There's not much to be done about period-1 interrupts; they have
  * to occur in every frame. But we can schedule period-2 interrupts
  * in odd-numbered frames, period-4 interrupts in frames congruent
  * to 2 (mod 4), and so on. This way each frame only has two
  * interrupt QHs, which will help spread out bandwidth utilization.
  *
  * ffs (Find First bit Set) does exactly what we need:
  * 1,3,5,... => ffs = 0 => use period-2 QH = skelqh[8],
  * 2,6,10,... => ffs = 1 => use period-4 QH = skelqh[7], etc.
  * ffs >= 7 => not on any high-period queue, so use
  * period-1 QH = skelqh[9].
  * Add in UHCI_NUMFRAMES to insure at least one bit is set.
  */

 skelnum = 8 - (int) __ffs(frame | UHCI_NUMFRAMES);
 if (skelnum <= 1)
  skelnum = 9;
 return LINK_TO_QH(uhci->skelqh[skelnum]);
}

这个函数很简单,是一个模运算,具体怎么模,参考fudan_abc的文章吧,哈哈~

UHCI_PTR_TERM为设置link或者element字段为无效
0号龙骨为无连接qh,1号龙骨为等时qh,2到9号龙骨为断龙骨,10号龙骨为终结龙骨,一共11个龙骨
如何真正使用这些龙骨在后面介绍
uhci_alloc_td在td_pool分配一个td结构所需要的内存大小

uhci_alloc_td在drivers/usb/host/uhci-q.c
 

static struct uhci_td *uhci_alloc_td(struct uhci_hcd *uhci)
{
 dma_addr_t dma_handle;
 struct uhci_td *td;
 //dma_handle保存的 是分配的dma缓冲区起始地址
 td = dma_pool_alloc(uhci->td_pool, GFP_ATOMIC, &dma_handle);
 if (!td)
  return NULL;
 //保存dma缓冲区起始地址
 td->dma_handle = dma_handle;
 td->frame = -1;
 INIT_LIST_HEAD(&td->list);
 INIT_LIST_HEAD(&td->fl_list);
 return td;
}
uhci_alloc_qh在qh_pool中分配一个qh结构所需要的内存大小
uhci_alloc_qh在drivers/usb/host/uhci-q.c中
static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci,
  struct usb_device *udev, struct usb_host_endpoint *hep)
{
 dma_addr_t dma_handle;
 struct uhci_qh *qh;
 qh = dma_pool_alloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle);
 if (!qh)
  return NULL;
 memset(qh, 0, sizeof(*qh));
 //保存dma缓冲区起始地址
 qh->dma_handle = dma_handle;
 //设置该qh的连接和部件都为终结
 qh->element = UHCI_PTR_TERM;
 qh->link = UHCI_PTR_TERM;
 INIT_LIST_HEAD(&qh->queue);
 INIT_LIST_HEAD(&qh->node);
 //没有usb设备则为普通qh
 if (udev)
 {
  //取得端点的传输类型
  qh->type = hep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
  //检测是否为等时传输
  if (qh->type != USB_ENDPOINT_XFER_ISOC)
  {
   //分配一个虚拟td
   qh->dummy_td = uhci_alloc_td(uhci);
   //检测分配是否成功
   if (!qh->dummy_td)
   {
    dma_pool_free(uhci->qh_pool, qh, dma_handle);
    return NULL;
   }
  }
  //设置qh的状态为空闲
  qh->state = QH_STATE_IDLE;
  //连接端点到qh
  qh->hep = hep;
  //连接usb设备到qh
  qh->udev = udev;
  //连接该qh到端点
  hep->hcpriv = qh;
  //检测qh的类型是否为中断或等时
  if (qh->type == USB_ENDPOINT_XFER_INT || qh->type == USB_ENDPOINT_XFER_ISOC)
   qh->load = usb_calc_bus_time(udev->speed,
     usb_endpoint_dir_in(&hep->desc),
     qh->type == USB_ENDPOINT_XFER_ISOC,
     le16_to_cpu(hep->desc.wMaxPacketSize))
    / 1000 + 1;
 }
  //龙骨qh,现在所使用的为龙骨qh,直接到这里
 else
 { /* Skeleton QH */
  //设置qh的状态为有效
  qh->state = QH_STATE_ACTIVE;
  qh->type = -1;
 }
 return qh;
}

configure_hc负责存储主机控制器需要的基本寄存器设定

configure_hc在drivers/usb/host/uhci-hcd.c
 

static void configure_hc(struct uhci_hcd *uhci)
{
 /* Set the frame length to the default: 1 ms exactly */
 //修改USBSOF寄存器,设置周期为默认值1ms
 outb(USBSOF_DEFAULT, uhci->io_addr + USBSOF);
 /* Store the frame list base address */
 //修改USBFLBASEADD寄存器,设置frame_list的地址
 outl(uhci->frame_dma_handle, uhci->io_addr + USBFLBASEADD);
 /* Set the current frame number */
 //修改USBFRNUM寄存器,设置当前第一帧帧号
 outw(uhci->frame_number & UHCI_MAX_SOF_NUMBER,uhci->io_addr + USBFRNUM);
 /* Mark controller as not halted before we enable interrupts */
 //设置主机控制器驱动为悬挂状态
 uhci_to_hcd(uhci)->state = HC_STATE_SUSPENDED;
 mb();
 /* Enable PIRQ */
 //修改USBLEGSUP寄存器,使能PIRQ
 pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP,USBLEGSUP_DEFAULT);
}

start_rh负责启动根集线器
start_rh在/drivers/usb/host/uhci-hcd.c

 

static void start_rh(struct uhci_hcd *uhci)
{
 //设置主机控制器驱动状态为运行
 uhci_to_hcd(uhci)->state = HC_STATE_RUNNING;
 //设置主机控制器的停止标志为0
 uhci->is_stopped = 0;
 /* Mark it configured and running with a 64-byte max packet.
  * All interrupts are enabled, even though RESUME won't do anything.
  */

 //写主机命令寄存器
 //设置开始运行位,配置标志,最大包为64比特
 outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, uhci->io_addr + USBCMD);
 //写主机中断寄存器
 //设置开启全部中断
 outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP,uhci->io_addr + USBINTR);
 mb();
 //设置uhci的状态为运行
 uhci->rh_state = UHCI_RH_RUNNING;
 uhci_to_hcd(uhci)->poll_rh = 1;
}

阅读(1617) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~