您的位置:首页 > 运维架构 > Linux

linux设备驱动之USB主机控制器驱动分析 (一)

2014-07-30 10:17 543 查看
一:前言

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

}

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

未完待续......
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: