linux设备驱动之USB主机控制器驱动分析 【转】

转自: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)是和总线相关的,忽略它吧 :-)

     /* "reset" is misnamed; its role is now one-time init. the controller

      * should already have been reset (and boot firmware kicked off etc).

      */

     if (hcd->driver->reset && (retval = hcd->driver->reset(hcd)) < 0) {

         dev_err(hcd->self.controller, "can't setup\n");

         goto err_hcd_driver_setup;

     }

 

     /* NOTE: root hub and controller capabilities may not be the same */

     if (device_can_wakeup(hcd->self.controller)

              && device_can_wakeup(&hcd->self.root_hub->dev))

         dev_dbg(hcd->self.controller, "supports USB remote wakeup\n");

 

     /* enable irqs just before we start the controller */

     if (hcd->driver->irq) {

         snprintf(hcd->irq_descr, sizeof(hcd->irq_descr), "%s:usb%d",

                   hcd->driver->description, hcd->self.busnum);

         if ((retval = request_irq(irqnum, &usb_hcd_irq, irqflags,

                   hcd->irq_descr, hcd)) != 0) {

              dev_err(hcd->self.controller,

                       "request interrupt %d failed\n", irqnum);

              goto err_request_irq;

         }

         hcd->irq = irqnum;

         dev_info(hcd->self.controller, "irq %d, %s 0x%08llx\n", irqnum,

                   (hcd->driver->flags & HCD_MEMORY) ?

                       "io mem" : "io base",

                       (unsigned long long)hcd->rsrc_start);

     } else {

         hcd->irq = -1;

         if (hcd->rsrc_start)

              dev_info(hcd->self.controller, "%s 0x%08llx\n",

                       (hcd->driver->flags & HCD_MEMORY) ?

                       "io mem" : "io base",

                       (unsigned long long)hcd->rsrc_start);

     }

 

     if ((retval = hcd->driver->start(hcd)) < 0) {

         dev_err(hcd->self.controller, "startup error %d\n", retval);

         goto err_hcd_driver_start;

     }

调用hc_driver的rese函数来初始化OHCI. device_can_wakeup()那一段是属于电源管理的,忽略吧.然后为OHCI的中断号注册中断处理函数.然后再调用hc_driver的start函数来启动OHCI.在这里,提醒一下,注册中断处理函数时所带的标志是usb_add_hcd()函数的第三个参数,也就是IRQF_DISABLED | IRQF_SHARED.也就是说,在进入到中断处理的时候,要禁用本地中断.中断处理函数的参数就是hcd

 

     /* starting here, usbcore will pay attention to this root hub */

     rhdev->bus_mA = min(500u, hcd->power_budget);

     if ((retval = register_root_hub(hcd)) != 0)

         goto err_register_root_hub;

 

     retval = sysfs_create_group(&rhdev->dev.kobj, &usb_bus_attr_group);

     if (retval < 0) {

         printk(KERN_ERR "Cannot register USB bus sysfs attributes: %d\n",

                retval);

         goto error_create_attr_group;

     }

     if (hcd->uses_new_polling && hcd->poll_rh)

         usb_hcd_poll_rh_status(hcd);

     return retval;

rhdev->bus_mA表示该HUB当前可用电流限制.在前面的流程中,我们并末对hcd->power_budget进行赋值,也就是说,并没有对roo hub限制电流.

之后,会调用register_root_hub()来对根集线器进行操作,这个函数很重要,以后再单独给出分析.

 

 

error_create_attr_group:

     mutex_lock(&usb_bus_list_lock);

     usb_disconnect(&hcd->self.root_hub);

     mutex_unlock(&usb_bus_list_lock);

err_register_root_hub:

     hcd->driver->stop(hcd);

err_hcd_driver_start:

     if (hcd->irq >= 0)

         free_irq(irqnum, hcd);

err_request_irq:

err_hcd_driver_setup:

     hcd->self.root_hub = NULL;

     usb_put_dev(rhdev);

err_allocate_root_hub:

     usb_deregister_bus(&hcd->self);

err_register_bus:

     hcd_buffer_destroy(hcd);

     return retval;

}

 

经过前面的段式分析,我们对这个函数的流程有了一定的了解.其中有几个函数特别列出,分析如下:

2.1:usb_alloc_dev()的操作

之所以要特别列出分析,是因为这个函数中有很重要的赋值操作.代码如下:

struct usb_device *usb_alloc_dev(struct usb_device *parent,

                    struct usb_bus *bus, unsigned port1)

{

     struct usb_device *dev;

     //从bus结构,求得usb_hcd

     struct usb_hcd *usb_hcd = container_of(bus, struct usb_hcd, self);

     unsigned root_hub = 0;

 

     dev = kzalloc(sizeof(*dev), GFP_KERNEL);

     if (!dev)

         return NULL;

 

     //增加hcd的引用计数

     if (!usb_get_hcd(bus_to_hcd(bus))) {

         kfree(dev);

         return NULL;

     }

 

     //usb_device,内嵌有struct device结构,对这个结构进行初始化

     device_initialize(&dev->dev);

     dev->dev.bus = &usb_bus_type;

     dev->dev.type = &usb_device_type;

     dev->dev.dma_mask = bus->controller->dma_mask;

     set_dev_node(&dev->dev, dev_to_node(bus->controller));

     //将dev的初始状态置为USB_STATE_ATTACHED.妻示已经连接上了

     dev->state = USB_STATE_ATTACHED;

     atomic_set(&dev->urbnum, 0);

 

     //初始化设备的端点0

     INIT_LIST_HEAD(&dev->ep0.urb_list);

     dev->ep0.desc.bLength = USB_DT_ENDPOINT_SIZE;

     dev->ep0.desc.bDescriptorType = USB_DT_ENDPOINT;

     /* ep0 maxpacket comes later, from device descriptor */

     usb_enable_endpoint(dev, &dev->ep0);

     dev->can_submit = 1;

 

     /* Save readable and stable topology id, distinguishing devices

      * by location for diagnostics, tools, driver model, etc.  The

      * string is a path along hub ports, from the root.  Each device's

      * dev->devpath will be stable until USB is re-cabled, and hubs

      * are often labeled with these port numbers.  The bus_id isn't

      * as stable:  bus->busnum changes easily from modprobe order,

      * cardbus or pci hotplugging, and so on.

      */

      //如果没有父结点,也即该设备是root hub.usb_device内嵌的dev的父结点指向它的控制器

     if (unlikely(!parent)) {

         dev->devpath[0] = '0';

 

         dev->dev.parent = bus->controller;

         sprintf(&dev->dev.bus_id[0], "usb%d", bus->busnum);

         root_hub = 1;

     } else {

 

         //如果有父结点,就指向其父结点

         /* match any labeling on the hubs; it's one-based */

         if (parent->devpath[0] == '0')

              snprintf(dev->devpath, sizeof dev->devpath,

                   "%d", port1);

         else

              snprintf(dev->devpath, sizeof dev->devpath,

                   "%s.%d", parent->devpath, port1);

 

         dev->dev.parent = &parent->dev;

         sprintf(&dev->dev.bus_id[0], "%d-%s",

              bus->busnum, dev->devpath);

 

         /* hub driver sets up TT records */

     }

//上面的节点名称赋值很有意思: 如果是根集线器,它的名称为"usb"+总线号

//如果是第1条总线上的root hub,对应就是usb0

//如果是根集线其下面的设备.它的名称为:总线号+ "-" + portnum 或者:总线号+ "-"  + 上层总线//的devpath

 

     dev->portnum = port1;

     dev->bus = bus;

     dev->parent = parent;

     INIT_LIST_HEAD(&dev->filelist);

 

#ifdef   CONFIG_PM

     mutex_init(&dev->pm_mutex);

     INIT_DELAYED_WORK(&dev->autosuspend, usb_autosuspend_work);

     dev->autosuspend_delay = usb_autosuspend_delay * HZ;

     dev->connect_time = jiffies;

     dev->active_duration = -jiffies;

#endif

     if (root_hub) /* Root hub always ok [and always wired] */

         dev->authorized = 1;

     else {

         dev->authorized = usb_hcd->authorized_default;

         dev->wusb = usb_bus_is_wusb(bus)? 1 : 0;

     }

     return dev;

}

该函数的参数如下:

Parent:该设备的上层hub.对于root hub来说,该参数为NULL.表示它的上层无设备

Bus  :该设备所属的bus

port1:该设备所连hub的端口号.对于root hub来说,该项为0.

参考添加的注释,这段代码应该很容易理解.注意在代码为usb_driver内嵌的struct device的赋值过程.它的bus被设置成了usb_bus_type.它的type被设置成了usb_device_type.这些赋值是我们以后分析usb设备驱动的基础.这里不再啰嗦.为以后的分析打一个伏笔.:-) .在这里,注重分析一下对端点0的操作以及设备的命名规则.

1:对于端点0:

