linux内核usb配置主机模式,linux设备驱动之USB主机控制器驱动分析

阅读: 评论:0

linux内核usb配置主机模式,linux设备驱动之USB主机控制器驱动分析

linux内核usb配置主机模式,linux设备驱动之USB主机控制器驱动分析

------------------------------------------

本文系本站原创,欢迎转载!

转载请注明出处:/

------------------------------------------

一:前言

Usb是一个很复杂的系统.在usb2.0规范中,将其定义成了一个分层模型.linux中的代码也是按照这个分层模型来设计的.具体的分为usb设备,hub和主机控制器三部份.在阅读代码的时候,必须要参考相应的规范.最基本的就是USB2.0的spec.它定义了USB协议.另外的一个是USB控制器的规范.有UHCI,EHCI,OHCI三种.其中UHCI是Intel推出的一种USB控制器标准.它将很多功能交给软件处理.相比之下,它也是最为复杂的.因此,本文档以UHCI为例分析.另外,在分析的过程中参考了情景分析和fudan_abc的<>.正是因为踩在许多牛人的肩膀上,才使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 "%sn",

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

retval = -EBUSY;

goto err2;

}

hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);

if (hcd->regs == NULL) {

dev_dbg(&dev->dev, "error mapping memoryn");

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

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, %dn", 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 failedn");

return NULL;

}

dev_set_drvdata(dev, hcd);

kref_init(&hcd->kref);

usb_bus_init(&hcd->self);

hcd-&ller = 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总线. ller表示该总线上的控制器,也就是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-&ller, "%sn", 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-&ller, "pool alloc failedn");

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-&ller, "unable to allocate root hubn");

retval = -ENOMEM;

goto err_allocate_root_hub;

}

//OHCI定义于usb1.1只能支持全速

rhdev->speed = (hcd->driver->flags & HCD_USB2) ? USB_SPEED_HIGH :

USB_SPEED_FULL;

hcd-&_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-&ller, "can't setupn");

goto err_hcd_driver_setup;

}

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

if (device_can_wakeup(hcd-&ller)

&& device_can_wakeup(&hcd-&_hub->dev))

dev_dbg(hcd-&ller, "supports USB remote wakeupn");

/* 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-&ller,

"request interrupt %d failedn", irqnum);

goto err_request_irq;

}

hcd->irq = irqnum;

dev_info(hcd-&ller, "irq %d, %s 0x%08llxn", 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-&ller, "%s 0x%08llxn",

(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-&ller, "startup error %dn", retval);

goto err_hcd_driver_start;

}

本文发布于:2024-02-02 10:44:30,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170684187143279.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:主机   内核   控制器   模式   设备
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23