欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

理解Linux设备驱动:USB主机控制器驱动的剖析(转)

最编程 2024-08-03 21:48:01
...
转自:http://blog.chinaunix.net/uid-20543183-id-1930831.html
 
------------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处:http://ericxiao.cublog.cn/
------------------------------------------
一:前言
Usb是一个很复杂的系统.在usb2.0规范中,将其定义成了一个分层模型.linux中的代码也是按照这个分层模型来设计的.具体的分为usb设备,hub和主机控制器三部份.在阅读代码的时候,必须要参考相应的规范.最基本的就是USB2.0的spec.它定义了USB协议.另外的一个是USB控制器的规范.有UHCI,EHCI,OHCI三种.其中UHCI是Intel推出的一种USB控制器标准.它将很多功能交给软件处理.相比之下,它也是最为复杂的.因此,本文档以UHCI为例分析.另外,在分析的过程中参考了情景分析和fudan_abc的<<linux那些事儿之我是uhci>>.正是因为踩在许多牛人的肩膀上,才使USB这个复杂的工程在我们面前变得越来越清晰.
本文的代码分析是基于linux kernel 2.6.25.涉及到的代码主要位于linux-2.6.25/drivers/usb目录下.
 
二:UHCI的初始化
UHCI主机控制器的代码位于linux-2.6.25/drivers/usb/host下面.在配置kernel的时候可以选择将其编译进内核或者编译成模块.模块的入口函数为: uhci_hcd_init().代码如下:
 
static int __init uhci_hcd_init(void)
{
    int retval = -ENOMEM;
 
     printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION "%s\n",
              ignore_oc ? ", overcurrent ignored" : "");
 
     if (usb_disabled())
         return -ENODEV;
 
     if (DEBUG_CONFIGURED) {
         errbuf = kmalloc(ERRBUF_LEN, GFP_KERNEL);
         if (!errbuf)
              goto errbuf_failed;
         uhci_debugfs_root = debugfs_create_dir("uhci", NULL);
         if (!uhci_debugfs_root)
              goto debug_failed;
     }
 
     uhci_up_cachep = kmem_cache_create("uhci_urb_priv",
         sizeof(struct urb_priv), 0, 0, NULL);
     if (!uhci_up_cachep)
         goto up_failed;
 
     retval = pci_register_driver(&uhci_pci_driver);
     if (retval)
         goto init_failed;
 
     return 0;
 
init_failed:
     kmem_cache_destroy(uhci_up_cachep);
 
up_failed:
     debugfs_remove(uhci_debugfs_root);
 
debug_failed:
     kfree(errbuf);
 
errbuf_failed:
 
     return retval;
}
入口函数比较简单.其中涉及到的接口在之前都已经详细的分析过.
在引导系统的时候,可以为kernel指定参数.如果配置了”nousb”,就明确禁止使用USB.该入口函数首先通过usb_disabled()来检测用户指定了nousb参数.然后为struct urb_priv创建了一个cache.然后注册了一个PCI驱动.struct usb_priv等以后用到的时候再进行分析.UHCI是一个PCI设备.PCI的驱动架构我们之前已经分析过了,这里不再赘述.
uhci_pci_driver定义如下所示:
static struct pci_driver uhci_pci_driver = {
     .name =       (char *)hcd_name,
     .id_table =   uhci_pci_ids,
 
     .probe = usb_hcd_pci_probe,
     .remove = usb_hcd_pci_remove,
     .shutdown =   uhci_shutdown,
 
#ifdef   CONFIG_PM
     .suspend =    usb_hcd_pci_suspend,
     .resume = usb_hcd_pci_resume,
#endif   /* PM */
};
通过之前的对PCI的分析,我们知道对于pci_dev和pci_driver的匹配过程是通过判断pci_driver的id_table项和pci_dev的相关项是否符合来进行的.在这里.id_talbe的定义如下所示:
static const struct pci_dev_id uhci_pci_ids[] = { {
     /* handle any USB UHCI controller */
     PCI_DEV_CLASS(PCI_CLASS_SERIAL_USB_UHCI, ~0),
     .driver_data =     (unsigned long) &uhci_driver,
     }, { /* end: all zeroes */ }
};
由此,可以看到,只要是属于PCI_CLASS_SERIAL_USB_UHCI类的设备,都能匹配到这个驱动.这个宏的定义如下:
#define PCI_CLASS_SERIAL_USB_UHCI    0x0c0300
其实该类型是由UHCI的spec规定的.
另外,id_talbe的私有项(driver_data)被置为了uhci_driver.这个在以后是会被用到的.
 
如果pci_driver成功匹配到设备.就会调用其probe接口.在这里.probe接口被置为了usb_hcd_pci_probe.如下所示:
int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_dev_id *id)
{
     struct hc_driver   *driver;
     struct usb_hcd         *hcd;
     int           retval;
 
     if (usb_disabled())
         return -ENODEV;
 
     if (!id)
         return -EINVAL;
     driver = (struct hc_driver *)id->driver_data;
     if (!driver)
         return -EINVAL;
 
     if (pci_enable_device(dev) < 0)
         return -ENODEV;
     dev->current_state = PCI_D0;
     dev->dev.power.power_state = PMSG_ON;
 
     if (!dev->irq) {
         dev_err(&dev->dev,
              "Found HC with no IRQ.  Check BIOS/PCI %s setup!\n",
              pci_name(dev));
         retval = -ENODEV;
         goto err1;
     }
 
     //创建usb_hcd
     hcd = usb_create_hcd(driver, &dev->dev, pci_name(dev));
     if (!hcd) {
         retval = -ENOMEM;
         goto err1;
     }
 
     //UCHI的flags没有定义成HCD_MEMORY
     if (driver->flags & HCD_MEMORY) {
         /* EHCI, OHCI */
         hcd->rsrc_start = pci_resource_start(dev, 0);
         hcd->rsrc_len = pci_resource_len(dev, 0);
         if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len,
                   driver->description)) {
              dev_dbg(&dev->dev, "controller already in use\n");
              retval = -EBUSY;
              goto err2;
         }
         hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);
         if (hcd->regs == NULL) {
              dev_dbg(&dev->dev, "error mapping memory\n");
              retval = -EFAULT;
              goto err3;
         }
 
     } else {
         /* UHCI */
         int  region;
 
         //找到一个I/O的缓冲区.UHCI只有一个I/O区间
         for (region = 0; region < PCI_ROM_RESOURCE; region++) {
              if (!(pci_resource_flags(dev, region) &
                       IORESOURCE_IO))
                   continue;
 
              hcd->rsrc_start = pci_resource_start(dev, region);
              hcd->rsrc_len = pci_resource_len(dev, region);
              if (request_region(hcd->rsrc_start, hcd->rsrc_len,
                       driver->description))
                   break;
         }
         if (region == PCI_ROM_RESOURCE) {
              dev_dbg(&dev->dev, "no i/o regions available\n");
              retval = -EBUSY;
              goto err1;
         }
     }
 
     //使用DMA
     pci_set_master(dev);
 
     retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED);
     if (retval != 0)
         goto err4;
     return retval;
 
 err4:
     if (driver->flags & HCD_MEMORY) {
         iounmap(hcd->regs);
 err3:
         release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
     } else
         release_region(hcd->rsrc_start, hcd->rsrc_len);
 err2:
     usb_put_hcd(hcd);
 err1:
     pci_disable_device(dev);
     dev_err(&dev->dev, "init %s fail, %d\n", pci_name(dev), retval);
     return retval;
}
这段代码位于linux-2.6.25/drivers/usb/core下的hcd-pci.c中.该路径下的代码是被所有USB控制器共享的.因此,我们在代码中可以看到usb_hcd_pci_probe()会有区别UHCI还是其它类型的控制器的操作.在USB驱动架构中,有很多代码是关于电源管理的.在这里我们先忽略电源管理的部份.之后再以单独章节的形式来分析linux上的电源管理子系统.
首先,会调用 pci_enable_device()来启用PCI设备.正如在分析PCI设备的时候.初始化之后的PCI设备很多功能都是被禁用的.例如I/O/内存空间,IRQ等.其次,OHCI必须要使用中断.如果对应中断号不存在,说明此设备不是一个UHCI.或者出现了错误.直接跳出.不进行后续操作.然后,OHCI必须要使用DMA.所以会调用pci_set_master()将开启设备的DMA传输能力.另外,OHCI SPEC上有定义.在PCI的配置空间中,0x20~0x23定义了OHCI的I/O区间和大小.也就是说OHCI对应的pci_dev中,只有一个I/O资源区间是有效的.
对应到上面的代码:
id->driver_data的赋值在uhci_hcd_init()中被特别指出过.被赋值为uhci_driver.它的结构如下:
static const struct hc_driver uhci_driver = {
     .description =         hcd_name,
     .product_desc =        "UHCI Host Controller",
     .hcd_priv_size =   sizeof(struct uhci_hcd),
 
     /* Generic hardware linkage */
     .irq =             uhci_irq,
     .flags =      HCD_USB11,
 
     /* Basic lifecycle operations */
     .reset =      uhci_init,
     .start =      uhci_start,
#ifdef CONFIG_PM
     .suspend =         uhci_suspend,
     .resume =     uhci_resume,
     .bus_suspend =         uhci_rh_suspend,
     .bus_resume =      uhci_rh_resume,
#endif
     .stop =            uhci_stop,
 
     .urb_enqueue =         uhci_urb_enqueue,
     .urb_dequeue =         uhci_urb_dequeue,
 
     .endpoint_disable =    uhci_hcd_endpoint_disable,
     .get_frame_number =    uhci_hcd_get_frame_number,
 
     .hub_status_data = uhci_hub_status_data,
     .hub_control =         uhci_hub_control,
};
可以看到,在的结构为struct hc_driver. Hc就是host control的意思.即为主机控制器驱动.该结构包函了很多函数指针,具体的操作我们等能后涉及的时候再回过来分析.另外,从里面可以看到,它的flags被定义成了HCD_USB1.1.
特别说明一下:UHCI是一个基于usb1.1的设备.USB1.1和USB2.0的最大区别就是USB2.0中定义有高速设备.因此,UHCI是一个不支持高速的USB控制器.只有EHCI才会支持高速.因此,在配置kernel的时候,UHCI和EHCI通常都会选上.如果只选用UHCI或者只选用EHCI.有很多设备都是不能够工作的.
因为flags被定义成HCD_USB1.1.所以代码中的if(driver->flags & HCD_MEMORY) … else …流程就转入到else下面.
 
然后,我们目光注视到usb_create_hcd()和usb_add_hcd()这两个函数.看函数名称,一个是产生struct usb_hcd.另外的一个是将这个hcd添加到系统.hcd就是host control driver的意思.先来分析一下usb_create_hcd的代码:
struct usb_hcd *usb_create_hcd (const struct hc_driver *driver,
         struct device *dev, char *bus_name)
{
     struct usb_hcd *hcd;
 
     hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL);
     if (!hcd) {
         dev_dbg (dev, "hcd alloc failed\n");
         return NULL;
     }
     dev_set_drvdata(dev, hcd);
     kref_init(&hcd->kref);
 
     usb_bus_init(&hcd->self);
     hcd->self.controller = dev;
     hcd->self.bus_name = bus_name;
     hcd->self.uses_dma = (dev->dma_mask != NULL);
 
     init_timer(&hcd->rh_timer);
     hcd->rh_timer.function = rh_timer_func;
     hcd->rh_timer.data = (unsigned long) hcd;
#ifdef CONFIG_PM
     INIT_WORK(&hcd->wakeup_work, hcd_resume_work);
#endif
 
     hcd->driver = driver;
     hcd->product_desc = (driver->product_desc) ? driver->product_desc :
              "USB Host Controller";
     return hcd;
}
函数的三个参数:
1: driver:也就是上面分析的pci_driver的id_table的driver_data项.即struct hc_driver
2: dev: OHCI所对应的pci_dev中内嵌的struct device结构
3: bus_name:OHCI对应的pci_dev的name
 
在这里,注意一下hcd内存的分配.如下示:
hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL);
我们知道,struct usb_hcd是一个位于usb_core下的东东,这个东东所有的host control都会用到.那么hcd就有一个私有区结构,用来表示host control之间不同的数据结构.而其它们相同的结构保存在struct usb_hcd中.这个hcd_priv成员在struct usb_hcd被定义成了0项数组的形式,而大小则是由hc_driver的hcd_priv_size项来指定的.
 
struct usb_hcd结构很庞大.这里不方便将其全部列出.只来说明一下在这里会用到的成员:
1:self成员: 我们可以这想思考.每条USB总线上只有一个host control.每个host control都对应着一条总线. 这个self成员就是表示hcd所对应的USB总线. self.controller表示该总线上的控制器,也就是UHCI对应的pci_dev中封装的struct device. Self. bus_name表示该总线的名称.也就是OHCI对应的pci_dev的名称.self. uses_dma来表示该总线上的控制器是否使用DMA
2: rh_timer成员:该成员是一个定时器,用来轮询控制器的根集线器的状态改变,通常用做电源管理.在这里不加详分析.
2: driver成员:表示该hcd对应驱动.
总而言之, usb_create_hcd就是对hcd的各项成员赋值.
 