USB协议规定每个设备都必须要有一个端点0.USB控制器和这个端点0通信都可以获得整个设备的信息.USB设备可以有多个端口.但是除了端点0外,其它端口的通信都是单向的.如:一些端点只能接收数据.另外的端点只能发送数据.每个端点都对应一个端点号,一个端点号+通信方向就确定了一个端点.也就是说,一个端点号对应二个端点,进来方向的一个,出去方向的一个.

对于端点0.就分析这么多.具体的流程.以后结合代码再来分析.

结合上面的代码:

dev->ep0.desc.表示ep0(端点0)的端点描述符.desc的定义为struct usb_endpoint_descriptor.在usb2.0的规范中,总共有8种描述符.端点描述符的类型定义为5.整个端点描述符的长度为7.

跟进去看一下usb_enable_endpoint():

void usb_enable_endpoint(struct usb_device *dev, struct usb_host_endpoint *ep)

{

     int epnum = usb_endpoint_num(&ep->desc);

     int is_out = usb_endpoint_dir_out(&ep->desc);

     int is_control = usb_endpoint_xfer_control(&ep->desc);

 

     if (is_out || is_control) {

         usb_settoggle(dev, epnum, 1, 0);

         dev->ep_out[epnum] = ep;

     }

     if (!is_out || is_control) {

         usb_settoggle(dev, epnum, 0, 0);

         dev->ep_in[epnum] = ep;

     }

     ep->enabled = 1;

}

Usb_endpoint_num()定义如下:

static inline int usb_endpoint_num(const struct usb_endpoint_descriptor *epd)

{

     return epd->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;

}

即在描述符的bEndpointAddress字段中,取得端点号.

usb_endpoint_dir_out()定义如下:

static inline int usb_endpoint_dir_out(

                   const struct usb_endpoint_descriptor *epd)

{

     return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT);

}

即判断该端点是否是OUT方向的.OUT方向.就是指从主机发往设备方向.

usb_endpoint_xfer_control()定义如下:

static inline int usb_endpoint_xfer_control(

                   const struct usb_endpoint_descriptor *epd)

{

     return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==

         USB_ENDPOINT_XFER_CONTROL);

}

即检查该端点是否是控制传输端点.

 

从上面的流程看,我们并没有对ep0的相关字段赋值,这些函数会全部都返回0.

所以,流程就转到这里:

 

if (!is_out || is_control) {

         usb_settoggle(dev, epnum, 0, 0);

         dev->ep_in[epnum] = ep;

}

     ep->enabled = 1;

 

这段代码执行的效果就是:dev->ep_in[0]=ep. Dev-> toggle[0]的0位被置1.

最后将ep->enabled置为1.表示启用该设备.

其实该段代码主要是改变dev->ep_in[]和dev->toggle[].将struct usb_device的相关成员列出:

struct usb_device {

     ……

     unsigned int toggle[2];     /* one bit for each endpoint*/

     ……

     struct usb_host_endpoint *ep_in[16];

     struct usb_host_endpoint *ep_out[16];

     ……

}

Usb2.0的spec规定.每个设备最多有15个端点号.即最多表示30个端点.另外再加一个端点0.共计31个.

数组ep_in[]表示in方向的端点集合.ep_out[]表示ONT方向的集合.它们在数组中的位置是以端点号做为索引的.

而对于toggle[]数组.他实际上就是一个位图.IN方向的是toggle[0].OUT方向的是toggle[1].其实,这个数组中的每一位表示ep的toggle值.关于toggle,在分析USB的数据传输再来说明,另外,从usb_enable_endpoint()中的代码可以看到,端点的toggle是初始化为0的.

2:对于usb设备的命名规则

注释中解释了一部份,在这里整理一下.相应的代码如下:

if (unlikely(!parent)) {

         dev->devpath[0] = '0';

 

         dev->dev.parent = bus->controller;

         sprintf(&dev->dev.bus_id[0], "usb%d", bus->busnum);

         root_hub = 1;

     } else {

 

         //如果有父结点,就指向其父结点

         /* match any labeling on the hubs; it's one-based */

         if (parent->devpath[0] == '0')

              snprintf(dev->devpath, sizeof dev->devpath,

                   "%d", port1);

         else

              snprintf(dev->devpath, sizeof dev->devpath,

                   "%s.%d", parent->devpath, port1);

 

         dev->dev.parent = &parent->dev;

         sprintf(&dev->dev.bus_id[0], "%d-%s",

              bus->busnum, dev->devpath);

 

         /* hub driver sets up TT records */

     }

如果父结点为NULL,也就是说root hub的情况.它的名称就是”usb”+usb总线号.例如,对于第1条总线上的root hub为usb1.第二条总线上的root hub为usb2….在这里要注意,对于root hub.会将dev->devpath[0]=’0’.

对于root hub下的设备.它的名称为:总线号+”-”+端口号.例如,第一条usb总线上的root hub的第一个端口上的设备叫”1-0”.第二个端口上的设备名称为”1-1”

对于父结点不是root hub的设备.它的名称为: 总线号+”-”+端口路径. 例如.在第一条usb总线上的root hub的第一个端口上的hub上.第一个端口上的设备名称叫做: 1-0.1 ,第二个端口上的设备名称叫做1-0.2

依次往下推……

如果你到/sys中查看usb设备的话,看到的名称跟这里分析的会不一样.这是因为,对bus_id的处理还没完呢!后面还有相关的处理.等代码分析到了的时候再看. *^_^*.

 

2.2:hcd->driver->reset()的操作.

在我们分析的流程中,对应的接口为uhci_init().代码如下:

static int uhci_init(struct usb_hcd *hcd)

{

     struct uhci_hcd *uhci = hcd_to_uhci(hcd);

     unsigned io_size = (unsigned) hcd->rsrc_len;

     int port;

 

     uhci->io_addr = (unsigned long) hcd->rsrc_start;

 

     /* The UHCI spec says devices must have 2 ports, and goes on to say

      * they may have more but gives no way to determine how many there

      * are.  However according to the UHCI spec, Bit 7 of the port

      * status and control register is always set to 1.  So we try to

      * use this to our advantage.  Another common failure mode when

      * a nonexistent register is addressed is to return all ones, so

      * we test for that also.

      */

     for (port = 0; port < (io_size - USBPORTSC1) / 2; port++) {

         unsigned int portstatus;

 

         portstatus = inw(uhci->io_addr + USBPORTSC1 + (port * 2));

         if (!(portstatus & 0x0080) || portstatus == 0xffff)

              break;

     }

     if (debug)

         dev_info(uhci_dev(uhci), "detected %d ports\n", port);

 

     /* Anything greater than 7 is weird so we'll ignore it. */

     if (port > UHCI_RH_MAXCHILD) {

         dev_info(uhci_dev(uhci), "port count misdetected? "

                   "forcing to 2 ports\n");

         port = 2;

     }

     uhci->rh_numports = port;

 

     /* Kick BIOS off this hardware and reset if the controller

      * isn't already safely quiescent.

      */

     check_and_reset_hc(uhci);

     return 0;

}

代码中hcd_to_uhci()的操作就不做详细分析了.在分配usb_hcd的内存时就已经分析过.

结合UHCI spec来理解这段代码.spec中规定.从I/O空间的0x10处开始,为端口控制状态寄存器(PORTSC).占有两个字节.这个端口也是指UHCI控制器的root hub端口.该寄存器用来表示端口的状态,和操作相应端口.协议中并没有规定一个UHCI有多少个端口,但规定不能够超过8个.另外,协议中规定,PORTSC的bit7始终为1.因此可以根据这个特征来判断端口是否存在.另外,寄存器中的位全为1也是不正常的.

这样就可以计算出UHCI的root hub有多少个端口.然后将值存放到uhci的rh_numports中.

注意代码中取寄存器值的*2操作.这是因为每个PORTSC占两个字节.

剩下的代码就只有check_and_reset_hc( )了.该函数用来检查UHCI是否需要重置.如果需要重置.那就进行UHCI的重置操作.代码如下:

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()的代码.如下所示:

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.

      */

     pci_read_config_word(pdev, UHCI_USBLEGSUP, &legsup);

     if (legsup & ~(UHCI_USBLEGSUP_RO | UHCI_USBLEGSUP_RWC)) {

         dev_dbg(&pdev->dev, "%s: legsup = 0x%04x\n",

                   __FUNCTION__, legsup);

         goto reset_needed;

     }

 

     cmd = inw(base + UHCI_USBCMD);

     if ((cmd & UHCI_USBCMD_RUN) || !(cmd & UHCI_USBCMD_CONFIGURE) ||

              !(cmd & UHCI_USBCMD_EGSM)) {

         dev_dbg(&pdev->dev, "%s: cmd = 0x%04x\n",

                   __FUNCTION__, cmd);

         goto reset_needed;

     }

 

     intr = inw(base + UHCI_USBINTR);

     if (intr & (~UHCI_USBINTR_RESUME)) {

         dev_dbg(&pdev->dev, "%s: intr = 0x%04x\n",

                   __FUNCTION__, intr);

         goto reset_needed;

     }

     return 0;

 

