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