在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) |