reset_needed:

     dev_dbg(&pdev->dev, "Performing full reset\n");

     uhci_reset_hc(pdev, base);

     return 1;

}

该函数的第一个参数为UHCI对应的pci_dev.第二个参数是I/O区间的起始地址.从代码中看来,有三种情况是需要重置的.这三种情况分别为:

1:如果LEGACY SUPPORT REGISTER寄存器中R/W属性位被置,那就需要重启. LEGACY SUPPORT REGISTER通常是用于legacy 键盘和鼠标.UHCI spec上对其有详细的定义.对照spec.所有R/W属性的位都是某种能力的使能开关.例如,bit13表示USB PIRQ Enable.如果该位被置,表示设备能够产生中断.否则就不可以.

因此,对于这样的位,应该将其初始化.也即将设备的功能关闭.这也很容易理解,为了R/W属性位被置就需要重启UHCI

 

2:USB CMD寄存器的UHCI_USBCMD_RUN被置为1, UHCI_USBCMD_CONFIGURE和UHCI_USBCMD_EGSM位为0的时候需要重启.

UHCI_USBCMD_RUN表示UHCI正在调度数据,处于运行状态.显然,这个时候是应该被重启的

UHCI_USBCMD_CONFIGURE:这个位是由软件控制的,只是起一个标识作用,不会对硬件产生任何影响.如果该位为了1,表示UHCI正处于配置状态.没有处于配置状态,当然就可以重启了.

UHCI_USBCMD_EGSM表示UHCI是否处于Global Suspend mode.在这种模式下,是不会产生数据交互的.显然.如果该位为0.则表示该位不是Global Suspend mode模式,当然就需要重启了.

 

3:USB INTR寄存器中除UHCI_USBINTR_RESUME如果其它位为1.则重启UHCI.

在USB INTR寄存器中,bit4~bit15是保留的,始终为0.其它四位对应了UHCI的四种不同类型的中断,除了bit1表示的Resume interrupt外,其它类型的应该全部都被关掉.

 

如果不需要重启UHCI,直接返回0即可.如果需要重启,则会调转到uhci_reset_hc().代码如下:

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.

      */

     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.

      */

     outw(UHCI_USBCMD_HCRESET, base + UHCI_USBCMD);

     mb();

     udelay(5);

     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.

      */

     outw(0, base + UHCI_USBINTR);

     outw(0, base + UHCI_USBCMD);

}

重启OHCI的步骤如下:

1:将UHCI_USBLEGSUP寄存器中的,RWC属性位清空.

RWC属性即为:该位可读可写.如果往该位写1,就会将该位清0.如果写0则什么都不干.上面代码的操作也就是将RWC位置为0.代码的注释上说的很清楚了.这样会禁用PIRQ和SMI.当然也会关掉Legacy设备的支持.

2:往USB CMD寄存器写入UHCI_USBCMD_HCRESET.用来重启UHCI.

UHCI重启完了之后,又会将该位清空

3:清空USB INTR寄存器和CMD寄存器

对于重启UHCI的情况,返回到check_and_reset_hc()里,还会调用finish_reset().代码如下:

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)

         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 */

}

该函数将UHCI 的各个PORTSC寄存器全部清空.然后设置UHCI为RESET状态.HCD为HALT状态等等.

 

2.3:hcd->driver->start( )的操作

将UHCI重启之后,注册好了中断处理函数就可以启动UHCI了.对应的接口为uhci_start().在分析代码之前,先来了解一下UHCI的调度架构.

从UHCI的spec中摘出一个图,先看下UHCI调度的大概情况:

 

 

从该图中可以看出:

图中的Frame List,翻译成中文叫框架表.TD表示Transfer Descriptor,即表示一次具体的传输.QH表示Queue Head.即传输队列.由上图可见.QH可以和其它的QH组成队列.QH下面又可以挂上TD链.

在UHCI内部.有一个Frame List Address Base Register(FLAB).用来存放Frame List的基地址和当前执行的Frame List序号.每过1ms. FLAB中的index段会加1.它总共占10位,当增加到1023时,又会回转到0.UHCI根据FLAB中存放的Frame list地址,以Index为序号执行Frame List的相关项.

由此可以看到.如果我们要UHCI往设备发送信息.只要将数据打成TD格式的,然后将其链入到相关QH或者TD就好.

从上图中也可以看到传送的优先级关系.先是ISO.然后是INTERRUPT.最后是CONTRL和BULK.关于这四种传输,请自行参照USB2.0 spec.

现在结合代码进行分析,如果代码较长,采用分段分析的方式:

static int uhci_start(struct usb_hcd *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);

 

     if (DEBUG_CONFIGURED) {

         dentry = debugfs_create_file(hcd->self.bus_name,

                   S_IFREG|S_IRUGO|S_IWUSR, uhci_debugfs_root,

                   uhci, &uhci_debug_operations);

         if (!dentry) {

              dev_err(uhci_dev(uhci), "couldn't create uhci "

                       "debugfs entry\n");

              retval = -ENOMEM;

              goto err_create_debug_entry;

         }

         uhci->dentry = dentry;

     }

建立fsbr_timer定时器. 这个定时器跟USB的高速传输有关.在后面再给出详细的分析.忽略选择调试的部份.

 

     //1024个frame 指针

     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;

     }

     memset(uhci->frame, 0, UHCI_NUMFRAMES * sizeof(*uhci->frame));

 

     //cpu 的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;

     }

按照UHCI SPEC的要求,初始化1024个frame list.在这里,UHCI都是使用DMA进行数据交互的.因此调用了dma_alloc_coherent的接口分配DMA内存.物理地址会保存在uhci->frame_dma_handle中.

然后再初始化了1024上cpu frame.这个结构是用来做辅助的,并不会影响具体的硬件

 

     //创建uhci_td的pool

     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;

     }

 

     //创建uhci_qh的pool

     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;

     }

 

     uhci->term_td = uhci_alloc_td(uhci);

     if (!uhci->term_td) {

         dev_err(uhci_dev(uhci), "unable to allocate terminating TD\n");

         goto err_alloc_term_td;

     }

因为以后要经常分配TD和QH结构.为其建立一个POLL.最后,我们还可以看到.初始化了uhci->term_td

 

     //创建11个skelqh

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

         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;

         }

     }

初始化11个QH,即uhci->skeqh[ ]数组

     /*

      * 8 Interrupt queues; link all higher int queues to int1 = async

      */

      //skel_async_qh = skelqh[9]

     for (i = SKEL_ISO + 1; i < SKEL_ASYNC; ++i)

         uhci->skelqh[i]->link = LINK_TO_QH(uhci->skel_async_qh);

     //int1后面没有跟TD或者QH了

     uhci->skel_async_qh->link = UHCI_PTR_TERM;

然后uhci->skelqh[]的2到8项的后续指针都指向了skelqh[9].skelqh[9]指向了UHCI_PTR_TERM.

其实uhci->skelqh[2]~ uhci->skelqh[9].代表8个时间间隔的调度队列.依次被称为int128,int64,int32,int16,int8,int4,int2,int1.即对于int128,即每隔128ms调度一次.int1.即每隔1ms调度一次,

LINK_TO_QH定义如下:

#define LINK_TO_QH(qh)      (UHCI_PTR_QH | cpu_to_le32((qh)->dma_handle))

UHCI_PTR_QH表示链接的是一个QH.然后加上QH的物理地址.相关的部份在UHCI spec上都有详细的描述.请自行查阅有关定义.

UHCI_PTR_TERM定义如下:

#define UHCI_PTR_TERM       __constant_cpu_to_le32(0x0001)

即它的bit0 =1.表示” Empty Frame (pointer is invalid)”.也就是表示,它的后面已经没有有效项了.不要再往后面去遍历了.其实就是一个终止项

 

     //skel_term_qh = skelqh[10]

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

     //term_td后面已经没有数据了

     uhci->term_td->link = UHCI_PTR_TERM;

     uhci->skel_async_qh->element = uhci->skel_term_qh->element =

              LINK_TO_TD(uhci->term_td);

Skel_term_qh定义成skelqh[10].即uhci->skelqh[ ]的最后一项.它自己指向了自己.

然后对于uhci->term_td是Intel PIIX的一个BUG.这部份就不再详细分析了.最后将uhci->term_td挂上了uhci->skelqh[9]和uhci->skelqh[10]

     /*

      * 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 */

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

     uhci->is_initialized = 1;

     start_rh(uhci);

     return 0;

最后,将初始化完成的TD和QH项挂到uhci->frame[].然后再调用configure_hc和start_rh来配置UHCI和启用UHCI.

 

/*

 * error exits:

 */