相比之下usb_add_hcd()的代码就比较繁杂了.下面以分段的形式分析如下:
int usb_add_hcd(struct usb_hcd *hcd,
         unsigned int irqnum, unsigned long irqflags)
{
     int retval;
     struct usb_device *rhdev;
 
     dev_info(hcd->self.controller, "%s\n", hcd->product_desc);
 
     hcd->authorized_default = hcd->wireless? 0 : 1;
     set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
 
     /* HC is in reset state, but accessible.  Now do the one-time init,
      * bottom up so that hcds can customize the root hubs before khubd
      * starts talking to them.  (Note, bus id is assigned early too.)
      */
      //创建pool
     if ((retval = hcd_buffer_create(hcd)) != 0) {
         dev_dbg(hcd->self.controller, "pool alloc failed\n");
         return retval;
     }
在我们分析的流程中, Hcd->wireless默认为0.相应的hcd->authorized_default也被置为了0.然后将hcd->flags置为HCD_FLAG_HW_ACCESSIBLE.表示该USB控制器是可以访问的.最后在hcd_buffer_create中,因为hc_driver的flags标志被末置HCD_LOCAL_MEM.该函数在这里什么都不做就返回0了.
 
     //注册usb_bus
     if ((retval = usb_register_bus(&hcd->self)) < 0)
         goto err_register_bus;
 
     //分配并初始化root hub
     if ((rhdev = usb_alloc_dev(NULL, &hcd->self, 0)) == NULL) {
         dev_err(hcd->self.controller, "unable to allocate root hub\n");
         retval = -ENOMEM;
         goto err_allocate_root_hub;
     }
     //OHCI定义于usb1.1只能支持全速
     rhdev->speed = (hcd->driver->flags & HCD_USB2) ? USB_SPEED_HIGH :
              USB_SPEED_FULL;
     hcd->self.root_hub = rhdev;
 
     /* wakeup flag init defaults to "everything works" for root hubs,
      * but drivers can override it in reset() if needed, along with
      * recording the overall controller's system wakeup capability.
      */
     device_init_wakeup(&rhdev->dev, 1);
在前面.我们看到了在hcd的self成员的赋值过程,而所有的总线信息都要保存在一个地方,在其它的地方会用到这些总线信息.所以usb_register_bus()对应的工作就是在全局变量busmap的位图中找到没有被使用的位做为usb_bus的序号(我们暂且称呼它为USB总线号).然后为该总线注册一个属于usb_host_class类的设备.以后在/sys/class/host中就可以看到该bus对应的目录了.最后,将总线链接到usb_bus_list链表中.
然后,每一个USB控制器都有一个根集线器.这里也要为总线下的根集钱器创建相应的结构, usb_alloc_dev()用来生成并初始化的usb_device结构.这个函数比较重要,在后面给出这个函数的详细分析.
因为OHCI是USB1.1的设备,所以,根集线器的speed会被定义成USB_SPEED_FULL(全速).最后将这个根集线器关联到总线中.
device_init_wakeup(&rhdev->dev, 1)是和总线相关的,忽略它吧 :-)

上一篇: 汇编语言中的状态标志符(CF、OF、SF、ZF)在运算(ADD、SUB、ADC、SBB)中的响应变化 详细解释: - SF标志位表示有符号数运算结果的正负性,有四种可能的情况: - 当结果的最高位为0,且操作数为正数时,SF为0; - 当结果的最高位为0,且操作数为负数时,SF为1; - 当结果的最高位为1,且操作数为正数时,SF为1; - 当结果的最高位为1,且操作数为负数时,SF为0。 - ZF标志反映结果是否为0,当结果不为0时,ZF为0。 以下是各种SUB运算下标志符的响应情况: - 无符号数和有符号数都溢出: - CF为1,说明无符号数运算存在溢出; - OF为1,说明有符号数运算存在溢出。 | 操作数 | 运算结果 | CF | OF | | --- | --- | --- | --- | | 114 | -147 | 1 | 1 | - 无符号数: - CF为0,说明没有发生解位; - OF为0,说明没有发生溢出。

下一篇: CB2201_MQTT_User_Guide