err_alloc_skelqh:

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

         if (uhci->skelqh[i])

              uhci_free_qh(uhci, uhci->skelqh[i]);

     }

 

     uhci_free_td(uhci, uhci->term_td);

 

err_alloc_term_td:

     dma_pool_destroy(uhci->qh_pool);

 

err_create_qh_pool:

     dma_pool_destroy(uhci->td_pool);

 

err_create_td_pool:

     kfree(uhci->frame_cpu);

 

err_alloc_frame_cpu:A

     dma_free_coherent(uhci_dev(uhci),

              UHCI_NUMFRAMES * sizeof(*uhci->frame),

              uhci->frame, uhci->frame_dma_handle);

 

err_alloc_frame:

     debugfs_remove(uhci->dentry);

 

err_create_debug_entry:

     return retval;

}

这里的TD,QH交错复杂,很容易把头看昏.画了个图.如下 :

 

从上图中可以看出,skelqh[]数组的第0项和第1项是没有经过初始化的.而skelqh[10]又是指向它本身的结点.

经过skelqh[]的初始化后.就可以将它和frmae[]关联起来了.

如下面代码片段所示:

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

 

         /* Only place we don't use the frame list routines */

         uhci->frame[i] = uhci_frame_skel_link(uhci, i);

     }

Uhci_frame_skel_link()的代码如下所示:

static __le32 uhci_frame_skel_link(struct uhci_hcd *uhci, int frame)

{

     int skelnum;

     skelnum = 8 - (int) __ffs(frame | UHCI_NUMFRAMES);

     if (skelnum <= 1)

         skelnum = 9;

     return LINK_TO_QH(uhci->skelqh[skelnum]);

}

这个函数虽然很短小,但是算法却很复杂.

首先来看一下这个函数要做什么事情:

我们在前面说过,int128,int64,int32……int4,int2,int1这样8个QH.我们在后面看到.会将uhci->frame的物理地址存放到UHCI控制器的Frame List Base Address Register中.所以现在要做的事情就是将这些QH与uhci->frame[ ]关联起来.必须要按照相应的时间间隔将QH插入到uhci->frame[]中.例如,例如如果frame[]的n存放int128的QH,那么下一个int128的QH就必须要放到n+128的位置.很明显,对于int1是可以随便放的,也可放可不放.因为int1链接在所有的间隔的QH后面.同时int1又可以单独存放到frame[]中.

另外,从skelqh[2]~skelqh[9]分别表示int128~int1.对于skelqh[0]和skelqh[1]是不需要用到的,而且8- (int) __ffs(frame | UHCI_NUMFRAMES)不可能大于9.所以,将用到skelqh[0]和skelqh[1]的地方.用间隔1ms的skelqh[9]代替.

经过这个函数这后,uhci->frame[]中的各个QH都按照对应的间隔存放到一起了.

 

接着看下面的configure_hc()函数:

static void configure_hc(struct uhci_hcd *uhci)

{

     /* Set the frame length to the default: 1 ms exactly */

     outb(USBSOF_DEFAULT, uhci->io_addr + USBSOF);

 

     /* Store the frame list base address */

     //将frame指针地址写入基地址寄存器

     outl(uhci->frame_dma_handle, uhci->io_addr + USBFLBASEADD);

 

     /* Set the current frame number */

     //当前的frame number

     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 */

     pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP,

              USBLEGSUP_DEFAULT);

}

这个函数比较简单.首先将uhci->frame[ ]的物理地址写到FRBASEADD寄存器中.再将起始帧号写入到FRNUM寄存器.再将状态置为HC_STATE_SUSPENDED.最后到USBLEGSUP中启用PIRQ.这样UHCI就可以产生中断了.

到这里,UHCI已经初始化完成了.现在到了启用它的时候了.

 

返回到uhci_start().流程转入到uhci_start().代码如下:

static void start_rh(struct uhci_hcd *uhci)

{

     //将UHCI的状态置为HC_STATE_RUNNING

     uhci_to_hcd(uhci)->state = HC_STATE_RUNNING;

     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.

      */

      //启用UHCI,设置CF位,表示已经配置好了,指定最大的包长为64 byte

     outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, uhci->io_addr + USBCMD);

     //启用各种中断.包括传输超时或者CRC检验错误,RESUME状态中断.传输完成时产生中断

     //短包中断

     outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP,

              uhci->io_addr + USBINTR);

     mb();

     //然后将UHCI的状态置为UHCI_RH_RUNNING状态.

     uhci->rh_state = UHCI_RH_RUNNING;

     uhci_to_hcd(uhci)->poll_rh = 1;

}

对照代码中的注释和UHCI spec,理解这段代码比较容易.在这里要特别提示一下,什么叫短包中断.

在发送包的时候,如果一次不能够打包完.那就需要将包截短成小包. 一个个传输传输出去.另外,最后一个包可能传输数据会小于允许包大小的最大值.这个的包叫短包.我们在后面的中断处理函数中会有对于短包的处理.到时再详细分析它的处理.

到这里.UHCI就开始调度了.不过这时候.整个调度系统中就只含有一个term_td.然而这个td的初始化如下(在uhci_start[ ]函数中):

uhci_fill_td(uhci->term_td, 0, uhci_explen(0) |

              (0x7f << TD_TOKEN_DEVADDR_SHIFT) | USB_PID_IN, 0);

也就是它的status为空,也就是说,这个TD是一个INACTIVE的.实际这个UHCI是空负荷运行.

 

运行到这里,hcd->start()运行完了.流程会返回到usb_add_hcd()到.的重要操作只剩余register_root_hub().probe过程也要接近尾声了.

 

2.4:register_root_hub()的操作

这个函数主要是对UHCI的root hub进行处理.代码如下:

static int register_root_hub(struct usb_hcd *hcd)

{

     struct device *parent_dev = hcd->self.controller;

     struct usb_device *usb_dev = hcd->self.root_hub;

     const int devnum = 1;

     int retval;

 

     //root hub的devnum为1.下一个设备号从2开始.devnum也即设备地址

     usb_dev->devnum = devnum;

     usb_dev->bus->devnum_next = devnum + 1;

 

     //usb_bus->devmap是一个位图.表示设备号的分配情况

     memset (&usb_dev->bus->devmap.devicemap, 0,

              sizeof usb_dev->bus->devmap.devicemap);

     //将root hub占用位置1

     set_bit (devnum, usb_dev->bus->devmap.devicemap);

     //设置成Address  状态

     usb_set_device_state(usb_dev, USB_STATE_ADDRESS);

 

     mutex_lock(&usb_bus_list_lock);

 

     //端点0的最大发送或者接收值

     usb_dev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(64);

     //取得root hub的设备描述符

     retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);

     if (retval != sizeof usb_dev->descriptor) {

         mutex_unlock(&usb_bus_list_lock);

         dev_dbg (parent_dev, "can't read %s device descriptor %d\n",

                   usb_dev->dev.bus_id, retval);

         return (retval < 0) ? retval : -EMSGSIZE;

     }

 

     //进一步初始化这个设备

     retval = usb_new_device (usb_dev);

     if (retval) {

         dev_err (parent_dev, "can't register root hub for %s, %d\n",

                   usb_dev->dev.bus_id, retval);

     }

     mutex_unlock(&usb_bus_list_lock);

 

     if (retval == 0) {

         spin_lock_irq (&hcd_root_hub_lock);

         //root hub注册成功,将rh_registered设为1

         hcd->rh_registered = 1;

         spin_unlock_irq (&hcd_root_hub_lock);

 

         /* Did the HC die before the root hub was registered? */

         //如果hcd被人为置为了HALT

         if (hcd->state == HC_STATE_HALT)

              usb_hc_died (hcd); /* This time clean up */

     }

 

     return retval;

}

对于代码中较简单部份,结合注释应该就能看懂了.详细分析一下里面涉及到的几个子函数.

第一个是usb_set_device_state().

在分析代码之前,先来看一下USB设备的状态机.在USB2.0的spec上.有一副这样的图:

 

上图表示USB设备的各种状态的转变.

1:如果设备末连接,对应状态为USB_STATE_NOTATTACHED. 这个状态在spec上末表示.是linux中自定义的.实际上它就是表示Attached的一个对立状态.

2:如果设备连上了,但是没有打开电源,处于Attached状态.USB检测到一个设备的时候,会将它初始化这个状态(道理很简单,因为要连上才能检测到 ^_^).可以查看下usb_alloc_dev()函数对状态的初始化.在代码,这个状态对应为: USB_STATE_ATTACHED.

3:如果在上个状态中打开了设备此时打开了电源,设备处于Rowered.在代码中对应USB_STATE_POWERED.

4:如果在上一个状态中,设备被重置,也即初始化,就会转入Default.代码中对应USB_STATE_DEFAULT.

5:如果在上一个状态中,USB为设备分配了地址,就会转入到Address.代码中对应USB_STATE_ADDRESS.

6:如果在上一个状态中,USB完成了设备的配置.就会转入Configured.代码中对应USB_STATE_CONFIGURED.

7:上面除NoAttached和Attached外的所有状态,如果设备被挂起,就会转入Suspended.代码中对应USB_STATE_SUSPENDED.

特别说明:UHCI本身带有root hub功能.hub是一个特殊的USB设备.它的设备地址被固定为1.

 

对应到上面的代码中:

指定root hub的devnum之后,就将其状态设为Address.这个devnum也即设备的地址.设备状态函数为usb_set_device_state().代码如下:

void usb_set_device_state(struct usb_device *udev,

         enum usb_device_state new_state)

{

     unsigned long flags;

 

     spin_lock_irqsave(&device_state_lock, flags);

     //如果设备末连接.不做任何处理

     if (udev->state == USB_STATE_NOTATTACHED)

         ;    /* do nothing */

     else if (new_state != USB_STATE_NOTATTACHED) {

 

         /* root hub wakeup capabilities are managed out-of-band

          * and may involve silicon errata ... ignore them here.

          */

          //如果不是root hub

         if (udev->parent) {

              if (udev->state == USB_STATE_SUSPENDED

                       || new_state == USB_STATE_SUSPENDED)

                   ;    /* No change to wakeup settings */

              else if (new_state == USB_STATE_CONFIGURED)

                   device_init_wakeup(&udev->dev,

                       (udev->actconfig->desc.bmAttributes

                        & USB_CONFIG_ATT_WAKEUP));

              else

                   device_init_wakeup(&udev->dev, 0);

         }

         //如果是从Suspended转到其它状态或者是转到Suspended状态

         //更新active_duration计数

         if (udev->state == USB_STATE_SUSPENDED &&

              new_state != USB_STATE_SUSPENDED)

              udev->active_duration -= jiffies;

         else if (new_state == USB_STATE_SUSPENDED &&

                   udev->state != USB_STATE_SUSPENDED)

              udev->active_duration += jiffies;

 

         //设置状态

         udev->state = new_state;

     }

     else

         //这里是多余的吧?

         recursively_mark_NOTATTACHED(udev);

     spin_unlock_irqrestore(&device_state_lock, flags);

}

这段代码没有什么好多讲的.就是设置状态而已.对于不是root hub的情况,还涉及到了电源管理的情况,在这里不做分析.

 

设置完设备状态之后,调用usb_get_device_descriptor()来取得设备描述符.这个函数涉及到数据的传输实现.在接下来的章节中再做详细分析.在这里只需知道,完成之后会将设备描述符存放在usb_dev->descriptor.

获取到设备描述符之后,就可以获得设备的详细信息了.具体的详细可考阅USB2.0 spec.在这些信息里会包括设备的配置项数目.因此在接下来的操作中,就会将设备所支持的所有配置取出来.这是在usb_new_device()中完成的.代码如下所示:

int usb_new_device(struct usb_device *udev)

{

     int err;

 

     //一些设备的fixup

     usb_detect_quirks(udev);         /* Determine quirks */

     //取得配置描述符

     err = usb_configure_device(udev);    /* detect & probe dev/intfs */

     if (err < 0)

         goto fail;

     /* export the usbdev device-node for libusb */

     //指定设备的设备号

     udev->dev.devt = MKDEV(USB_DEVICE_MAJOR,

              (((udev->bus->busnum-1) * 128) + (udev->devnum-1)));

 

     /* Increment the parent's count of unsuspended children */

     if (udev->parent)

         usb_autoresume_device(udev->parent);

 

     /* Register the device.  The device driver is responsible

      * for adding the device files to sysfs and for configuring

      * the device.

      */

      //注册usb_dev中内嵌的struct device

     err = device_add(&udev->dev);

     if (err) {

         dev_err(&udev->dev, "can't device_add, error %d\n", err);

         goto fail;

     }

 

     /* Tell the world! */

     //输出一些该设备的信息

     announce_device(udev);

     return err;

 

fail:

     usb_set_device_state(udev, USB_STATE_NOTATTACHED);

     return err;

}

这个代码的逻辑比较清淅.首先是usb_detect_quirks()函数,为个函数较简单,不打算进行详细分析,只是简单提一下.有些设备可能在设计存在一些问题.比如说,有的设备在Reset的时候会出现问题,或者在取string描述符的时候对buffer长度有要求.这样的设备都会在linux内核中形成一个链表,即usb_quirk_list.然后将设备的厂商ID,版本等信息与usb_quirk_list上的设备匹配.如果匹配到了,就在usb_dev添上相应的标识,不允许设备进行限制的功能.或者是设备驱动根据修改信息调整相关的操作.

然后是usb_configure_device()函数.这个函数比较重要,跟踪进去分析一下 :

static int usb_configure_device(struct usb_device *udev)

{

     int err;

 

     //取得设备的配置

     if (udev->config == NULL) {

         err = usb_get_configuration(udev);

         if (err < 0) {

              dev_err(&udev->dev, "can't read configurations, error %d\n",

                   err);

              goto fail;

         }

     }

     //如果是无线设备

     if (udev->wusb == 1 && udev->authorized == 0) {

         udev->product = kstrdup("n/a (unauthorized)", GFP_KERNEL);

         udev->manufacturer = kstrdup("n/a (unauthorized)", GFP_KERNEL);

         udev->serial = kstrdup("n/a (unauthorized)", GFP_KERNEL);

     }

     else {

         //usb_cache_string:会将其关的字串存进一个缓冲,用户空间如果要取设备信息的话

         //只要从缓存区取就可以了

         /* read the standard strings and cache them if present */

         udev->product = usb_cache_string(udev, udev->descriptor.iProduct);

         udev->manufacturer = usb_cache_string(udev,

                                  udev->descriptor.iManufacturer);

         udev->serial = usb_cache_string(udev, udev->descriptor.iSerialNumber);

     }

     //OTG: On-The-GO.表示设备有主机控制器的功能

     err = usb_configure_device_otg(udev);

fail:

     return err;

}

首先解释一下CONFIG_USB_OTG的配置选项.一般来说,系统中只能有一个主机控制器.但有时候设备也可以带一个host control的功能.举个例子,数码相机.它接在PC上,做为一般的USB设备使用.它也可以连接在打印上直接打印,这时就会做会一个HC使用.

关于OTG的选择编译代码,这里不做深入研究,忽略掉.

其次要解释的是关于usb_cache_string()的操作.这个函数在取字串的时候还会将字符信息保存到一个缓存区.这样一些读USB信息的工具,就只要从指定的缓存区里取值就可以了.

重点放在usb_get_configuration()函数上.这个函数很烦锁.在分析之前.先来了解一下相关的部份.

USB设备有时候会用做多种用途.比如上面的一个例如.数码相机中的USB,可以用做视频存储,也可以当做U盘来使用.那做为驱动程序.它必须要知道设备有多少种功能.在USB2.0 spec中,用配置表示功能.也就是说,对于上在的例子来说,数码相机的USB设备至少应该有两个配置,一个是视频存储的配置,另外的是U盘的配置.由驱动程序来决定应用哪种配置来使用对应的功能.

接口是USB提供的单元组件.因此,有可能一个配置要使用到多个接口,也有可能一个接口也被多个配置使用的情况,不过不使用接口的配置是不存在的.

根据USB的spec有关设备的检测过程中描述,USB控制器先取得设备描述符,这个描述符里包含了配置的个数.然后再以长度9做为参数去取设备配置描述符头部,这个描述符里包含了描述符的实际长际.最后再以实际长度做参数去取完整的配置描述符.取得的配置描述符不仅包含配置描述符信息,还包括了接口信息和接口所使用的端口信息.

将代码中的有关数据结构如下所示 :

 

大概说一下,usb_dev中的config数组对应每一项配置.config数组的数据结构为struct usb_host_config.这个数据结构中又包含Inft_cache[ ]数组,这个数组用来表示存放接口信息.由于一个接口可能属于同一配置的不同设置,用接口描述符的bAlternateSetting字段来区别接口所属的接口描述符.所以在inft_cache[]对应的usb_host_cache中又有一个扩展项来存放每一个接口描述符.

 

以注释的方式列出usb_get_configuration(),就不做详细分析了,结合上面的说明和代码中的注释来分析这段代码应该没什么问题了.如下:

int usb_get_configuration(struct usb_device *dev)

{

     struct device *ddev = &dev->dev;

     int ncfg = dev->descriptor.bNumConfigurations;

     int result = 0;

     unsigned int cfgno, length;

     unsigned char *buffer;

     unsigned char *bigbuffer;

     struct usb_config_descriptor *desc;

 

     cfgno = 0;

     if (dev->authorized == 0)   /* Not really an error */

         goto out_not_authorized;

     result = -ENOMEM;

     //如果配置项数目超过允许的最大数.将其强制设为最大数

     if (ncfg > USB_MAXCONFIG) {

         dev_warn(ddev, "too many configurations: %d, "

             "using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);

         dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;

     }

 

     //如果一个配置都没有.错误

     if (ncfg < 1) {

         dev_err(ddev, "no configurations\n");

          return -EINVAL;

     }

 

     //dev->config所占内存大小.总共有ncfg个配置项

     length = ncfg * sizeof(struct usb_host_config);

//为dev->config分存内存

     dev->config = kzalloc(length, GFP_KERNEL);

     if (!dev->config)

         goto err2;

 

     length = ncfg * sizeof(char *);

     dev->rawdescriptors = kzalloc(length, GFP_KERNEL);

     if (!dev->rawdescriptors)

         goto err2;

 

     buffer = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL);

     if (!buffer)

         goto err2;

     desc = (struct usb_config_descriptor *)buffer;

 

     result = 0;

     //从设备中依次取出各配置.

     for (; cfgno < ncfg; cfgno++) {

 

         //这里有两次取CONFIG的过程.第一次是9为size取得配置的长度.然后

         //再以特定长度做为size去取完整的config

        

         /* We grab just the first descriptor so we know how long

          * the whole configuration is */

         result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,

             buffer, USB_DT_CONFIG_SIZE);

         if (result < 0) {

              dev_err(ddev, "unable to read config index %d "

                  "descriptor/%s: %d\n", cfgno, "start", result);

              dev_err(ddev, "chopping to %d config(s)\n", cfgno);

              dev->descriptor.bNumConfigurations = cfgno;

              break;

         } else if (result < 4) {

              dev_err(ddev, "config index %d descriptor too short "

                  "(expected %i, got %i)\n", cfgno,

                  USB_DT_CONFIG_SIZE, result);

              result = -EINVAL;

              goto err;

         }

         //取config长度

         length = max((int) le16_to_cpu(desc->wTotalLength),

             USB_DT_CONFIG_SIZE);

 

         /* Now that we know the length, get the whole thing */

         bigbuffer = kmalloc(length, GFP_KERNEL);

         if (!bigbuffer) {

              result = -ENOMEM;

              goto err;

         }

 

         //取完整的config,并将其存放在bigbuffer中

         result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,

             bigbuffer, length);

         if (result < 0) {

              dev_err(ddev, "unable to read config index %d "

                  "descriptor/%s\n", cfgno, "all");

              kfree(bigbuffer);

              goto err;

         }

         if (result < length) {

              dev_warn(ddev, "config index %d descriptor too short "

                  "(expected %i, got %i)\n", cfgno, length, result);

              length = result;

         }

 

         //dev->rawdescriptors中存放了取得的CONFIG

         dev->rawdescriptors[cfgno] = bigbuffer;

         //解析取得的config信息

         result = usb_parse_configuration(&dev->dev, cfgno,

             &dev->config[cfgno], bigbuffer, length);

         if (result < 0) {

              ++cfgno;

              goto err;

         }

     }

     result = 0;

 

err:

     kfree(buffer);

out_not_authorized:

     dev->descriptor.bNumConfigurations = cfgno;

err2:

     if (result == -ENOMEM)

         dev_err(ddev, "out of memory\n");

     return result;

}

对每个配置都会调用usb_parse_configuration()对它进行解析.代码如下:

static int usb_parse_configuration(struct device *ddev, int cfgidx,

    struct usb_host_config *config, unsigned char *buffer, int size)

{

     unsigned char *buffer0 = buffer;

     int cfgno;

     int nintf, nintf_orig;

     int i, j, n;

     struct usb_interface_cache *intfc;

     unsigned char *buffer2;

     int size2;

     struct usb_descriptor_header *header;

     int len, retval;

     u8 inums[USB_MAXINTERFACES], nalts[USB_MAXINTERFACES];

     unsigned iad_num = 0;

 

     //配置描述符信息.这个信息在后面还会修正的

     memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE);

     if (config->desc.bDescriptorType != USB_DT_CONFIG ||

         config->desc.bLength < USB_DT_CONFIG_SIZE) {

         dev_err(ddev, "invalid descriptor for config index %d: "

             "type = 0x%X, length = %d\n", cfgidx,

             config->desc.bDescriptorType, config->desc.bLength);

         return -EINVAL;

     }

     //CONFIG序号

     cfgno = config->desc.bConfigurationValue;

     //完整的配置信息除了标准头部处,还会带上接口和端口描述符信息

     //bLength: 描述符长度

     buffer += config->desc.bLength;

     //接口描述符大小

     size -= config->desc.bLength;

     //接口数目

     nintf = nintf_orig = config->desc.bNumInterfaces;

 

     //接口数目太多

     if (nintf > USB_MAXINTERFACES) {

         dev_warn(ddev, "config %d has too many interfaces: %d, "

             "using maximum allowed: %d\n",

             cfgno, nintf, USB_MAXINTERFACES);

         nintf = USB_MAXINTERFACES;

     }

 

     /* Go through the descriptors, checking their length and counting the

      * number of altsettings for each interface */

     n = 0;

     for ((buffer2 = buffer, size2 = size);

           size2 > 0;

          (buffer2 += header->bLength, size2 -= header->bLength)) {

 

         if (size2 < sizeof(struct usb_descriptor_header)) {

              dev_warn(ddev, "config %d descriptor has %d excess "

                  "byte%s, ignoring\n",

                  cfgno, size2, plural(size2));

              break;

         }

 

         header = (struct usb_descriptor_header *) buffer2;

         if ((header->bLength > size2) || (header->bLength < 2)) {

              dev_warn(ddev, "config %d has an invalid descriptor "

                  "of length %d, skipping remainder of the config\n",

                  cfgno, header->bLength);

              break;

         }

 

         //如果后面跟的是INTERFACE的描述符

         if (header->bDescriptorType == USB_DT_INTERFACE) {

              struct usb_interface_descriptor *d;

              int inum;

 

              d = (struct usb_interface_descriptor *) header;

              //如果长度太短,不合法.继续下一个interface config

              if (d->bLength < USB_DT_INTERFACE_SIZE) {

                   dev_warn(ddev, "config %d has an invalid "

                       "interface descriptor of length %d, "

                       "skipping\n", cfgno, d->bLength);

                   continue;

              }

 

              //接号序号   

              inum = d->bInterfaceNumber;

              //接口序号超过了最大值

              if (inum >= nintf_orig)

                   dev_warn(ddev, "config %d has an invalid "

                       "interface number: %d but max is %d\n",

                       cfgno, inum, nintf_orig - 1);

 

              /* Have we already encountered this interface?

               * Count its altsettings */

               //nalts[ ]是相同端口出现次数的统计

               //如果在inums[ ]中已经包含这个接口了.

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

                   if (inums[i] == inum)

                       break;

              }

 

              //如果已经在inums[ ]了,增加nalts[]相应项的统计计数

              if (i < n) {

                   if (nalts[i] < 255)

                       ++nalts[i];

              }

              //否则将序号设置进inums[ ]中,nalts[]相应项为1.因为还是第一次出现

              else if (n < USB_MAXINTERFACES) {

                   inums[n] = inum;

                   nalts[n] = 1;

                   ++n;

              }

 

         }

         //minor usb only

         else if (header->bDescriptorType ==

                   USB_DT_INTERFACE_ASSOCIATION) {

              if (iad_num == USB_MAXIADS) {

                   dev_warn(ddev, "found more Interface "

                              "Association Descriptors "

                              "than allocated for in "

                              "configuration %d\n", cfgno);

              } else {

                   config->intf_assoc[iad_num] =

                       (struct usb_interface_assoc_descriptor

                       *)header;

                   iad_num++;

              }

 

         } else if (header->bDescriptorType == USB_DT_DEVICE ||

                  header->bDescriptorType == USB_DT_CONFIG)

              dev_warn(ddev, "config %d contains an unexpected "

                  "descriptor of type 0x%X, skipping\n",

                  cfgno, header->bDescriptorType);

 

     }    /* for ((buffer2 = buffer, size2 = size); ...) */

     //size是有效的interface config数据部份的长度

     size = buffer2 - buffer;

     config->desc.wTotalLength = cpu_to_le16(buffer2 - buffer0);

 

     //n是inums[ ]数组的有效项数,也即端口个数.

     //更新n,使表示实际的端口个数

     if (n != nintf)

         dev_warn(ddev, "config %d has %d interface%s, different from "

             "the descriptor's value: %d\n",

             cfgno, n, plural(n), nintf_orig);

     else if (n == 0)

         dev_warn(ddev, "config %d has no interfaces?\n", cfgno);

     config->desc.bNumInterfaces = nintf = n;

 

     /* Check for missing interface numbers */

     //检查inums[ ]是否准确.如果有异常,打印出警告信息

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

         for (j = 0; j < nintf; ++j) {

              if (inums[j] == i)

                   break;

         }

         if (j >= nintf)

              dev_warn(ddev, "config %d has no interface number "

                  "%d\n", cfgno, i);

     }

 

     /* Allocate the usb_interface_caches and altsetting arrays */

     //每一个接口号对应intf_cache[ ]一项.然nals[ ]表示该接口号的个数

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

         j = nalts[i];

         //

         if (j > USB_MAXALTSETTING) {

              dev_warn(ddev, "too many alternate settings for "

                  "config %d interface %d: %d, "

                  "using maximum allowed: %d\n",

                  cfgno, inums[i], j, USB_MAXALTSETTING);

              nalts[i] = j = USB_MAXALTSETTING;

         }

 

         len = sizeof(*intfc) + sizeof(struct usb_host_interface) * j;

         config->intf_cache[i] = intfc = kzalloc(len, GFP_KERNEL);

         if (!intfc)

              return -ENOMEM;

         kref_init(&intfc->ref);

     }

 

     /* Skip over any Class Specific or Vendor Specific descriptors;

      * find the first interface descriptor */

      //config->extar:config的扩展部份,即interface config的那部份

     config->extra = buffer;

     //找到一下个USB_DT_INTERFACE项.返回跳过去的数据长度和描述符项

     i = find_next_descriptor(buffer, size, USB_DT_INTERFACE,

         USB_DT_INTERFACE, &n);

     config->extralen = i;

     //到现在为止,config->extra返回的是下一个配置描述符起始地址

     //config->extralen下一个配置描述符地址和config->extra的偏移值

     if (n > 0)

         dev_dbg(ddev, "skipped %d descriptor%s after %s\n",

             n, plural(n), "configuration");

     //现在interface config有效的起点位置,size有效大小

     buffer += i;

     size -= i;

 

     /* Parse all the interface/altsetting descriptors */

     while (size > 0) {

         retval = usb_parse_interface(ddev, cfgno, config,

             buffer, size, inums, nalts);

         if (retval < 0)

              return retval;

 

         buffer += retval;

         size -= retval;

     }

 

     /* Check for missing altsettings */

     //检查config->inft)cache[]中是否有异常

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

         intfc = config->intf_cache[i];

         for (j = 0; j < intfc->num_altsetting; ++j) {

              for (n = 0; n < intfc->num_altsetting; ++n) {

                   if (intfc->altsetting[n].desc.

                       bAlternateSetting == j)

                       break;

              }

              if (n >= intfc->num_altsetting)

                   dev_warn(ddev, "config %d interface %d has no "

                       "altsetting %d\n", cfgno, inums[i], j);

         }

     }

 

     return 0;

}

usb_parse_interface()代码如下:

static int usb_parse_interface(struct device *ddev, int cfgno,

    struct usb_host_config *config, unsigned char *buffer, int size,

    u8 inums[], u8 nalts[])

{

     unsigned char *buffer0 = buffer;

     struct usb_interface_descriptor  *d;

     int inum, asnum;

     struct usb_interface_cache *intfc;

     struct usb_host_interface *alt;

     int i, n;

     int len, retval;

     int num_ep, num_ep_orig;

 

     d = (struct usb_interface_descriptor *) buffer;

     buffer += d->bLength;

     size -= d->bLength;

 

     if (d->bLength < USB_DT_INTERFACE_SIZE)

         goto skip_to_next_interface_descriptor;

 

     /* Which interface entry is this? */

     intfc = NULL;

     inum = d->bInterfaceNumber;

     //config->intf_cache保存着端点的相关信息

     for (i = 0; i < config->desc.bNumInterfaces; ++i) {

         if (inums[i] == inum) {

              intfc = config->intf_cache[i];

              break;

         }

     }

     //保存的端口总数超过了最大值,非法

     if (!intfc || intfc->num_altsetting >= nalts[i])

         goto skip_to_next_interface_descriptor;

 

     /* Check for duplicate altsetting entries */

     //标识字段

     asnum = d->bAlternateSetting;

     //如果存在相同的.非法

     for ((i = 0, alt = &intfc->altsetting[0]);

           i < intfc->num_altsetting;

          (++i, ++alt)) {

         if (alt->desc.bAlternateSetting == asnum) {

              dev_warn(ddev, "Duplicate descriptor for config %d "

                  "interface %d altsetting %d, skipping\n",

                  cfgno, inum, asnum);

              goto skip_to_next_interface_descriptor;

         }

     }

 

     //更新计数

     ++intfc->num_altsetting;

     //如果合法的话,那alt就是指向一个空的接点描述符

     memcpy(&alt->desc, d, USB_DT_INTERFACE_SIZE);

 

     /* Skip over any Class Specific or Vendor Specific descriptors;

      * find the first endpoint or interface descriptor */

      //下一个endpoint descriptors的地址

     alt->extra = buffer;

     i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,

         USB_DT_INTERFACE, &n);

     //alt->extar+alt->extralen表示下一个描述符地址

     alt->extralen = i;

     if (n > 0)

         dev_dbg(ddev, "skipped %d descriptor%s after %s\n",

             n, plural(n), "interface");

     //下个intreface desp的地址

     buffer += i;

     size -= i;

 

     //接口中端点描述符的个数. 注意在这里将alt->desc.bNumEndpoints清0了

     /* Allocate space for the right(?) number of endpoints */

     num_ep = num_ep_orig = alt->desc.bNumEndpoints;

     alt->desc.bNumEndpoints = 0;         /* Use as a counter */

     if (num_ep > USB_MAXENDPOINTS) {

         dev_warn(ddev, "too many endpoints for config %d interface %d "

             "altsetting %d: %d, using maximum allowed: %d\n",

             cfgno, inum, asnum, num_ep, USB_MAXENDPOINTS);

         num_ep = USB_MAXENDPOINTS;

     }

 

     if (num_ep > 0) {

         /* Can't allocate 0 bytes */

         len = sizeof(struct usb_host_endpoint) * num_ep;

         alt->endpoint = kzalloc(len, GFP_KERNEL);

         if (!alt->endpoint)

              return -ENOMEM;

     }

 

     /* Parse all the endpoint descriptors */

     n = 0;

     while (size > 0) {

         if (((struct usb_descriptor_header *) buffer)->bDescriptorType

              == USB_DT_INTERFACE)

              break;

         retval = usb_parse_endpoint(ddev, cfgno, inum, asnum, alt,

             num_ep, buffer, size);

         if (retval < 0)

              return retval;

         ++n;

 

         buffer += retval;

         size -= retval;

     }

 

     if (n != num_ep_orig)

         dev_warn(ddev, "config %d interface %d altsetting %d has %d "

             "endpoint descriptor%s, different from the interface "

             "descriptor's value: %d\n",

             cfgno, inum, asnum, n, plural(n), num_ep_orig);

     return buffer - buffer0;

 

skip_to_next_interface_descriptor:

     i = find_next_descriptor(buffer, size, USB_DT_INTERFACE,

         USB_DT_INTERFACE, NULL);

     return buffer - buffer0 + i;

}

usb_parse_endpoint()代码如下:

static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum,

    int asnum, struct usb_host_interface *ifp, int num_ep,

    unsigned char *buffer, int size)

{

     unsigned char *buffer0 = buffer;

     struct usb_endpoint_descriptor *d;

     struct usb_host_endpoint *endpoint;

     int n, i, j;

 

     d = (struct usb_endpoint_descriptor *) buffer;

     buffer += d->bLength;

     size -= d->bLength;

 

     //判断长度是否合法

     if (d->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE)

         n = USB_DT_ENDPOINT_AUDIO_SIZE;

     else if (d->bLength >= USB_DT_ENDPOINT_SIZE)

         n = USB_DT_ENDPOINT_SIZE;

     else {

         dev_warn(ddev, "config %d interface %d altsetting %d has an "

             "invalid endpoint descriptor of length %d, skipping\n",

             cfgno, inum, asnum, d->bLength);

         goto skip_to_next_endpoint_or_interface_descriptor;

     }

 

     //取得端点的地址,也就是端口号

     i = d->bEndpointAddress & ~USB_ENDPOINT_DIR_MASK;

     //不可能会超16个端点,也不可能

     if (i >= 16 || i == 0) {

         dev_warn(ddev, "config %d interface %d altsetting %d has an "

             "invalid endpoint with address 0x%X, skipping\n",

             cfgno, inum, asnum, d->bEndpointAddress);

         goto skip_to_next_endpoint_or_interface_descriptor;

     }

 

     /* Only store as many endpoints as we have room for */

     //注意在前面调用函数中已经将ifp->desc.bNumEndpoints清0了,以后每处理

     //一个端点描述符,都会将这个成员值+1

     if (ifp->desc.bNumEndpoints >= num_ep)

         goto skip_to_next_endpoint_or_interface_descriptor;

 

     //保存端点描述符信息,并更新端点数目

     endpoint = &ifp->endpoint[ifp->desc.bNumEndpoints];

     ++ifp->desc.bNumEndpoints;

 

     memcpy(&endpoint->desc, d, n);

     INIT_LIST_HEAD(&endpoint->urb_list);

 

     /* Fix up bInterval values outside the legal range. Use 32 ms if no

      * proper value can be guessed. */

     i = 0;        /* i = min, j = max, n = default */

     j = 255;

     //根据不同的传输类型,计算间隔时间

     if (usb_endpoint_xfer_int(d)) {

         i = 1;

         switch (to_usb_device(ddev)->speed) {

         case USB_SPEED_HIGH:

              /* Many device manufacturers are using full-speed

               * bInterval values in high-speed interrupt endpoint

               * descriptors. Try to fix those and fall back to a

               * 32 ms default value otherwise. */

              n = fls(d->bInterval*8);

              if (n == 0)

                   n = 9;   /* 32 ms = 2^(9-1) uframes */

              j = 16;

              break;

         default:      /* USB_SPEED_FULL or _LOW */

              /* For low-speed, 10 ms is the official minimum.

               * But some "overclocked" devices might want faster

               * polling so we'll allow it. */

              n = 32;

              break;

         }

     } else if (usb_endpoint_xfer_isoc(d)) {

         i = 1;

         j = 16;

         switch (to_usb_device(ddev)->speed) {

         case USB_SPEED_HIGH:

              n = 9;        /* 32 ms = 2^(9-1) uframes */

              break;

         default:      /* USB_SPEED_FULL */

              n = 6;        /* 32 ms = 2^(6-1) frames */

              break;

         }

     }

     if (d->bInterval < i || d->bInterval > j) {

         dev_warn(ddev, "config %d interface %d altsetting %d "

             "endpoint 0x%X has an invalid bInterval %d, "

             "changing to %d\n",

             cfgno, inum, asnum,

             d->bEndpointAddress, d->bInterval, n);

         endpoint->desc.bInterval = n;

     }

 

     /* Some buggy low-speed devices have Bulk endpoints, which is

      * explicitly forbidden by the USB spec.  In an attempt to make

      * them usable, we will try treating them as Interrupt endpoints.

      */

     if (to_usb_device(ddev)->speed == USB_SPEED_LOW &&

              usb_endpoint_xfer_bulk(d)) {

         dev_warn(ddev, "config %d interface %d altsetting %d "

             "endpoint 0x%X is Bulk; changing to Interrupt\n",

             cfgno, inum, asnum, d->bEndpointAddress);

         endpoint->desc.bmAttributes = USB_ENDPOINT_XFER_INT;

         endpoint->desc.bInterval = 1;

         if (le16_to_cpu(endpoint->desc.wMaxPacketSize) > 8)

              endpoint->desc.wMaxPacketSize = cpu_to_le16(8);

     }

 

     /* Skip over any Class Specific or Vendor Specific descriptors;

      * find the next endpoint or interface descriptor */

      //同之前分析的一样,下一个描述符的有效地址和偏移

     endpoint->extra = buffer;

     i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,

         USB_DT_INTERFACE, &n);

     endpoint->extralen = i;

     if (n > 0)

         dev_dbg(ddev, "skipped %d descriptor%s after %s\n",

             n, plural(n), "endpoint");

     return buffer - buffer0 + i;

 

skip_to_next_endpoint_or_interface_descriptor:

     i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,

         USB_DT_INTERFACE, NULL);

     return buffer - buffer0 + i;

}

 

到这里,root hub对应的配置,接口,端点信息都可以在usb_dev中找到了.UHCI的初始化工作就全部完成了.在之后的分析中,会经常涉及到具体的信息传输过程.在前面的代码中遇到也一笔代过了.为了以后的分析方便,在下一节里,对每个类型的传输过程做一个全面的分析.

时间: 2024-11-10 05:28:37

linux设备驱动之USB主机控制器驱动分析 【转】的相关文章

【驱动】USB驱动&amp;#183;入门【转】

转自:http://www.cnblogs.com/lcw/p/3159371.html Preface USB是目前最流行的系统总线之一.随着计算机周围硬件的不断扩展,各种设备使用不同的总线接口,导致计算机外部总线种类繁多,管理困难.USB总线正是因此而诞生的. USB总线提供了所有外部设备的统一连接方式,并且支持热插拔,方便了厂商开发设备和用户使用设备. USB遵循原则 USB的设计目标是对现有的PC机体系进行扩充,但是目前不仅是PC机,许多的嵌入式系统都开始支持USB总线和接口标准.USB

USB WDM驱动开发实例 bulkusb

参考书籍<<Windows驱动开发技术详解>> 1.该示例介绍如何进行USB驱动的开发. 它全面地支持了即插即用的处理, 也很全面地支持了电源管理,同时很好地支持了USB设备的bulk读写. 如果从头开发 USB 驱动,往往很难达到USB驱动的稳定性,所以建议在此驱动修改基础上进行USB驱动开发.     2.功能驱动与物理总线驱动 程序员不需要了解USB如何将请求化成数据包等细节,只需要指定何种管道,发送何种数据即可. 当功能驱动想向某个管道发出读写请求时,首先构造请求发给USB

linux下usb转串口驱动分析【转】

转自:http://blog.csdn.net/txxm520/article/details/8934706 首先说一下linux的风格,个人理解 1. linux大小结构体其实是面向对象的方法,(如果把struct 比作类,kmalloc就是类的实例化,结构体里面的函数指针就是方法,还有重构,多态) 2. 在linux里面,设备是对象,驱动也是对象,并且这两个是分开的   现在我们来看驱动的总体架构 并不用太在意这个图,对用户来说usb_serial设备就是普通的串口设备 我们可以看驱动里面

Linux 串口、usb转串口驱动分析(2-2) 【转】

转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26807463&id=4186852 Linux 串口.usb转串口驱动分析2 内核版本:2.6.35.6                                                                        荣鹏140319   声明:图和个别段落(我做了小的修改)是直接从网上截取   目标:主要是想对Linux 串口.usb转串

Linux 串口、usb转串口驱动分析(2-1) 【转】

转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26807463&id=4186851   Linux 串口.usb转串口驱动分析1 内核版本:2.6.35.6                                                                        荣鹏140319   声明:图和个别段落(我做了小的修改)是直接从网上截取   目标:主要是想对Linux 串口.usb

Linux驱动之usb鼠标

写驱动的时候一定要参考内核源代码相应的驱动,不要总是copy别人的代码,还要看看自己的内核函数接口,不同内核版本不一定一样,在这里我得吐槽一下FL2440开发板里面linux2.6.28.7这个内核错误的地方很多,你们学习的时候自己要善于总结方法,千万不要气馁,当你自己解决了一个问题的时候,你会学到很多东西,不要一味让别人给你做好咯,自己只是copy,你这样是学不到东西的. 学习驱动这一块自己还得多花自己去琢磨,去分析源代码,理解整个框架和模型.在这里给大家推荐一本书,<深入Linux设备驱动程

《Linux 设备驱动开发详解(第2版)》——1.4 Linux设备驱动

1.4 Linux设备驱动 Linux 设备驱动开发详解(第2版)1.4.1 设备的分类及特点 计算机系统的硬件主要由CPU.存储器和外设组成.随着IC制作工艺的发展,目前,芯片的集成度越来越高,往往在CPU内部就集成了存储器和外设适配器.譬如,相当多的ARM.PowerPC.MIPS等处理器都集成了UART.I2C控制器.USB控制器.SDRAM控制器等,有的处理器还集成了片内RAM和Flash. 驱动针对的对象是存储器和外设(包括CPU内部集成的存储器和外设),而不是针对CPU核.Linux

《Linux 设备驱动开发详解(第2版)》——导读

前言 本书第1版在2008年初出版以后,受到广大读者的支持和厚爱,累计销售1.6万册,从几年的市场和读者反馈看,在第1版中还存在一些不足,主要是以下几方面. 没有现成的开发环境,读者需要从头到尾构建,而构建需要花费很长的时间,许多时候会不成功,加之配套光盘中的实例没有Makefile,更加大了操作的难度. 没有配套的开发板,大量的基于S3C2410的实例读者身边如果没有可以直接运行的平台,就无法亲身体验这些驱动. 个别内容实用性不强或过于陈旧,也有个别知识点的讲解语言晦涩,读者不易理解,如pla

《Linux设备驱动开发详解 A》一一2.3 接口与总线

2.3 接口与总线 2.3.1 串口 RS-232.RS-422与RS-485都是串行数据接口标准,最初都是由电子工业协会(EIA)制订并发布的. RS-232在1962年发布,命名为EIA-232-E.之后发布的RS-422定义了一种平衡通信接口,它是一种单机发送.多机接收的单向.平衡传输规范,被命名为TIA/EIA-422-A标准.RS-422改进了RS-232通信距离短.速率低的缺点.为进一步扩展应用范围,EIA又于1983年在RS-422的基础上制定了RS-485标准,增加了多点.双向通