程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

linux驱动移植-USB主机控制器驱动

linux驱动移植-usb驱动基础中我们介绍了USB主机控制器主要有OHCI、UHCI,EHCI,xHCI,其中HCI表示Host Controller Interface。usb主机控制器通过usb根集线器和其他的usb设备相连。

linux驱动移植-usb总线设备驱动中我们介绍了usb子系统的初始化过程、以及usb设备/接口驱动的注册,这一节我们将介绍usb主机控制驱动和根hub的注册过程。

一、S3C2440USB主机控制器

1.1 基本概述

S3C2440支持两个端口的USB主机接口:

  • 兼容OHCI Rev 1.0
  • 兼容USB Rev1.1
  • 两路下行端口
  • 支持低速以及全速USB设备

1.2 USB主机控制器结构图

1.3 Mini2440 usb接线图

我使用的开发板为Mini2440,我们来看一下开发板的接线图:

Mini2440开发板只有一个usb主机接口和一个USB设备接口,均为usb全速接口。其中usb设备接口是用来和PC接连接,usb主机接口是用来连接usb外设的。这里的usb主机/设备是针对于开发板来说的。

其中usb设备的D+是由GPC5来控制的,如果GPC5 输出高电平,则D+ 上相当于通过上拉电阻接到+5V ,则设备启用;如果GPC5 输出低电平,则D+ 上相当于通过下拉电阻接到GND ,则设备禁用。

1.4 USB总线物理拓扑结构

USB总线可能呈现出树状的拓扑接口吗,USB标准上说USB总线物理拓扑是一种分层星型结构,如下图所示:

从树结构我们可以看到:

  • 树的根节点是usb主机控制器,连接在usb主机控制器上的是usb根集线器(root hub);
  • usb集线器(hub)可以将一个usb接口扩展成多个usb接口,扩展出的usb接口又可以通过usb集线器(hub)扩展,每个usb接口都可以接usb设备;

所以可以总结出usb主机控制器需要完成的任务:

  • 完成控制器硬件的初始化,使得USB PHY工作在主状态下;
  • 从软件上构造出主机控制器结构体,并提供主机控制器驱动的操作函数,用于数据交互等;
  • 既然根hub是一个usb设备,对于驱动中,一定需要创建一个usb设备来表示这个根hub;
  • 外部设备插在根hub上,软件需要完成对根hub的监控,以及usb设备插入后的创建操作;

二、USB主机控制器驱动分析

usb主机控制器驱动一般以platform的形式将驱动注册进内核,,因此我们需要从名字为"s3c2410-ohci"的platform设备/驱动注册说起。

这里我们主要说一下当有usb设备接入时,usb hub端口状态会发生改变,会触发中断处理函数hub_irq,将work添加到工作队列中,work线程会执行工作任务hub->events中的工作函数hub_event,该函数主要做了以下事情:

  • 为usb设备申请usb_device结构;
  • 为usb设备分配一个设备地址0~127之间(由于usb设备刚插入,此时还未为usb设备分配设备地址,数据是通过管道usb_sndaddr0pip()发送的)
drivers/usb/core/hub.c:4424:#define usb_sndaddr0pipe()  (PIPE_CONTROL << 30)   // 采用控制传输方式、设备地址为0、端点号为0、传输方向主机->设备
  • 调用usb_new_device函数:
    • 先是调用usb_enumerate_device获取usb设备配置描述信息(这里枚举获取的pid、vid等用于设备驱动匹配的id使用):
    • 然后调用device_add将设备加入到设备链表中;

注意:hub_irq并不是一个真正的硬件中断,实际这是通过一个定时器实现的,定时器每隔一定时间执行一次,用来检测usb hub段口有没有发生改变,如果发生改变,将会触发hub_irq执行。

使用定时器查询的主要原因是usb没有中断USB控制器的能力,所以当usb设备接入之后,获取usb输入的信息是无法通过中断方式来获取,只能通过定时器定时轮训获取。

2.1 platform设备注册(s3c2410-ohci)

2.1.1 platform设备定义

在 arch/arm/plat-samsung/devs.c我们可以看到如果定义了宏CONFIG_S3C_DEV_USB_HOST,将会定义platform设备s3c_device_ohci:

复制代码
/* USB */

#ifdef CONFIG_S3C_DEV_USB_HOST
static struct resource s3c_usb_resource[] = {
        [0] = DEFINE_RES_MEM(S3C_PA_USBHOST, SZ_256),
        [1] = DEFINE_RES_IRQ(IRQ_USBH),
};

struct platform_device s3c_device_ohci = {
        .name           = "s3c2410-ohci",
        .id             = -1,
        .num_resources  = ARRAY_SIZE(s3c_usb_resource),
        .resource       = s3c_usb_resource,
        .dev            = {
                .dma_mask               = &samsung_device_dma_mask,
                .coherent_dma_mask      = DMA_BIT_MASK(32),
        }
};

/*
 * s3c_ohci_set_platdata - initialise OHCI device platform data
 * @info: The platform data.
 *
 * This call copies the @info passed in and sets the device .platform_data
 * field to that copy. The @info is copied so that the original can be marked
 * __initdata.
 */

void __init s3c_ohci_set_platdata(struct s3c2410_hcd_info *info)
{
        s3c_set_platdata(info, sizeof(struct s3c2410_hcd_info),
                         &s3c_device_ohci);
}
#endif /* CONFIG_S3C_DEV_USB_HOST */
复制代码

这里我们首先来看一下设备侧提供的信息s3c_usb_resource,resources中指定了usb主机控制器的寄存器范围以及中断:

  • 定义内存资源,起始地址 0x49000000(USB主机控制器的OHCI寄存器基地址)、大小为256个字节;
  • 定义中断资源,中断编号为IRQ_USBH;

如果我们调用了s3c_set_platdata函数,该函数将会设置s3c_device_ohci->dev.platform_data=info。。

2.1.2  s3c_set_platdata

s3c_set_platdata定义在arch/arm/plat-samsung/platformdata.c文件中:

复制代码
void __init *s3c_set_platdata(void *pd, size_t pdsize,   // pd = smdk2440_fb_info , pdev = s3c_device_lcd
                              struct platform_device *pdev)
{
        void *npd;

        if (!pd) {
                /* too early to use dev_name(), may not be registered */
                printk(KERN_ERR "%s: no platform data supplied\n", pdev->name);
                return NULL;
        }

        npd = kmemdup(pd, pdsize, GFP_KERNEL);
        if (!npd)
                return NULL;

        pdev->dev.platform_data = npd;
        return npd;
}
复制代码

这个函数主要是用来设置pdev->dev的platform_data成员,是个void *类型,可以给平台driver提供各种数据(比如:GPIO引脚等等)。

2.1.3 usb_simtec_init

那么s3c_ohci_set_platdata是在哪里被调用的呢?实际是在usb_simtec_init中调用的,该函数定义在arch/arm/mach-s3c24xx/simtec-usb.c:

复制代码
int __init usb_simtec_init(void)
{
        int ret;

        printk("USB Power Control, Copyright 2004 Simtec Electronics\n");

        ret = gpio_request(S3C2410_GPB(4), "USB power control");
        if (ret < 0) {
                pr_err("%s: failed to get GPB4\n", __func__);
                return ret;
        }

        ret = gpio_request(S3C2410_GPG(10), "USB overcurrent");
        if (ret < 0) {
                pr_err("%s: failed to get GPG10\n", __func__);
                gpio_free(S3C2410_GPB(4));
                return ret;
        }

        /* turn power on */
        gpio_direction_output(S3C2410_GPB(4), 1);
        gpio_direction_input(S3C2410_GPG(10));

        s3c_ohci_set_platdata(&usb_simtec_info);
        return 0;
}
复制代码

这里使用调用gpio_request函数申请GPB4、GPG10引脚,这个函数有一个重要的“检测”功能,就是如果其它地方申请了这个IO,那么这里就会返回错误,提示已经被占用了。

然后设置GPB4引脚输出高电平,设置GPG10引脚为输入。这里设置这两个端口有啥用么,实际上我们去看一下S3C2440芯片手册这两个端口的说明,我们会发现:

GPB4 可复用为 TCLK0;GPG10 可复用为 EINT18/nCTS1。好像这都都和usb无关吧。

查看MIni2440开发板的电路图,我们会发现开发板这两个引脚根本就没有接任何外设。

最后调用s3c_ohci_set_platdata,参数为usb_simtec_info。

复制代码
static struct s3c2410_hcd_info usb_simtec_info __initdata = {
        .port[0]        = {
                .flags  = S3C_HCDFLG_USED
        },
        .port[1]        = {
                .flags  = S3C_HCDFLG_USED
        },

        .power_control  = usb_simtec_powercontrol,
        .enable_oc      = usb_simtec_enableoc,
};
复制代码
2.1.4 platform设备注册

到了这里我们定义了usb相关的platform_device设备,并进行了初始化,那platform设备啥时候注册的呢?

我们定位到arch/arm/mach-s3c24xx/mach-smdk2440.c文件中smdk2440_machine_init函数:

复制代码
static void __init smdk2440_machine_init(void)
{
        s3c24xx_fb_set_platdata(&smdk2440_fb_info);
        s3c_i2c0_set_platdata(NULL);

        platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
        smdk_machine_init();
}

MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <ben-linux@fluff.org> */
        .atag_offset    = 0x100,

        .init_irq       = s3c2440_init_irq,
        .map_io         = smdk2440_map_io,
        .init_machine   = smdk2440_machine_init,
        .init_time      = smdk2440_init_time,
MACHINE_END
复制代码

实际上在开发板初始化的过程中并没有调用usb_simtec_init函数(不过我们可以在smdk2440_machine_init最后调用usb_simtec_init)这里仅仅是利用platform_add_devices进行若干个platform设备的注册,该函数最终通过调用platform_device_register实现platform设备注册:

复制代码
/**
 * platform_add_devices - add a numbers of platform devices
 * @devs: array of platform devices to add
 * @num: number of platform devices in array
 */
int platform_add_devices(struct platform_device **devs, int num)
{
        int i, ret = 0;

        for (i = 0; i < num; i++) {
                ret = platform_device_register(devs[i]);
                if (ret) {
                        while (--i >= 0)
                                platform_device_unregister(devs[i]);
                        break;
                }
        }

        return ret;
}
复制代码

smdk2440_devices中就包含了s3c_device_lcd:

static struct platform_device *smdk2440_devices[] __initdata = {
        &s3c_device_ohci,
        &s3c_device_lcd,
        &s3c_device_wdt,
        &s3c_device_i2c0,
        &s3c_device_iis,
        &smdk2440_device_eth,
};

2.2 platform驱动注册(s3c2410-ohci)

既然注册了名字为"s3c2410-ohci"的platform设备,那么名字为"s3c2410-ohci"的platform驱动在哪里注册的呢?

其相关代码为drivers/usb/host/ohci-s3c2410.c,我们可以在该文件定位到驱动模块的入口和出口:

module_init(ohci_s3c2410_init);
module_exit(ohci_s3c2410_cleanup);

我们定位到ohci-s3c2410.c的入口函数,也就是ohci_s3c2410_init:

复制代码
static int __init ohci_s3c2410_init(void)
{
        if (usb_disabled())
                return -ENODEV;

        pr_info("%s: " DRIVER_DESC "\n", hcd_name);
        ohci_init_driver(&ohci_s3c2410_hc_driver, NULL);

        /*
         * The Samsung HW has some unusual quirks, which require
         * Sumsung-specific workarounds. We override certain hc_driver
         * functions here to achieve that. We explicitly do not enhance
         * ohci_driver_overrides to allow this more easily, since this
         * is an unusual case, and we don't want to encourage others to
         * override these functions by making it too easy.
         */

        ohci_s3c2410_hc_driver.hub_status_data  = ohci_s3c2410_hub_status_data;
        ohci_s3c2410_hc_driver.hub_control      = ohci_s3c2410_hub_control;

        return platform_driver_register(&ohci_hcd_s3c2410_driver);
}
复制代码

这里首先初始化全局变量ohci_s3c2410_hc_driver,类型为struct hc_driver:

static struct hc_driver __read_mostly ohci_s3c2410_hc_driver;

然后通过platform_driver_register函数注册了platform驱动ohci_hcd_s3c2410_driver。

在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备的name名称和platform驱动s3c2410fb_driver里driver的name相同,会调用到platform_driver里的成员.probe,在这里就是ohci_hcd_s3c2410_probe函数。

复制代码
static struct platform_driver ohci_hcd_s3c2410_driver = {
        .probe          = ohci_hcd_s3c2410_probe,
        .remove         = ohci_hcd_s3c2410_remove,
        .shutdown       = usb_hcd_platform_shutdown,
        .driver         = {
                .name   = "s3c2410-ohci",
                .pm     = &ohci_hcd_s3c2410_pm_ops,
                .of_match_table = ohci_hcd_s3c2410_dt_ids,
        },
};
复制代码

2.3 ohci_hcd_s3c2410_probe

复制代码
/**
 * ohci_hcd_s3c2410_probe - initialize S3C2410-based HCDs
 * Context: !in_interrupt()
 *
 * Allocates basic resources for this USB host controller, and
 * then invokes the start() method for the HCD associated with it
 * through the hotplug entry's driver_data.
 *
 */
static int ohci_hcd_s3c2410_probe(struct platform_device *dev)
{
        struct usb_hcd *hcd = NULL;
        struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev);
        int retval;

        s3c2410_usb_set_power(info, 1, 1);
        s3c2410_usb_set_power(info, 2, 1);

        hcd = usb_create_hcd(&ohci_s3c2410_hc_driver, &dev->dev, "s3c24xx");   // 分配主机控制器usb_hcd结构,并初始化,绑定hc_driver等
        if (hcd == NULL)
                return -ENOMEM;

        hcd->rsrc_start = dev->resource[0].start;               // usb主机控制器起始地址
        hcd->rsrc_len   = resource_size(&dev->resource[0]);     // 映射内存长度

        hcd->regs = devm_ioremap_resource(&dev->dev, &dev->resource[0]);  // 物理地址映射到虚拟地址
        if (IS_ERR(hcd->regs)) {
                retval = PTR_ERR(hcd->regs);
                goto err_put;
        }

        clk = devm_clk_get(&dev->dev, "usb-host");         // 获取usb-host时钟频率
        if (IS_ERR(clk)) {
                dev_err(&dev->dev, "cannot get usb-host clock\n");
                retval = PTR_ERR(clk);
                goto err_put;
        }

        usb_clk = devm_clk_get(&dev->dev, "usb-bus-host"); // 获取usb-bus-host时钟
        if (IS_ERR(usb_clk)) {
                dev_err(&dev->dev, "cannot get usb-bus-host clock\n");
                retval = PTR_ERR(usb_clk);
                goto err_put;
        }

        s3c2410_start_hc(dev, hcd);    // 使能时钟

        retval = usb_add_hcd(hcd, dev->resource[1].start, 0);  // 向linux内核注册usb主机控制器驱动(主要就是创建根hub设备,并注册到内核设备链表)
        if (retval != 0)
                goto err_ioremap;

        device_wakeup_enable(hcd->self.controller);
        return 0;

 err_ioremap:
        s3c2410_stop_hc(dev);

 err_put:
        usb_put_hcd(hcd);
        return retval;
}
复制代码

函数执行流程如下:

  • 调用dev_get_platdata获取s3c_device_ohci->dev.platform_data,即struct s3c2410_hcd_info *info,实际上这里我们获取到的info为null;

  • 如果info为空指针,s3c2410_usb_set_power函数忽略;

  • 通过usb_create_hcd创建usb_hcd设备,并绑定hc_driver等;

  • 根据resource资源,进行usb主机控制器寄存器虚拟地址映射;

  • 获取usb-host,usb-bus-host时钟频率,使能hcd时钟;

  • 用usb_add_hcd函数向linux内核注册usb主机控制器驱动;

可以将代码分为两部分解读:

  • 平台相关的硬件初始化:
    • devm_clk_get/clk_enable申请并使能时钟;
    • 进行usb主机控制器寄存器虚拟地址映射;
  • 内核通用的usb hcd软件初始化流程
    • usb主机控制器驱动的创建;
    • 根hub设备的创建和注册,匹配hub接口驱动hub_driver,并执行hub_probe;
    • 开启根hub端口监测,usb主机控制器通过定时轮询判断根hub端口是否有usb设备插入;
    • 如果有新设备接入触发hub_irq函数,将会为新的usb设备分配usb_device结构,并注册到内核,匹配对应的驱动;

关于usb_hcd结构,以及usb_add_hcd我们后面单独介绍。

三、usb主机控制器相关函数

3.1 usb主机控制器驱动相关结构

3.1.1 usb_hcd结构

usb主机控制器相关实现代码在include/linux/usb/hcd.h定义,使用struct usb_hcd结构描述usb主机控制器驱动,它包含usb主机控制器的“家务”信息、硬件资源、状态描述和用于操作主机控制器的hc_driver等:

复制代码
struct usb_hcd {

        /*
         * housekeeping
         */
        struct usb_bus          self;           /* hcd is-a bus */
        struct kref             kref;           /* reference counter */

        const char              *product_desc;  /* product/vendor string */
        int                     speed;          /* Speed for this roothub.
                                                 * May be different from
                                                 * hcd->driver->flags & HCD_MASK
                                                 */
        char                    irq_descr[24];  /* driver + bus # */

        struct timer_list       rh_timer;       /* drives root-hub polling */
        struct urb              *status_urb;    /* the current status urb */
#ifdef CONFIG_PM
        struct work_struct      wakeup_work;    /* for remote wakeup */
#endif
        struct work_struct      died_work;      /* for when the device dies */

        /*
         * hardware info/state
         */
        const struct hc_driver  *driver;        /* hw-specific hooks */

        /*
         * OTG and some Host controllers need software interaction with phys;
         * other external phys should be software-transparent
         */
        struct usb_phy          *usb_phy;
        struct usb_phy_roothub  *phy_roothub;

        /* Flags that need to be manipulated atomically because they can
         * change while the host controller is running.  Always use
         * set_bit() or clear_bit() to change their values.
         */
        unsigned long           flags;
#define HCD_FLAG_HW_ACCESSIBLE          0       /* at full power */
#define HCD_FLAG_POLL_RH                2       /* poll for rh status? */
#define HCD_FLAG_POLL_PENDING           3       /* status has changed? */
#define HCD_FLAG_WAKEUP_PENDING         4       /* root hub is resuming? */
#define HCD_FLAG_RH_RUNNING             5       /* root hub is running? */
#define HCD_FLAG_DEAD                   6       /* controller has died? */
#define HCD_FLAG_INTF_AUTHORIZED        7       /* authorize interfaces? */

        /* The flags can be tested using these macros; they are likely to
         * be slightly faster than test_bit().
         */
#define HCD_HW_ACCESSIBLE(hcd)  ((hcd)->flags & (1U << HCD_FLAG_HW_ACCESSIBLE))
#define HCD_POLL_RH(hcd)        ((hcd)->flags & (1U << HCD_FLAG_POLL_RH))
#define HCD_POLL_PENDING(hcd)   ((hcd)->flags & (1U << HCD_FLAG_POLL_PENDING))
#define HCD_WAKEUP_PENDING(hcd) ((hcd)->flags & (1U << HCD_FLAG_WAKEUP_PENDING))
#define HCD_RH_RUNNING(hcd)     ((hcd)->flags & (1U << HCD_FLAG_RH_RUNNING))
#define HCD_DEAD(hcd)           ((hcd)->flags & (1U << HCD_FLAG_DEAD))
 /*
         * Specifies if interfaces are authorized by default
         * or they require explicit user space authorization; this bit is
         * settable through /sys/class/usb_host/X/interface_authorized_default
         */
#define HCD_INTF_AUTHORIZED(hcd) \
        ((hcd)->flags & (1U << HCD_FLAG_INTF_AUTHORIZED))

        /*
         * Specifies if devices are authorized by default
         * or they require explicit user space authorization; this bit is
         * settable through /sys/class/usb_host/X/authorized_default
         */
        enum usb_dev_authorize_policy dev_policy;

        /* Flags that get set only during HCD registration or removal. */
        unsigned                rh_registered:1;/* is root hub registered? */
        unsigned                rh_pollable:1;  /* may we poll the root hub? */
        unsigned                msix_enabled:1; /* driver has MSI-X enabled? */
        unsigned                msi_enabled:1;  /* driver has MSI enabled? */
        /*
         * do not manage the PHY state in the HCD core, instead let the driver
         * handle this (for example if the PHY can only be turned on after a
         * specific event)
         */
        unsigned                skip_phy_initialization:1;

        /* The next flag is a stopgap, to be removed when all the HCDs
         * support the new root-hub polling mechanism. */
        unsigned                uses_new_polling:1;
        unsigned                wireless:1;     /* Wireless USB HCD */
        unsigned                has_tt:1;       /* Integrated TT in root hub */
        unsigned                amd_resume_bug:1; /* AMD remote wakeup quirk */
        unsigned                can_do_streams:1; /* HC supports streams */
        unsigned                tpl_support:1; /* OTG & EH TPL support */
        unsigned                cant_recv_wakeups:1;
                        /* wakeup requests from downstream aren't received */

        unsigned int            irq;            /* irq allocated */
        void __iomem            *regs;          /* device memory/io */
        resource_size_t         rsrc_start;     /* memory/io resource start */
        resource_size_t         rsrc_len;       /* memory/io resource length */
        unsigned                power_budget;   /* in mA, 0 = no limit */

        struct giveback_urb_bh  high_prio_bh;
        struct giveback_urb_bh  low_prio_bh;

        /* bandwidth_mutex should be taken before adding or removing
         * any new bus bandwidth constraints:
         *   1. Before adding a configuration for a new device.
         *   2. Before removing the configuration to put the device into
         *      the addressed state.
         *   3. Before selecting a different configuration.
         *   4. Before selecting an alternate interface setting.
         *
         * bandwidth_mutex should be dropped after a successful control message
         * to the device, or resetting the bandwidth after a failed attempt.
         */
 struct mutex            *address0_mutex;
        struct mutex            *bandwidth_mutex;
        struct usb_hcd          *shared_hcd;
        struct usb_hcd          *primary_hcd;


#define HCD_BUFFER_POOLS        4
        struct dma_pool         *pool[HCD_BUFFER_POOLS];

        int                     state;
#       define  __ACTIVE                0x01
#       define  __SUSPEND               0x04
#       define  __TRANSIENT             0x80

#       define  HC_STATE_HALT           0
#       define  HC_STATE_RUNNING        (__ACTIVE)
#       define  HC_STATE_QUIESCING      (__SUSPEND|__TRANSIENT|__ACTIVE)
#       define  HC_STATE_RESUMING       (__SUSPEND|__TRANSIENT)
#       define  HC_STATE_SUSPENDED      (__SUSPEND)

#define HC_IS_RUNNING(state) ((state) & __ACTIVE)
#define HC_IS_SUSPENDED(state) ((state) & __SUSPEND)

        /* more shared queuing code would be good; it should support
         * smarter scheduling, handle transaction translators, etc;
         * input size of periodic table to an interrupt scheduler.
         * (ohci 32, uhci 1024, ehci 256/512/1024).
         */

        /* The HC driver's private data is stored at the end of
         * this structure.
         */
        unsigned long hcd_priv[0]
                        __attribute__ ((aligned(sizeof(s64))));
};
View Code
复制代码

部分参数含义:

  • self:usb总线,一个usb主机控制器驱动对应一个usb_bus;
  • product_desc:产品/厂商字符串;
  • irq_descr:驱动+总线;
  • rh_timer:根hub端口轮询定时器,定时器超时函数会被设置为rh_timer_func,usb主机控制器以轮询的方式查找端口状态变化;
  • status_urb:目前状态的urb;
  • driver:用于操作usb主机控制器的钩子函数;
  • rh_registered:根hub注册标志位;
  • irq:被分配的中断编号;
  • regs:设备内存和I/0;
  • rsrc_start:内存和I/O资源开始位置;
  • rerc_len:内存和I/O资源长度;
  • hcd_priv:主机控制器驱动的私有数据;
3.1.2 usb_bus结构

一个usb主机控制器驱动对应一个usb_bus,那什么是usb_bus呢?不是已经有了一个struct bus_type类型的usb_bus_type了么?

没错,在usb子系统中的确注册了注册了usb_bus_type,更准确的说usb_bus_type是总线类型。它是让系统知道有这么一个类型的总线,而一个总线类型和一条总线是两码子事儿。

从硬件上来讲,一个usb主机控制器就会对应一条usb总线,而从软件上来讲,不管你有多少个主机控制器,或者说有多少条总线,它们通通属于usb_bus_type这么一个类型,只是每一条总线对应一个struct usb_bus结构体变量,这个变量在usb主机控制器的驱动程序中去申请。

struct usb_bus定义在include/linux/usb.h::

复制代码
/*
 * Allocated per bus (tree of devices) we have:
 */
struct usb_bus {
        struct device *controller;      /* host/master side hardware */
        struct device *sysdev;          /* as seen from firmware or bus */
        int busnum;                     /* Bus number (in order of reg) */
        const char *bus_name;           /* stable id (PCI slot_name etc) */
        u8 uses_dma;                    /* Does the host controller use DMA? */
        u8 uses_pio_for_control;        /*
                                         * Does the host controller use PIO
                                         * for control transfers?
                                         */
        u8 otg_port;                    /* 0, or number of OTG/HNP port */
        unsigned is_b_host:1;           /* true during some HNP roleswitches */
        unsigned b_hnp_enable:1;        /* OTG: did A-Host enable HNP? */
        unsigned no_stop_on_short:1;    /*
                                         * Quirk: some controllers don't stop
                                         * the ep queue on a short transfer
                                         * with the URB_SHORT_NOT_OK flag set.
                                         */
        unsigned no_sg_constraint:1;    /* no sg constraint */
        unsigned sg_tablesize;          /* 0 or largest number of sg list entries */

        int devnum_next;                /* Next open device number in
                                         * round-robin allocation */
        struct mutex devnum_next_mutex; /* devnum_next mutex */

        struct usb_devmap devmap;       /* device address allocation map */
        struct usb_device *root_hub;    /* Root hub */
        struct usb_bus *hs_companion;   /* Companion EHCI bus, if any */

        int bandwidth_allocated;        /* on this bus: how much of the time
                                         * reserved for periodic (intr/iso)
                                         * requests is used, on average?
                                         * Units: microseconds/frame.
                                         * Limits: Full/low speed reserve 90%,
                                         * while high speed reserves 80%.
                                         */
        int bandwidth_int_reqs;         /* number of Interrupt requests */
        int bandwidth_isoc_reqs;        /* number of Isoc. requests */

        unsigned resuming_ports;        /* bit array: resuming root-hub ports */

#if defined(CONFIG_USB_MON) || defined(CONFIG_USB_MON_MODULE)
        struct mon_bus *mon_bus;        /* non-null when associated */
        int monitored;                  /* non-zero when monitored */
#endif
};
View Code
复制代码

其中部分参数含义如下:

  • controller:存放device设备,比如s3c_device_ohci ->dev;

  • busnum:bus编号;
  • bus_name:总线名称;

  • uses_dma:主机控制器使用DMA标志位;

  • devmap:设备地址映射表,用来表示一条usb总线上设备连接的情况;如果我们电脑只有一个usb主机控制器,通过usb接口插入的设备,都会连接到这个usb总线上;

  • root_hub:根hub设备(struct usb_device);
3.1.3 hc_driver结构体

 hc_driver结构体定义了usb主机控制器操作的函数指针:

复制代码
struct hc_driver {
        const char      *description;   /* "ehci-hcd" etc */
        const char      *product_desc;  /* product/vendor string */
        size_t          hcd_priv_size;  /* size of private data */

        /* irq handler */
        irqreturn_t     (*irq) (struct usb_hcd *hcd);

        int     flags;
#define HCD_MEMORY      0x0001          /* HC regs use memory (else I/O) */
#define HCD_LOCAL_MEM   0x0002          /* HC needs local memory */
#define HCD_SHARED      0x0004          /* Two (or more) usb_hcds share HW */
#define HCD_USB11       0x0010          /* USB 1.1 */
#define HCD_USB2        0x0020          /* USB 2.0 */
#define HCD_USB25       0x0030          /* Wireless USB 1.0 (USB 2.5)*/
#define HCD_USB3        0x0040          /* USB 3.0 */
#define HCD_USB31       0x0050          /* USB 3.1 */
#define HCD_USB32       0x0060          /* USB 3.2 */
#define HCD_MASK        0x0070
#define HCD_BH          0x0100          /* URB complete in BH context */

        /* called to init HCD and root hub */
        int     (*reset) (struct usb_hcd *hcd);
        int     (*start) (struct usb_hcd *hcd);

        /* NOTE:  these suspend/resume calls relate to the HC as
         * a whole, not just the root hub; they're for PCI bus glue.
         */
        /* called after suspending the hub, before entering D3 etc */
        int     (*pci_suspend)(struct usb_hcd *hcd, bool do_wakeup);

        /* called after entering D0 (etc), before resuming the hub */
        int     (*pci_resume)(struct usb_hcd *hcd, bool hibernated);

        /* cleanly make HCD stop writing memory and doing I/O */
        void    (*stop) (struct usb_hcd *hcd);

        /* shutdown HCD */
        void    (*shutdown) (struct usb_hcd *hcd);

        /* return current frame number */
        int     (*get_frame_number) (struct usb_hcd *hcd);

        /* manage i/o requests, device state */
        int     (*urb_enqueue)(struct usb_hcd *hcd,
                                struct urb *urb, gfp_t mem_flags);
        int     (*urb_dequeue)(struct usb_hcd *hcd,
                                struct urb *urb, int status);

 /*
         * (optional) these hooks allow an HCD to override the default DMA
         * mapping and unmapping routines.  In general, they shouldn't be
         * necessary unless the host controller has special DMA requirements,
         * such as alignment contraints.  If these are not specified, the
         * general usb_hcd_(un)?map_urb_for_dma functions will be used instead
         * (and it may be a good idea to call these functions in your HCD
         * implementation)
         */
        int     (*map_urb_for_dma)(struct usb_hcd *hcd, struct urb *urb,
                                   gfp_t mem_flags);
        void    (*unmap_urb_for_dma)(struct usb_hcd *hcd, struct urb *urb);

        /* hw synch, freeing endpoint resources that urb_dequeue can't */
        void    (*endpoint_disable)(struct usb_hcd *hcd,
                        struct usb_host_endpoint *ep);

        /* (optional) reset any endpoint state such as sequence number
           and current window */
        void    (*endpoint_reset)(struct usb_hcd *hcd,
                        struct usb_host_endpoint *ep);

        /* root hub support */
        int     (*hub_status_data) (struct usb_hcd *hcd, char *buf);
        int     (*hub_control) (struct usb_hcd *hcd,
                                u16 typeReq, u16 wValue, u16 wIndex,
                                char *buf, u16 wLength);
        int     (*bus_suspend)(struct usb_hcd *);
        int     (*bus_resume)(struct usb_hcd *);
        int     (*start_port_reset)(struct usb_hcd *, unsigned port_num);
        unsigned long   (*get_resuming_ports)(struct usb_hcd *);

                /* force handover of high-speed port to full-speed companion */
        void    (*relinquish_port)(struct usb_hcd *, int);
                /* has a port been handed over to a companion? */
        int     (*port_handed_over)(struct usb_hcd *, int);

                /* CLEAR_TT_BUFFER completion callback */
        void    (*clear_tt_buffer_complete)(struct usb_hcd *,
                                struct usb_host_endpoint *);

        /* xHCI specific functions */
                /* Called by usb_alloc_dev to alloc HC device structures */
        int     (*alloc_dev)(struct usb_hcd *, struct usb_device *);
                /* Called by usb_disconnect to free HC device structures */
        void    (*free_dev)(struct usb_hcd *, struct usb_device *);
        /* Change a group of bulk endpoints to support multiple stream IDs */
        int     (*alloc_streams)(struct usb_hcd *hcd, struct usb_device *udev,
                struct usb_host_endpoint **eps, unsigned int num_eps,
                unsigned int num_streams, gfp_t mem_flags);
        /* Reverts a group of bulk endpoints back to not using stream IDs.
         * Can fail if we run out of memory.
         */
        int     (*free_streams)(struct usb_hcd *hcd, struct usb_device *udev,
                struct usb_host_endpoint **eps, unsigned int num_eps,
                gfp_t mem_flags);
 /* Bandwidth computation functions */
        /* Note that add_endpoint() can only be called once per endpoint before
         * check_bandwidth() or reset_bandwidth() must be called.
         * drop_endpoint() can only be called once per endpoint also.
         * A call to xhci_drop_endpoint() followed by a call to
         * xhci_add_endpoint() will add the endpoint to the schedule with
         * possibly new parameters denoted by a different endpoint descriptor
         * in usb_host_endpoint.  A call to xhci_add_endpoint() followed by a
         * call to xhci_drop_endpoint() is not allowed.
         */
                /* Allocate endpoint resources and add them to a new schedule */
        int     (*add_endpoint)(struct usb_hcd *, struct usb_device *,
                                struct usb_host_endpoint *);
                /* Drop an endpoint from a new schedule */
        int     (*drop_endpoint)(struct usb_hcd *, struct usb_device *,
                                 struct usb_host_endpoint *);
                /* Check that a new hardware configuration, set using
                 * endpoint_enable and endpoint_disable, does not exceed bus
                 * bandwidth.  This must be called before any set configuration
                 * or set interface requests are sent to the device.
                 */
        int     (*check_bandwidth)(struct usb_hcd *, struct usb_device *);
                /* Reset the device schedule to the last known good schedule,
                 * which was set from a previous successful call to
                 * check_bandwidth().  This reverts any add_endpoint() and
                 * drop_endpoint() calls since that last successful call.
                 * Used for when a check_bandwidth() call fails due to resource
                 * or bandwidth constraints.
                 */
        void    (*reset_bandwidth)(struct usb_hcd *, struct usb_device *);
                /* Returns the hardware-chosen device address */
        int     (*address_device)(struct usb_hcd *, struct usb_device *udev);
                /* prepares the hardware to send commands to the device */
        int     (*enable_device)(struct usb_hcd *, struct usb_device *udev);
                /* Notifies the HCD after a hub descriptor is fetched.
                 * Will block.
                 */
        int     (*update_hub_device)(struct usb_hcd *, struct usb_device *hdev,
                        struct usb_tt *tt, gfp_t mem_flags);
        int     (*reset_device)(struct usb_hcd *, struct usb_device *);
                /* Notifies the HCD after a device is connected and its
                 * address is set
                 */
        int     (*update_device)(struct usb_hcd *, struct usb_device *);
        int     (*set_usb2_hw_lpm)(struct usb_hcd *, struct usb_device *, int);
        /* USB 3.0 Link Power Management */
                /* Returns the USB3 hub-encoded value for the U1/U2 timeout. */
        int     (*enable_usb3_lpm_timeout)(struct usb_hcd *,
                        struct usb_device *, enum usb3_link_state state);
                /* The xHCI host controller can still fail the command to
                 * disable the LPM timeouts, so this can return an error code.
                 */
        int     (*disable_usb3_lpm_timeout)(struct usb_hcd *,
                        struct usb_device *, enum usb3_link_state state);
        int     (*find_raw_port_number)(struct usb_hcd *, int);
        /* Call for power on/off the port if necessary */
        int     (*port_power)(struct usb_hcd *hcd, int portnum, bool enable);

};
View Code
复制代码

其中部分参数含义:

  • description:ehci-hcd等;
  • product_desc:产品//厂商字符串;
  • hcd_priv_size:主机控制器驱动私有数据大小,单位字节;

  • irq:中断处理函数;

3.2 usb_create_hcd

我们来看一下usb_create_hcd函数,该函数定义在drivers/usb/core/hcd.c:

复制代码
/**
 * usb_create_hcd - create and initialize an HCD structure
 * @driver: HC driver that will use this hcd
 * @dev: device for this HC, stored in hcd->self.controller
 * @bus_name: value to store in hcd->self.bus_name
 * Context: !in_interrupt()
 *
 * Allocate a struct usb_hcd, with extra space at the end for the
 * HC driver's private data.  Initialize the generic members of the
 * hcd structure.
 *
 * Return: On success, a pointer to the created and initialized HCD
 * structure. On failure (e.g. if memory is unavailable), %NULL.
 */
struct usb_hcd *usb_create_hcd(const struct hc_driver *driver,    // 参数1为ohci_s3c2410_hc_driver,参数2为s3c_device_ohci ->dev,参数3位s3c24xx
                struct device *dev, const char *bus_name)
{
        return __usb_create_hcd(driver, dev, dev, bus_name, NULL);
}
复制代码

这里调用了__usb_create_hcd函数:

复制代码
struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver,   
                struct device *sysdev, struct device *dev, const char *bus_name,
                struct usb_hcd *primary_hcd)
{
        struct usb_hcd *hcd;

        hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL); // 动态申请内存空间,额外申请hcd私有数据大小的内存
        if (!hcd)
                return NULL;
        if (primary_hcd == NULL) {                     // 成立
                hcd->address0_mutex = kmalloc(sizeof(*hcd->address0_mutex),
                                GFP_KERNEL);
                if (!hcd->address0_mutex) {
                        kfree(hcd);
                        dev_dbg(dev, "hcd address0 mutex alloc failed\n");
                        return NULL;
                }
                mutex_init(hcd->address0_mutex);  // 初始化互斥锁
                hcd->bandwidth_mutex = kmalloc(sizeof(*hcd->bandwidth_mutex),
                                GFP_KERNEL);
                if (!hcd->bandwidth_mutex) {
                        kfree(hcd->address0_mutex);
                        kfree(hcd);
                        dev_dbg(dev, "hcd bandwidth mutex alloc failed\n");
                        return NULL;
                }
                mutex_init(hcd->bandwidth_mutex);   // 初始化互斥锁
                dev_set_drvdata(dev, hcd);          // 设置设备驱动数据dev->driver_data = hcd
        } else {
                mutex_lock(&usb_port_peer_mutex);
                hcd->address0_mutex = primary_hcd->address0_mutex;
                hcd->bandwidth_mutex = primary_hcd->bandwidth_mutex;
                hcd->primary_hcd = primary_hcd;
                primary_hcd->primary_hcd = primary_hcd;
                hcd->shared_hcd = primary_hcd;
                primary_hcd->shared_hcd = hcd;
                mutex_unlock(&usb_port_peer_mutex);
        }

        kref_init(&hcd->kref);

        usb_bus_init(&hcd->self);           // 初始化usb_bus,一个usb主机控制器对应一个usb_bus
        hcd->self.controller = dev;
        hcd->self.sysdev = sysdev;
        hcd->self.bus_name = bus_name;
        hcd->self.uses_dma = (sysdev->dma_mask != NULL);

        timer_setup(&hcd->rh_timer, rh_timer_func, 0);  // 初始化定时器,设置定时器超时函数
#ifdef CONFIG_PM
        INIT_WORK(&hcd->wakeup_work, hcd_resume_work);
#endif

        INIT_WORK(&hcd->died_work, hcd_died_work);

        hcd->driver = driver;   // 绑定hc_driver
        hcd->speed = driver->flags & HCD_MASK;
        hcd->product_desc = (driver->product_desc) ? driver->product_desc :
                        "USB Host Controller";
        return hcd;
}
复制代码
复制代码
/**
 * usb_bus_init - shared initialization code
 * @bus: the bus structure being initialized
 *
 * This code is used to initialize a usb_bus structure, memory for which is
 * separately managed.
 */
static void usb_bus_init (struct usb_bus *bus)
{
        memset (&bus->devmap, 0, sizeof(struct usb_devmap));

        bus->devnum_next = 1;

        bus->root_hub = NULL;
        bus->busnum = -1;
        bus->bandwidth_allocated = 0;
        bus->bandwidth_int_reqs  = 0;
        bus->bandwidth_isoc_reqs = 0;
        mutex_init(&bus->devnum_next_mutex);
}
复制代码

总结一下函数执行流程:

  • 首先分配一个hcd的内存空间,包含私有数据空间;
  • primary_hcd == NULL
    • 分配mutx内存空间,并初始化互斥锁;
    • 设置设备驱动数据dev->driver_data = hcd;
  • 执行usb_bus_init,初始化成员usb_bus:
    • bus->devmap清零,根hub指向NULL等;
  • 初始化usb_bus:
    • hcd->self.controller = s3c_device_usb.dev (最开始创建的平台device);
    • hcd->self.bus_name = “s3c24xx”等;
  • 设置hcd->rh_timer设置定时器超时函数rh_timer_func,主机控制器以轮询的方式查找端口变化状态;
  • 设置 hcd->driver = &ohci_s3c2410_hc_driver等 ;

ohci_s3c2410_hc_driver是usb主机控制器的驱动函数,实现了通过主机控制器硬件向外通信的方法。

3.2.1 rh_timer_func

定时器回调函数定义在drivers/usb/core/hcd.c文件中:

/* timer callback */
static void rh_timer_func (struct timer_list *t)
{
        struct usb_hcd *_hcd = from_timer(_hcd, t, rh_timer);

        usb_hcd_poll_rh_status(_hcd);
}

3.3 usc_add_hcd

用usb_hcd结构体定义好usb_hdc设备后,用usb_add_hcd函数向linux内核注册usb主机控制器驱动,函数定义在drivers/usb/core/hcd.c:

复制代码
/**
 * usb_add_hcd - finish generic HCD structure initialization and register
 * @hcd: the usb_hcd structure to initialize
 * @irqnum: Interrupt line to allocate
 * @irqflags: Interrupt type flags
 *
 * Finish the remaining parts of generic HCD initialization: allocate the
 * buffers of consistent memory, register the bus, request the IRQ line,
 * and call the driver's reset() and start() routines.
 */
int usb_add_hcd(struct usb_hcd *hcd,
                unsigned int irqnum, unsigned long irqflags)
{
        int retval;
        struct usb_device *rhdev;

        if (!hcd->skip_phy_initialization && usb_hcd_is_primary_hcd(hcd)) {
                hcd->phy_roothub = usb_phy_roothub_alloc(hcd->self.sysdev);
                if (IS_ERR(hcd->phy_roothub))
                        return PTR_ERR(hcd->phy_roothub);

                retval = usb_phy_roothub_init(hcd->phy_roothub);
                if (retval)
                        return retval;

                retval = usb_phy_roothub_set_mode(hcd->phy_roothub,
                                                  PHY_MODE_USB_HOST_SS);
                if (retval)
                        retval = usb_phy_roothub_set_mode(hcd->phy_roothub,
                                                          PHY_MODE_USB_HOST);
                if (retval)
                        goto err_usb_phy_roothub_power_on;

                retval = usb_phy_roothub_power_on(hcd->phy_roothub);
                if (retval)
                        goto err_usb_phy_roothub_power_on;
        }

        dev_info(hcd->self.controller, "%s\n", hcd->product_desc);

        switch (authorized_default) {
        case USB_AUTHORIZE_NONE:
                hcd->dev_policy = USB_DEVICE_AUTHORIZE_NONE;
                break;

        case USB_AUTHORIZE_ALL:
                hcd->dev_policy = USB_DEVICE_AUTHORIZE_ALL;
                break;

        case USB_AUTHORIZE_INTERNAL:
                hcd->dev_policy = USB_DEVICE_AUTHORIZE_INTERNAL;
                break;

        case USB_AUTHORIZE_WIRED:
        default:
                hcd->dev_policy = hcd->wireless ?
                        USB_DEVICE_AUTHORIZE_NONE : USB_DEVICE_AUTHORIZE_ALL;
                break;
        }
 set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);

        /* per default all interfaces are authorized */
        set_bit(HCD_FLAG_INTF_AUTHORIZED, &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 hub_wq
         * starts talking to them.  (Note, bus id is assigned early too.)
         */
        retval = hcd_buffer_create(hcd);
        if (retval != 0) {
                dev_dbg(hcd->self.sysdev, "pool alloc failed\n");
                goto err_create_buf;
        }

        retval = usb_register_bus(&hcd->self);
        if (retval < 0)
                goto err_register_bus;

        rhdev = usb_alloc_dev(NULL, &hcd->self, 0);
        if (rhdev == NULL) {
                dev_err(hcd->self.sysdev, "unable to allocate root hub\n");
                retval = -ENOMEM;
                goto err_allocate_root_hub;
        }
        mutex_lock(&usb_port_peer_mutex);
        hcd->self.root_hub = rhdev;
        mutex_unlock(&usb_port_peer_mutex);

        rhdev->rx_lanes = 1;
        rhdev->tx_lanes = 1;

        switch (hcd->speed) {
        case HCD_USB11:
                rhdev->speed = USB_SPEED_FULL;
                break;
        case HCD_USB2:
                rhdev->speed = USB_SPEED_HIGH;
                break;
        case HCD_USB25:
                rhdev->speed = USB_SPEED_WIRELESS;
                break;
        case HCD_USB3:
                rhdev->speed = USB_SPEED_SUPER;
                break;
        case HCD_USB32:
                rhdev->rx_lanes = 2;
      rhdev->tx_lanes = 2;
                /* fall through */
        case HCD_USB31:
                rhdev->speed = USB_SPEED_SUPER_PLUS;
                break;
        default:
                retval = -EINVAL;
                goto err_set_rh_speed;
        }

        /* 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_set_wakeup_capable(&rhdev->dev, 1);

        /* HCD_FLAG_RH_RUNNING doesn't matter until the root hub is
         * registered.  But since the controller can die at any time,
         * let's initialize the flag before touching the hardware.
         */
        set_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);

        /* "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);
                if (retval < 0) {
                        dev_err(hcd->self.controller, "can't setup: %d\n",
                                        retval);
                        goto err_hcd_driver_setup;
                }
        }
        hcd->rh_pollable = 1;

        /* 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");

        /* initialize tasklets */
        init_giveback_urb_bh(&hcd->high_prio_bh);
        init_giveback_urb_bh(&hcd->low_prio_bh);

        /* enable irqs just before we start the controller,
         * if the BIOS provides legacy PCI irqs.
         */
        if (usb_hcd_is_primary_hcd(hcd) && irqnum) {
                retval = usb_hcd_request_irqs(hcd, irqnum, irqflags);
                if (retval)
                        goto err_request_irq;
        }

        hcd->state = HC_STATE_RUNNING;
        retval = hcd->driver->start(hcd);
        if (retval < 0) {
                dev_err(hcd->self.controller, "startup error %d\n", retval);
 goto err_hcd_driver_start;
        }

        /* starting here, usbcore will pay attention to this root hub */
        retval = register_root_hub(hcd);
        if (retval != 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(hcd))
                usb_hcd_poll_rh_status(hcd);     // 定时器轮询root hub端口状态

        return retval;

error_create_attr_group:
        clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
        if (HC_IS_RUNNING(hcd->state))
                hcd->state = HC_STATE_QUIESCING;
        spin_lock_irq(&hcd_root_hub_lock);
        hcd->rh_registered = 0;
        spin_unlock_irq(&hcd_root_hub_lock);

#ifdef CONFIG_PM
        cancel_work_sync(&hcd->wakeup_work);
#endif
        cancel_work_sync(&hcd->died_work);
        mutex_lock(&usb_bus_idr_lock);
        usb_disconnect(&rhdev);         /* Sets rhdev to NULL */
        mutex_unlock(&usb_bus_idr_lock);
err_register_root_hub:
        hcd->rh_pollable = 0;
        clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
        del_timer_sync(&hcd->rh_timer);
        hcd->driver->stop(hcd);
        hcd->state = HC_STATE_HALT;
        clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
        del_timer_sync(&hcd->rh_timer);
err_hcd_driver_start:
        if (usb_hcd_is_primary_hcd(hcd) && hcd->irq > 0)
                free_irq(irqnum, hcd);
err_request_irq:
err_hcd_driver_setup:
err_set_rh_speed:
        usb_put_invalidate_rhdev(hcd);
err_allocate_root_hub:
        usb_deregister_bus(&hcd->self);
err_register_bus:
        hcd_buffer_destroy(hcd);
err_create_buf:
        usb_phy_roothub_power_off(hcd->phy_roothub);
err_usb_phy_roothub_power_on:
        usb_phy_roothub_exit(hcd->phy_roothub);

        return retval;
}
复制代码

该函数代码比较长,这里挑重点说:

  • hcd_buffer_create(hcd) ;
  • usb_register_bus(&hcd->self) ,将 hcd->usb_bus 注册到全局链表usb_bus_list;
  • usb_alloc_dev为根hub分配一个usb_device 结构(内核中,所有的真实的usb设备;比如hub,鼠标等都用usb_device结构来描述);
  • register_root_hub注册根hub设备到usb_bus_type;
  • usb_hcd_poll_rh_status定时器函数轮询hub端口状态;

 弄了半天,usb主机控制器也只不过是分配了一个usb_hcd结构体,为它的根hub分配了一个usb_device 结构体,注册到usb_bus_type罢了,后边是根hub的注册和设备枚举过程了。

3.3.1 usb_register_bus

usb_register_bus函数定义在drivers/usb/core/hcd.c中:

复制代码
/**
 * usb_register_bus - registers the USB host controller with the usb core
 * @bus: pointer to the bus to register
 * Context: !in_interrupt()
 *
 * Assigns a bus number, and links the controller into usbcore data
 * structures so that it can be seen by scanning the bus list.
 *
 * Return: 0 if successful. A negative error code otherwise.
 */
static int usb_register_bus(struct usb_bus *bus)
{
        int result = -E2BIG;
        int busnum;

        mutex_lock(&usb_bus_idr_lock);
        busnum = idr_alloc(&usb_bus_idr, bus, 1, USB_MAXBUS, GFP_KERNEL);
        if (busnum < 0) {
                pr_err("%s: failed to get bus number\n", usbcore_name);
                goto error_find_busnum;
        }
        bus->busnum = busnum;
        mutex_unlock(&usb_bus_idr_lock);

        usb_notify_add_bus(bus);

        dev_info (bus->controller, "new USB bus registered, assigned bus "
                  "number %d\n", bus->busnum);
        return 0;

error_find_busnum:
        mutex_unlock(&usb_bus_idr_lock);
        return result;
}
复制代码

该函数调用idr_alloc为usb_bus总线随机分配一个总线编号,然后发送USB_BUS_ADD事件。

void usb_notify_add_bus(struct usb_bus *ubus)
{
        blocking_notifier_call_chain(&usb_notifier_list, USB_BUS_ADD, ubus);
}

这个usb_notifier_list为drivers/usb/core/usb.c中usb子系统初始化过程中通过usb_devio_init注册的。

当事件发生后会调用usbdev_nb指定的notifier_cal方法,即usbdev_notify:

复制代码
static int usbdev_notify(struct notifier_block *self,
                               unsigned long action, void *dev)
{
        switch (action) {
        case USB_DEVICE_ADD:  // 设备添加
                break;
        case USB_DEVICE_REMOVE:  // 设备删除
                usbdev_remove(dev);
                break;
        }
        return NOTIFY_OK;
}
复制代码

实际上这里啥也没做。

3.3.2 usb_alloc_dev

usb_alloc_dev函数定义在drivers/usb/core/hcd.c:

复制代码
/**
 * usb_alloc_dev - usb device constructor (usbcore-internal)
 * @parent: hub to which device is connected; null to allocate a root hub
 * @bus: bus used to access the device
 * @port1: one-based index of port; ignored for root hubs
 * Context: !in_interrupt()
 *
 * Only hub drivers (including virtual root hub drivers for host
 * controllers) should ever call this.
 *
 * This call may not be used in a non-sleeping context.
 *
 * Return: On success, a pointer to the allocated usb device. %NULL on
 * failure.
 */
struct usb_device *usb_alloc_dev(struct usb_device *parent,
                                 struct usb_bus *bus, unsigned port1)
{
        struct usb_device *dev;
        struct usb_hcd *usb_hcd = bus_to_hcd(bus);
        unsigned root_hub = 0;
        unsigned raw_port = port1;

        dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        if (!dev)
                return NULL;

        if (!usb_get_hcd(usb_hcd)) {
                kfree(dev);
                return NULL;
        }
        /* Root hubs aren't true devices, so don't allocate HCD resources */
        if (usb_hcd->driver->alloc_dev && parent &&
                !usb_hcd->driver->alloc_dev(usb_hcd, dev)) {
                usb_put_hcd(bus_to_hcd(bus));
                kfree(dev);
                return NULL;
        }

        device_initialize(&dev->dev);
        dev->dev.bus = &usb_bus_type;
        dev->dev.type = &usb_device_type;
        dev->dev.groups = usb_device_groups;
        /*
         * Fake a dma_mask/offset for the USB device:
         * We cannot really use the dma-mapping API (dma_alloc_* and
         * dma_map_*) for USB devices but instead need to use
         * usb_alloc_coherent and pass data in 'urb's, but some subsystems
         * manually look into the mask/offset pair to determine whether
         * they need bounce buffers.
         * Note: calling dma_set_mask() on a USB device would set the
         * mask for the entire HCD, so don't do that.
         */
        dev->dev.dma_mask = bus->sysdev->dma_mask;
        dev->dev.dma_pfn_offset = bus->sysdev->dma_pfn_offset;
        set_dev_node(&dev->dev, dev_to_node(bus->sysdev));
        dev->state = USB_STATE_ATTACHED;
        dev->lpm_disable_count = 1;
        atomic_set(&dev->urbnum, 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, false);
        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 name isn't
         * as stable:  bus->busnum changes easily from modprobe order,
         * cardbus or pci hotplugging, and so on.
         */
        if (unlikely(!parent)) {
                dev->devpath[0] = '0';
                dev->route = 0;

                dev->dev.parent = bus->controller;
                device_set_of_node_from_dev(&dev->dev, bus->sysdev);
                dev_set_name(&dev->dev, "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);
                        /* Root ports are not counted in route string */
                        dev->route = 0;
                } else {
                        snprintf(dev->devpath, sizeof dev->devpath,
                                "%s.%d", parent->devpath, port1);
                        /* Route string assumes hubs have less than 16 ports */
                        if (port1 < 15)
                                dev->route = parent->route +
                                        (port1 << ((parent->level - 1)*4));
                        else
                                dev->route = parent->route +
                                        (15 << ((parent->level - 1)*4));
                }

                dev->dev.parent = &parent->dev;
                dev_set_name(&dev->dev, "%d-%s", bus->busnum, dev->devpath);

                if (!parent->parent) {
                        /* device under root hub's port */
                        raw_port = usb_hcd_find_raw_port_number(usb_hcd,
                                port1);
                }
                dev->dev.of_node = usb_of_get_device_node(parent, raw_port);

                /* hub driver sets up TT records */
        }

        dev->portnum = port1;
        dev->bus = bus;
        dev->parent = parent;
        INIT_LIST_HEAD(&dev->filelist);

#ifdef  CONFIG_PM
        pm_runtime_set_autosuspend_delay(&dev->dev,
                        usb_autosuspend_delay * 1000);
        dev->connect_time = jiffies;
        dev->active_duration = -jiffies;
#endif

        dev->authorized = usb_dev_authorized(dev, usb_hcd);
        if (!root_hub)
                dev->wusb = usb_bus_is_wusb(bus) ? 1 : 0;

        return dev;
}
复制代码

该函数主要做了以下事情:

  • 这里实际上就是为根hub,动态分配一个usb_device;
  • 初始化设备基类dev->dev:
    •  dev->dev.bus = &usb_bus_type,设置总线类型
    •  dev->dev.type = &usb_device_type,设置设备类型,用于和usb接口区分;
    • 等等;
  • 初始化dev其它成员;
    • dev->state = USB_STATE_ATTACHED,设备状态设置位已连接;
    • usb_enable_endpoint函数使能usb端点0,用于usb通信,usb主机控制器会通过该端点和root hub通信获取设备/配置描述信息;一个 usb 设备有多个配置,每个配置又有多个接口,每个接口有多个端点。但是端点0比较特殊,它是整个usb设备共享的,端点0是直接存储在usb_device->ep0字段中。
    • 等等;
3.3.3 usb_enable_endpoint
复制代码
/**
 * usb_enable_endpoint - Enable an endpoint for USB communications
 * @dev: the device whose interface is being enabled
 * @ep: the endpoint
 * @reset_ep: flag to reset the endpoint state
 *
 * Resets the endpoint state if asked, and sets dev->ep_{in,out} pointers.
 * For control endpoints, both the input and output sides are handled.
 */
void usb_enable_endpoint(struct usb_device *dev, struct usb_host_endpoint *ep,
                bool reset_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 (reset_ep)
                usb_hcd_reset_endpoint(dev, ep);
        if (is_out || is_control)
                dev->ep_out[epnum] = ep;
        if (!is_out || is_control)
                dev->ep_in[epnum] = ep;
        ep->enabled = 1;
}
复制代码

首先:

  • 从端点描述符获取端点编号,这里应该就是0;
  • 判断当前端点是不是输出;
  • 判断当前端点是不是采用的控制传输;
  • 如果是输出,设置dev->ep_out,如果是输入设置dev->ep_in;
  • 使能端点0;
3.3.4 register_root_hub

register_root_hub函数定义在drivers/usb/core/hcd.c,顾名思义,就是注册root_hub设备,先是对自身初始化,然后获取设备描述符,最后把自己当普通usb设备给注册了。

复制代码
/**
 * register_root_hub - called by usb_add_hcd() to register a root hub
 * @hcd: host controller for this root hub
 *
 * This function registers the root hub with the USB subsystem.  It sets up
 * the device properly in the device tree and then calls usb_new_device()
 * to register the usb device.  It also assigns the root hub's USB address
 * (always 1).
 *
 * Return: 0 if successful. A negative error code otherwise.
 */
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;  // 根hub设备
        const int devnum = 1;
        int retval;

        usb_dev->devnum = devnum;        //根hub设备地址为1
        usb_dev->bus->devnum_next = devnum + 1;
        set_bit (devnum, usb_dev->bus->devmap.devicemap);
        usb_set_device_state(usb_dev, USB_STATE_ADDRESS);   // 变更状态

        mutex_lock(&usb_bus_idr_lock);

        usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
        retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);  // 获取设备描述符
        if (retval != sizeof usb_dev->descriptor) {
                mutex_unlock(&usb_bus_idr_lock);
                dev_dbg (parent_dev, "can't read %s device descriptor %d\n",
                                dev_name(&usb_dev->dev), retval);
                return (retval < 0) ? retval : -EMSGSIZE;
        }

        if (le16_to_cpu(usb_dev->descriptor.bcdUSB) >= 0x0201) {
                retval = usb_get_bos_descriptor(usb_dev);
                if (!retval) {
                        usb_dev->lpm_capable = usb_device_supports_lpm(usb_dev);
                } else if (usb_dev->speed >= USB_SPEED_SUPER) {
                        mutex_unlock(&usb_bus_idr_lock);
                        dev_dbg(parent_dev, "can't read %s bos descriptor %d\n",
                                        dev_name(&usb_dev->dev), retval);
                        return retval;
                }
        }

        retval = usb_new_device (usb_dev);
        if (retval) {
                dev_err (parent_dev, "can't register root hub for %s, %d\n",
                                dev_name(&usb_dev->dev), retval);
        } else {
                spin_lock_irq (&hcd_root_hub_lock);
                hcd->rh_registered = 1;
                spin_unlock_irq (&hcd_root_hub_lock);

                /* Did the HC die before the root hub was registered? */
                if (HCD_DEAD(hcd))
                        usb_hc_died (hcd);      /* This time clean up */
        }
        mutex_unlock(&usb_bus_idr_lock);

        return retval;
}
复制代码

这里我们先介绍usb_get_device_descriptor函数,该函数用于获取根hub设备的描述符;

复制代码
/*
 * usb_get_device_descriptor - (re)reads the device descriptor (usbcore)
 * @dev: the device whose device descriptor is being updated
 * @size: how much of the descriptor to read
 * Context: !in_interrupt ()
 *
 * Updates the copy of the device descriptor stored in the device structure,
 * which dedicates space for this purpose.
 *
 * Not exported, only for use by the core.  If drivers really want to read
 * the device descriptor directly, they can call usb_get_descriptor() with
 * type = USB_DT_DEVICE and index = 0.
 *
 * This call is synchronous, and may not be used in an interrupt context.
 *
 * Return: The number of bytes received on success, or else the status code
 * returned by the underlying usb_control_msg() call.
 */
int usb_get_device_descriptor(struct usb_device *dev, unsigned int size)
{
        struct usb_device_descriptor *desc;
        int ret;

        if (size > sizeof(*desc))
                return -EINVAL;
        desc = kmalloc(sizeof(*desc), GFP_NOIO);
        if (!desc)
                return -ENOMEM;

        ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, size); // 获取usb设备描述符,request = 0x06,requestType = 0x80、value = 0x100
        if (ret >= 0)
                memcpy(&dev->descriptor, desc, size);   // 将usb设备描述符,拷贝到dev->descriptor
        kfree(desc);
        return ret;
}
复制代码

接下来我们再来看看usb_new_device。

3.3.5 usb_new_device
复制代码
/**
 * usb_new_device - perform initial device setup (usbcore-internal)
 * @udev: newly addressed device (in ADDRESS state)
 *
 * This is called with devices which have been detected but not fully
 * enumerated.  The device descriptor is available, but not descriptors
 * for any device configuration.  The caller must have locked either
 * the parent hub (if udev is a normal device) or else the
 * usb_bus_idr_lock (if udev is a root hub).  The parent's pointer to
 * udev has already been installed, but udev is not yet visible through
 * sysfs or other filesystem code.
 *
 * This call is synchronous, and may not be used in an interrupt context.
 *
 * Only the hub driver or root-hub registrar should ever call this.
 *
 * Return: Whether the device is configured properly or not. Zero if the
 * interface was registered with the driver core; else a negative errno
 * value.
 *
 */
int usb_new_device(struct usb_device *udev)
{
        int err;

        if (udev->parent) {
                /* Initialize non-root-hub device wakeup to disabled;
                 * device (un)configuration controls wakeup capable
                 * sysfs power/wakeup controls wakeup enabled/disabled
                 */
                device_init_wakeup(&udev->dev, 0);
        }

        /* Tell the runtime-PM framework the device is active */
        pm_runtime_set_active(&udev->dev);
        pm_runtime_get_noresume(&udev->dev);
        pm_runtime_use_autosuspend(&udev->dev);
        pm_runtime_enable(&udev->dev);

        /* By default, forbid autosuspend for all devices.  It will be
         * allowed for hubs during binding.
         */
        usb_disable_autosuspend(udev);

        err = usb_enumerate_device(udev);       /* Read descriptors */
        if (err < 0)
                goto fail;
        dev_dbg(&udev->dev, "udev %d, busnum %d, minor = %d\n",
                        udev->devnum, udev->bus->busnum,
                        (((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
        /* export the usbdev device-node for libusb */
        udev->dev.devt = MKDEV(USB_DEVICE_MAJOR,
                        (((udev->bus->busnum-1) * 128) + (udev->devnum-1)));

        /* Tell the world! */
        announce_device(udev);
  if (udev->serial)
                add_device_randomness(udev->serial, strlen(udev->serial));
        if (udev->product)
                add_device_randomness(udev->product, strlen(udev->product));
        if (udev->manufacturer)
                add_device_randomness(udev->manufacturer,
                                      strlen(udev->manufacturer));

        device_enable_async_suspend(&udev->dev);

        /* check whether the hub or firmware marks this port as non-removable */
        if (udev->parent)
                set_usb_port_removable(udev);

        /* Register the device.  The device driver is responsible
         * for configuring the device and invoking the add-device
         * notifier chain (used by usbfs and possibly others).
         */
        err = device_add(&udev->dev);
        if (err) {
                dev_err(&udev->dev, "can't device_add, error %d\n", err);
                goto fail;
        }

        /* Create link files between child device and usb port device. */
        if (udev->parent) {
                struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
                int port1 = udev->portnum;
                struct usb_port *port_dev = hub->ports[port1 - 1];

                err = sysfs_create_link(&udev->dev.kobj,
                                &port_dev->dev.kobj, "port");
                if (err)
                        goto fail;

                err = sysfs_create_link(&port_dev->dev.kobj,
                                &udev->dev.kobj, "device");
                if (err) {
                        sysfs_remove_link(&udev->dev.kobj, "port");
                        goto fail;
                }

                if (!test_and_set_bit(port1, hub->child_usage_bits))
                        pm_runtime_get_sync(&port_dev->dev);
        }

        (void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev);
        usb_mark_last_busy(udev);
        pm_runtime_put_sync_autosuspend(&udev->dev);
        return err;

fail:
        usb_set_device_state(udev, USB_STATE_NOTATTACHED);
        pm_runtime_disable(&udev->dev);
        pm_runtime_set_suspended(&udev->dev);
        return err;
}
复制代码

usb_new_device函数用于注册usb根hub设备,先是枚举调用usb_enumerate_device获取设备配置描述符信息(这里枚举获取的pid、vid等用于设备驱动匹配的id使用):

复制代码
/**
 * usb_enumerate_device - Read device configs/intfs/otg (usbcore-internal)
 * @udev: newly addressed device (in ADDRESS state)
 *
 * This is only called by usb_new_device() and usb_authorize_device()
 * and FIXME -- all comments that apply to them apply here wrt to
 * environment.
 *
 * If the device is WUSB and not authorized, we don't attempt to read
 * the string descriptors, as they will be errored out by the device
 * until it has been authorized.
 *
 * Return: 0 if successful. A negative error code otherwise.
 */
static int usb_enumerate_device(struct usb_device *udev)
{
        int err;
        struct usb_hcd *hcd = bus_to_hcd(udev->bus);

        if (udev->config == NULL) {
                err = usb_get_configuration(udev);    // 获取配置描述符,这里根据设备描述符dev->descriptor.bNumConfiguratios中配置数量,来一一发送USC_DT_CONFIG(value=0x200)命令获取配置描述符,初始化dev->config[num]成员
                if (err < 0) {
                        if (err != -ENODEV)
                                dev_err(&udev->dev, "can't read configurations, error %d\n",
                                                err);
                        return err;
                }
        }

        /* 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);  // 从设备描述符获取产品串号信息

        err = usb_enumerate_device_otg(udev);
        if (err < 0)
                return err;

        if (IS_ENABLED(CONFIG_USB_OTG_WHITELIST) && hcd->tpl_support &&
                !is_targeted(udev)) {
                /* Maybe it can talk to us, though we can't talk to it.
                 * (Includes HNP test device.)
                 */
                if (IS_ENABLED(CONFIG_USB_OTG) && (udev->bus->b_hnp_enable
                        || udev->bus->is_b_host)) {
                        err = usb_port_suspend(udev, PMSG_AUTO_SUSPEND);
                        if (err < 0)
                                dev_dbg(&udev->dev, "HNP fail, %d\n", err);
                }
                return -ENOTSUPP;
        }

        usb_detect_interface_quirks(udev);

        return 0;
}
复制代码

usb_get_configuration,大致步骤也是调用usb_control_msg函数通过usb主机控制器向root hub发送控制报文,获取配置描述符信息。

源码就不介绍了:

复制代码
/*
 * Get the USB config descriptors, cache and parse'em
 *
 * hub-only!! ... and only in reset path, or usb_new_device()
 * (used by real hubs and virtual root hubs)
 */
int usb_get_configuration(struct usb_device *dev)
{
        struct device *ddev = &dev->dev;
        int ncfg = dev->descriptor.bNumConfigurations;
        int result = -ENOMEM;
        unsigned int cfgno, length;
        unsigned char *bigbuffer;
        struct usb_config_descriptor *desc;

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

        length = ncfg * sizeof(struct usb_host_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;

        desc = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL);
        if (!desc)
                goto err2;

        for (cfgno = 0; cfgno < ncfg; cfgno++) {
                /* We grab just the first descriptor so we know how long
                 * the whole configuration is */
                result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
                    desc, USB_DT_CONFIG_SIZE);
                if (result < 0) {
                        dev_err(ddev, "unable to read config index %d "
                            "descriptor/%s: %d\n", cfgno, "start", result);
                        if (result != -EPIPE)
                                goto err;
                        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;
                }
                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;
 }

                if (dev->quirks & USB_QUIRK_DELAY_INIT)
                        msleep(200);

                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[cfgno] = bigbuffer;

                result = usb_parse_configuration(dev, cfgno,
                    &dev->config[cfgno], bigbuffer, length);
                if (result < 0) {
                        ++cfgno;
                        goto err;
                }
        }

err:
        kfree(desc);
        dev->descriptor.bNumConfigurations = cfgno;
err2:
        if (result == -ENOMEM)
                dev_err(ddev, "out of memory\n");
        return result;
}
View Code
复制代码

然后调用device_add将设备加入到设备链表中,在执行device_add后,将执行总线的匹配函数usb_device_match:

  • device_add
    • bus_probe_devic
      • device_attach
        • bus_for_each_drv
          • __device_attach
            • driver_match_device
              • drv->bus->match(dev, drv)
                • usb_device_match

注册根hub设备后,由于在usb子系统初始化函数中注册了usb通用设备驱动usb_generic_driver,这样就会执行usb_generic_driver的probe函数,即generic_probe:

复制代码
static int generic_probe(struct usb_device *udev)
{
        int err, c;

        /* Choose and set the configuration.  This registers the interfaces
         * with the driver core and lets interface drivers bind to them.
         */
        if (udev->authorized == 0)
                dev_err(&udev->dev, "Device is not authorized for usage\n");
        else {
                c = usb_choose_configuration(udev);     // 选择一个配置,返回配置的索引
                if (c >= 0) {
                        err = usb_set_configuration(udev, c);
                        if (err && err != -ENODEV) {
                                dev_err(&udev->dev, "can't set config #%d, error %d\n",
                                        c, err);
                                /* This need not be fatal.  The user can try to
                                 * set other configurations. */
                        }
                }
        }
        /* USB device state == configured ... usable */
        usb_notify_add_device(udev);

        return 0;
}
复制代码
3.3.6  usb_choose_configuration

usb_choose_configuration定义在drivers/usb/core/generic.c,该函数从udev->descriptor.bNumConfigurations个配置里选择一个合适的配置(struct usb_host_config),并返回该配置的索引值:

复制代码
int usb_choose_configuration(struct usb_device *udev)
{
        int i;
        int num_configs;
        int insufficient_power = 0;
        struct usb_host_config *c, *best;

        if (usb_device_is_owned(udev))
                return 0;

        best = NULL;
        c = udev->config;
        num_configs = udev->descriptor.bNumConfigurations;  // 获取设备中配置的个数
        for (i = 0; i < num_configs; (i++, c++)) {           // 遍历配置
                struct usb_interface_descriptor *desc = NULL;

                /* It's possible that a config has no interfaces! */
                if (c->desc.bNumInterfaces > 0)               // 判断当前配置接口的个数
                        desc = &c->intf_cache[0]->altsetting->desc;

                /*
                 * HP's USB bus-powered keyboard has only one configuration
                 * and it claims to be self-powered; other devices may have
                 * similar errors in their descriptors.  If the next test
                 * were allowed to execute, such configurations would always
                 * be rejected and the devices would not work as expected.
                 * In the meantime, we run the risk of selecting a config
                 * that requires external power at a time when that power
                 * isn't available.  It seems to be the lesser of two evils.
                 *
                 * Bugzilla #6448 reports a device that appears to crash
                 * when it receives a GET_DEVICE_STATUS request!  We don't
                 * have any other way to tell whether a device is self-powered,
                 * but since we don't use that information anywhere but here,
                 * the call has been removed.
                 *
                 * Maybe the GET_DEVICE_STATUS call and the test below can
                 * be reinstated when device firmwares become more reliable.
                 * Don't hold your breath.
                 */
#if 0
                /* Rule out self-powered configs for a bus-powered device */
                if (bus_powered && (c->desc.bmAttributes &
                                        USB_CONFIG_ATT_SELFPOWER))
                        continue;
#endif
    /*
                 * The next test may not be as effective as it should be.
                 * Some hubs have errors in their descriptor, claiming
                 * to be self-powered when they are really bus-powered.
                 * We will overestimate the amount of current such hubs
                 * make available for each port.
                 *
                 * This is a fairly benign sort of failure.  It won't
                 * cause us to reject configurations that we should have
                 * accepted.
                 */

                /* Rule out configs that draw too much bus current */
                if (usb_get_max_power(udev, c) > udev->bus_mA) {
                        insufficient_power++;
                        continue;
                }

                /*
                 * Select first configuration as default for audio so that
                 * devices that don't comply with UAC3 protocol are supported.
                 * But, still iterate through other configurations and
                 * select UAC3 compliant config if present.
                 */
                if (desc && is_audio(desc)) {
                        /* Always prefer the first found UAC3 config */
                        if (is_uac3_config(desc)) {
                                best = c;
                                break;
                        }

                        /* If there is no UAC3 config, prefer the first config */
                        else if (i == 0)
                                best = c;

                        /* Unconditional continue, because the rest of the code
                         * in the loop is irrelevant for audio devices, and
                         * because it can reassign best, which for audio devices
                         * we don't want.
                         */
                        continue;
                }

                /* When the first config's first interface is one of Microsoft's
                 * pet nonstandard Ethernet-over-USB protocols, ignore it unless
                 * this kernel has enabled the necessary host side driver.
                 * But: Don't ignore it if it's the only config.
                 */
                if (i == 0 && num_configs > 1 && desc &&
                                (is_rndis(desc) || is_activesync(desc))) {
#if !defined(CONFIG_USB_NET_RNDIS_HOST) && !defined(CONFIG_USB_NET_RNDIS_HOST_MODULE)
                        continue;
#else
                        best = c;
#endif
                }
  /* From the remaining configs, choose the first one whose
                 * first interface is for a non-vendor-specific class.
                 * Reason: Linux is more likely to have a class driver
                 * than a vendor-specific driver. */
                else if (udev->descriptor.bDeviceClass !=
                                                USB_CLASS_VENDOR_SPEC &&
                                (desc && desc->bInterfaceClass !=
                                                USB_CLASS_VENDOR_SPEC)) {
                        best = c;
                        break;
                }

                /* If all the remaining configs are vendor-specific,
                 * choose the first one. */
                else if (!best)
                        best = c;
        }

        if (insufficient_power > 0)
                dev_info(&udev->dev, "rejected %d configuration%s "
                        "due to insufficient available bus power\n",
                        insufficient_power, plural(insufficient_power));

        if (best) {
                i = best->desc.bConfigurationValue;
                dev_dbg(&udev->dev,
                        "configuration #%d chosen from %d choice%s\n",
                        i, num_configs, plural(num_configs));
        } else {
                i = -1;
                dev_warn(&udev->dev,
                        "no configuration chosen from %d choice%s\n",
                        num_configs, plural(num_configs));
        }
        return i;
}
复制代码

至于怎么选择配置的,有兴趣的可以自己研究这段源码。

3.3.7 usb_set_configuration

usb_set_configuration定义在drivers/usb/core/message.c

复制代码
/*
 * usb_set_configuration - Makes a particular device setting be current
 * @dev: the device whose configuration is being updated
 * @configuration: the configuration being chosen.
 * Context: !in_interrupt(), caller owns the device lock
 *
 * This is used to enable non-default device modes.  Not all devices
 * use this kind of configurability; many devices only have one
 * configuration.
 *
 * @configuration is the value of the configuration to be installed.
 * According to the USB spec (e.g. section 9.1.1.5), configuration values
 * must be non-zero; a value of zero indicates that the device in
 * unconfigured.  However some devices erroneously use 0 as one of their
 * configuration values.  To help manage such devices, this routine will
 * accept @configuration = -1 as indicating the device should be put in
 * an unconfigured state.
 *
 * USB device configurations may affect Linux interoperability,
 * power consumption and the functionality available.  For example,
 * the default configuration is limited to using 100mA of bus power,
 * so that when certain device functionality requires more power,
 * and the device is bus powered, that functionality should be in some
 * non-default device configuration.  Other device modes may also be
 * reflected as configuration options, such as whether two ISDN
 * channels are available independently; and choosing between open
 * standard device protocols (like CDC) or proprietary ones.
 *
 * Note that a non-authorized device (dev->authorized == 0) will only
 * be put in unconfigured mode.
 *
 * Note that USB has an additional level of device configurability,
 * associated with interfaces.  That configurability is accessed using
 * usb_set_interface().
 *
 * This call is synchronous. The calling context must be able to sleep,
 * must own the device lock, and must not hold the driver model's USB
 * bus mutex; usb interface driver probe() methods cannot use this routine.
 *
 * Returns zero on success, or else the status code returned by the
 * underlying call that failed.  On successful completion, each interface
 * in the original device configuration has been destroyed, and each one
 * in the new configuration has been probed by all relevant usb device
 * drivers currently known to the kernel.
 */
int usb_set_configuration(struct usb_device *dev, int configuration)
{
        int i, ret;
        struct usb_host_config *cp = NULL;
        struct usb_interface **new_interfaces = NULL;
        struct usb_hcd *hcd = bus_to_hcd(dev->bus);
        int n, nintf;

        if (dev->authorized == 0 || configuration == -1)
                configuration = 0;
        else {
                for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {  // 遍历每一个配置
                        if (dev->config[i].desc.bConfigurationValue ==      //  从配置描述符中获取配置值,配置值保存的是该配置在dev->config中的索引
                                        configuration) {
                                cp = &dev->config[i];           // 获取当前指定的配置
                                break;
                        }
                }
        }
        if ((!cp && configuration != 0))
                return -EINVAL;

       ......

        /* Now that all the interfaces are set up, register them
         * to trigger binding of drivers to interfaces.  probe()
         * routines may install different altsettings and may
         * claim() any interfaces not yet bound.  Many class drivers
         * need that: CDC, audio, video, etc.
         */
        for (i = 0; i < nintf; ++i) {            // 遍历当前配置的每一个接口
                struct usb_interface *intf = cp->interface[i];    

                if (intf->dev.of_node &&
                    !of_device_is_available(intf->dev.of_node)) {
                        dev_info(&dev->dev, "skipping disabled interface %d\n",
                                 intf->cur_altsetting->desc.bInterfaceNumber);
                        continue;
                }

                dev_dbg(&dev->dev,
                        "adding %s (config #%d, interface %d)\n",
                        dev_name(&intf->dev), configuration,
                        intf->cur_altsetting->desc.bInterfaceNumber);
                device_enable_async_suspend(&intf->dev);
                ret = device_add(&intf->dev);                 // 注册当前接口设备到内核
                if (ret != 0) {
                        dev_err(&dev->dev, "device_add(%s) --> %d\n",
                                dev_name(&intf->dev), ret);
                        continue;
                }
                create_intf_ep_devs(intf);
        }

        usb_autosuspend_device(dev);
        return 0;
}
复制代码

在usb_set_configuration的尾部,会将usb_device设备指定配置里的每一个接口设备注册到内核中。

在执行device_add后导致总线的匹配函数usb_device_match再次被调用,这次由于是接口设备,设备类型为usb_if_device_type,那么匹配的一定是接口驱动,于是会执行usb_device_match的else分支,去匹配接口驱动。

根据上面对usb_device_match的分析,此时在core注册的hub_driver的hub_probe函数被调用。该部分内容在第四小节介绍。

3.3.8 usb_hcd_poll_rh_status
复制代码
/*
 * Root Hub interrupt transfers are polled using a timer if the
 * driver requests it; otherwise the driver is responsible for
 * calling usb_hcd_poll_rh_status() when an event occurs.
 *
 * Completions are called in_interrupt(), but they may or may not
 * be in_irq().
 */
void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
{
        struct urb      *urb;
        int             length;
        unsigned long   flags;
        char            buffer[6];      /* Any root hubs with > 31 ports? */

        if (unlikely(!hcd->rh_pollable))
                return;
        if (!hcd->uses_new_polling && !hcd->status_urb)
                return;

        length = hcd->driver->hub_status_data(hcd, buffer);
        if (length > 0) {

                /* try to complete the status urb */
                spin_lock_irqsave(&hcd_root_hub_lock, flags);
                urb = hcd->status_urb;
                if (urb) {
                        clear_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
                        hcd->status_urb = NULL;
                        urb->actual_length = length;
                        memcpy(urb->transfer_buffer, buffer, length);

                        usb_hcd_unlink_urb_from_ep(hcd, urb);
                        usb_hcd_giveback_urb(hcd, urb, 0);
                } else {
                        length = 0;
                        set_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
                }
                spin_unlock_irqrestore(&hcd_root_hub_lock, flags);
        }

        /* The USB 2.0 spec says 256 ms.  This is close enough and won't
         * exceed that limit if HZ is 100. The math is more clunky than
         * maybe expected, this is to make sure that all timers for USB devices
         * fire at the same time to give the CPU a break in between */
        if (hcd->uses_new_polling ? HCD_POLL_RH(hcd) :
                        (length == 0 && hcd->status_urb != NULL))
                mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));   // 修改定时器超时时间,并重新激活定时器
}
复制代码

usb_hcd_poll_rh_status 调用主机控制器的hub_status_data函数获取端口状态。如果端口的状态有变化,那么length > 0,把获取到的端口状态的数组拷贝到urb->transfer_buffer中,就是前面的hub->buffer中,同时调用usb_hcd_giveback_urb函数。

3.3.9 usb_hcd_giveback_urb
复制代码
/**
 * usb_hcd_giveback_urb - return URB from HCD to device driver
 * @hcd: host controller returning the URB
 * @urb: urb being returned to the USB device driver.
 * @status: completion status code for the URB.
 * Context: in_interrupt()
 *
 * This hands the URB from HCD to its USB device driver, using its
 * completion function.  The HCD has freed all per-urb resources
 * (and is done using urb->hcpriv).  It also released all HCD locks;
 * the device driver won't cause problems if it frees, modifies,
 * or resubmits this URB.
 *
 * If @urb was unlinked, the value of @status will be overridden by
 * @urb->unlinked.  Erroneous short transfers are detected in case
 * the HCD hasn't checked for them.
 */
void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status)
{
        struct giveback_urb_bh *bh;
        bool running, high_prio_bh;

        /* pass status to tasklet via unlinked */
        if (likely(!urb->unlinked))
                urb->unlinked = status;

        if (!hcd_giveback_urb_in_bh(hcd) && !is_root_hub(urb->dev)) {
                __usb_hcd_giveback_urb(urb);
                return;
        }

        if (usb_pipeisoc(urb->pipe) || usb_pipeint(urb->pipe)) {
                bh = &hcd->high_prio_bh;
                high_prio_bh = true;
        } else {
                bh = &hcd->low_prio_bh;
                high_prio_bh = false;
        }

        spin_lock(&bh->lock);
        list_add_tail(&urb->urb_list, &bh->head);
        running = bh->running;
        spin_unlock(&bh->lock);

        if (running)
                ;
        else if (high_prio_bh)
                tasklet_hi_schedule(&bh->bh);
        else
                tasklet_schedule(&bh->bh);
}
static void __usb_hcd_giveback_urb(struct urb *urb)
{
        struct usb_hcd *hcd = bus_to_hcd(urb->dev->bus);
        struct usb_anchor *anchor = urb->anchor;
        int status = urb->unlinked;

        urb->hcpriv = NULL;
        if (unlikely((urb->transfer_flags & URB_SHORT_NOT_OK) &&
            urb->actual_length < urb->transfer_buffer_length &&
            !status))
                status = -EREMOTEIO;

        unmap_urb_for_dma(hcd, urb);
        usbmon_urb_complete(&hcd->self, urb, status);
        usb_anchor_suspend_wakeups(anchor);
        usb_unanchor_urb(urb);
        if (likely(status == 0))
                usb_led_activity(USB_LED_EVENT_HOST);

        /* pass ownership to the completion handler */
        urb->status = status;
        urb->complete(urb);

        usb_anchor_resume_wakeups(anchor);
        atomic_dec(&urb->use_count);
        if (unlikely(atomic_read(&urb->reject)))
                wake_up(&usb_kill_urb_queue);
        usb_put_urb(urb);
}
复制代码

usb_hcd_giveback_urb函数中调用urb->complete (urb),而urb->complete = hub_irq,这样就返回到了hub中 ;

3.4 usb_remove_hcd

用usb_hcd结构体定义好usb_hdc设备后,用usb_remove_hcd函数从linux内核卸载usb主机控制器驱动,函数定义在drivers/usb/core/hcd.c:

复制代码
/**
 * usb_remove_hcd - shutdown processing for generic HCDs
 * @hcd: the usb_hcd structure to remove
 * Context: !in_interrupt()
 *
 * Disconnects the root hub, then reverses the effects of usb_add_hcd(),
 * invoking the HCD's stop() method.
 */
void usb_remove_hcd(struct usb_hcd *hcd)
{
        struct usb_device *rhdev = hcd->self.root_hub;

        dev_info(hcd->self.controller, "remove, state %x\n", hcd->state);

        usb_get_dev(rhdev);
        sysfs_remove_group(&rhdev->dev.kobj, &usb_bus_attr_group);

        clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
        if (HC_IS_RUNNING (hcd->state))
                hcd->state = HC_STATE_QUIESCING;

        dev_dbg(hcd->self.controller, "roothub graceful disconnect\n");
        spin_lock_irq (&hcd_root_hub_lock);
        hcd->rh_registered = 0;
        spin_unlock_irq (&hcd_root_hub_lock);

#ifdef CONFIG_PM
        cancel_work_sync(&hcd->wakeup_work);
#endif
        cancel_work_sync(&hcd->died_work);

        mutex_lock(&usb_bus_idr_lock);
        usb_disconnect(&rhdev);         /* Sets rhdev to NULL */
        mutex_unlock(&usb_bus_idr_lock);

        /*
         * tasklet_kill() isn't needed here because:
         * - driver's disconnect() called from usb_disconnect() should
         *   make sure its URBs are completed during the disconnect()
         *   callback
         *
         * - it is too late to run complete() here since driver may have
         *   been removed already now
         */

        /* Prevent any more root-hub status calls from the timer.
         * The HCD might still restart the timer (if a port status change
         * interrupt occurs), but usb_hcd_poll_rh_status() won't invoke
         * the hub_status_data() callback.
         */
        hcd->rh_pollable = 0;
        clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
        del_timer_sync(&hcd->rh_timer);

        hcd->driver->stop(hcd);
        hcd->state = HC_STATE_HALT;

        /* In case the HCD restarted the timer, stop it again. */
        clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
        del_timer_sync(&hcd->rh_timer);

        if (usb_hcd_is_primary_hcd(hcd)) {
                if (hcd->irq > 0)
                        free_irq(hcd->irq, hcd);
        }

        usb_deregister_bus(&hcd->self);
        hcd_buffer_destroy(hcd);

        usb_phy_roothub_power_off(hcd->phy_roothub);
        usb_phy_roothub_exit(hcd->phy_roothub);

        usb_put_invalidate_rhdev(hcd);
        hcd->flags = 0;
}
复制代码

四、usb hub

4.1 stuct usb_hub结构体

在linux中内核中,使用struct usb_hub来表示hub设备,其定义在drivers/usb/core/hub.h:

复制代码
struct usb_hub {
        struct device           *intfdev;       /* the "interface" device */
        struct usb_device       *hdev;
        struct kref             kref;
        struct urb              *urb;           /* for interrupt polling pipe */

        /* buffer for urb ... with extra space in case of babble */
        u8                      (*buffer)[8];
        union {
                struct usb_hub_status   hub;
                struct usb_port_status  port;
        }                       *status;        /* buffer for status reports */
        struct mutex            status_mutex;   /* for the status buffer */

        int                     error;          /* last reported error */
        int                     nerrors;        /* track consecutive errors */

        unsigned long           event_bits[1];  /* status change bitmask */
        unsigned long           change_bits[1]; /* ports with logical connect
                                                        status change */
        unsigned long           removed_bits[1]; /* ports with a "removed"
                                                        device present */
        unsigned long           wakeup_bits[1]; /* ports that have signaled
                                                        remote wakeup */
        unsigned long           power_bits[1]; /* ports that are powered */
        unsigned long           child_usage_bits[1]; /* ports powered on for
                                                        children */
        unsigned long           warm_reset_bits[1]; /* ports requesting warm
                                                        reset recovery */
#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */
#error event_bits[] is too short!
#endif

        struct usb_hub_descriptor *descriptor;  /* class descriptor */
        struct usb_tt           tt;             /* Transaction Translator */

        unsigned                mA_per_port;    /* current for each child */
#ifdef  CONFIG_PM
        unsigned                wakeup_enabled_descendants;
#endif

        unsigned                limited_power:1;
        unsigned                quiescing:1;
        unsigned                disconnected:1;
        unsigned                in_reset:1;

        unsigned                quirk_check_port_auto_suspend:1;

        unsigned                has_indicators:1;
        u8                      indicator[USB_MAXCHILDREN];
        struct delayed_work     leds;
        struct delayed_work     init_work;
        struct work_struct      events;
        spinlock_t              irq_urb_lock;
        struct timer_list       irq_urb_retry;
        struct usb_port         **ports;
}
复制代码

其中:

  • hdev:指向我们前面register_root_hub中介绍的为根hub申请的struct usb_device结构;
  • intfdev: 在之前我们从根hub这个usb_device设备中选择了一个配置,一个配置又包含若干个接口,这里存放的就是接口usb_interface的设备基类,即struct device结构;
  • events:表示当前工作,struct work_struct结构;
  • buffer:urb缓冲区;
  • descriptor:usb_hub描述符;

usb_hub_descriptor定义在include/uapi/linux/usb/ch11.h:

复制代码
struct usb_hub_descriptor {
        __u8  bDescLength;          // 描述符长度
        __u8  bDescriptorType;      // 描述符类型  
        __u8  bNbrPorts;            // 端口数量 
        __le16 wHubCharacteristics;
        __u8  bPwrOn2PwrGood;
        __u8  bHubContrCurrent;

        /* 2.0 and 3.0 hubs differ here */
        union {
                struct {
                        /* add 1 bit for hub status change; round to bytes */
                        __u8  DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8];
                        __u8  PortPwrCtrlMask[(USB_MAXCHILDREN + 1 + 7) / 8];
                }  __attribute__ ((packed)) hs;

                struct {
                        __u8 bHubHdrDecLat;
                        __le16 wHubDelay;
                        __le16 DeviceRemovable;
                }  __attribute__ ((packed)) ss;
        } u;
} __attribute__ ((packed));
复制代码

4.2 hub_probe

当根hub接口设备注册之后,hub_probe函数被调用,函数定义在drivers/usb/core/hub.c:

复制代码
static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
        struct usb_host_interface *desc;
        struct usb_device *hdev;
        struct usb_hub *hub;

        desc = intf->cur_altsetting;       // 当前激活的接口配置   
        hdev = interface_to_usbdev(intf);  // 根据接口获取到usb设备

#ifdef CONFIG_PM
        if (hdev->dev.power.autosuspend_delay >= 0)
                pm_runtime_set_autosuspend_delay(&hdev->dev, 0);
#endif
/*
         * Hubs have proper suspend/resume support, except for root hubs
         * where the controller driver doesn't have bus_suspend and
         * bus_resume methods.
         */
        if (hdev->parent) {             /* normal device */
                usb_enable_autosuspend(hdev);
        } else {                        /* root hub */
                const struct hc_driver *drv = bus_to_hcd(hdev->bus)->driver;

                if (drv->bus_suspend && drv->bus_resume)
                        usb_enable_autosuspend(hdev);
        }

        if (hdev->level == MAX_TOPO_LEVEL) {
                dev_err(&intf->dev,
                        "Unsupported bus topology: hub nested too deep\n");
                return -E2BIG;
        }

#ifdef  CONFIG_USB_OTG_BLACKLIST_HUB
        if (hdev->parent) {
                dev_warn(&intf->dev, "ignoring external hub\n");
                return -ENODEV;
        }
#endif

        if (!hub_descriptor_is_sane(desc)) {
                dev_err(&intf->dev, "bad descriptor, ignoring hub\n");
                return -EIO;
        }

        /* We found a hub */
        dev_info(&intf->dev, "USB hub found\n");

        hub = kzalloc(sizeof(*hub), GFP_KERNEL);    // 分配usb_hub结构体
        if (!hub)
                return -ENOMEM;

        kref_init(&hub->kref);
        hub->intfdev = &intf->dev;
        hub->hdev = hdev;
        INIT_DELAYED_WORK(&hub->leds, led_work);
        INIT_DELAYED_WORK(&hub->init_work, NULL);
        INIT_WORK(&hub->events, hub_event);
        spin_lock_init(&hub->irq_urb_lock);
        timer_setup(&hub->irq_urb_retry, hub_retry_irq_urb, 0);  // 设置定时器
        usb_get_intf(intf);
        usb_get_dev(hdev);

        usb_set_intfdata(intf, hub);
        intf->needs_remote_wakeup = 1;
        pm_suspend_ignore_children(&intf->dev, true);
 if (hdev->speed == USB_SPEED_HIGH)
                highspeed_hubs++;

        if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND)
                hub->quirk_check_port_auto_suspend = 1;

        if (hub_configure(hub, &desc->endpoint[0].desc) >= 0)  // 配置hub  一个接口包含若干个端点、第二个参数为接口端点0的端点描述符
                return 0;

        hub_disconnect(intf);
        return -ENODEV;
}
复制代码

这里:

  • 首先动态分配usb_hub结构;
  • 填充kref、hdev、init_work、events等;
  • 调用hub_configure配置hub;

这里我们重点关注如下代码,具体可以参考linux驱动移植-软中断工作队列部分内容,

INIT_WORK(&hub->events, hub_event);

这里调用INIT_WORK进行对work初始化,INIT_WORK宏展开后如下,work_struct实例最重要的成员就是func函数指针,这里把此指针初始化为hub_event()函数:

 __init_work((&hub->events), _onstack);     
(&hub->events)->data = (atomic_long_t) WORK_DATA_INIT();     
 INIT_LIST_HEAD(&(&hub->events)->entry); 
 (&hub->events)->func = (hub_event);   
这是INIT_WORK宏指向后的结果:

4.3 hub_configure

复制代码
static int hub_configure(struct usb_hub *hub,
        struct usb_endpoint_descriptor *endpoint)
{
        struct usb_hcd *hcd;
        struct usb_device *hdev = hub->hdev;
        struct device *hub_dev = hub->intfdev;
        u16 hubstatus, hubchange;
        u16 wHubCharacteristics;
        unsigned int pipe;
        int maxp, ret, i;
        char *message = "out of memory";
        unsigned unit_load;
        unsigned full_load;
        unsigned maxchild;

        hub->buffer = kmalloc(sizeof(*hub->buffer), GFP_KERNEL);
        if (!hub->buffer) {
                ret = -ENOMEM;
                goto fail;
        }

        hub->status = kmalloc(sizeof(*hub->status), GFP_KERNEL);
        if (!hub->status) {
                ret = -ENOMEM;
                goto fail;
        }
        mutex_init(&hub->status_mutex);

        hub->descriptor = kzalloc(sizeof(*hub->descriptor), GFP_KERNEL);
        if (!hub->descriptor) {
                ret = -ENOMEM;
                goto fail;
        }

        /* Request the entire hub descriptor.
         * hub->descriptor can handle USB_MAXCHILDREN ports,
         * but a (non-SS) hub can/will return fewer bytes here.
         */
        ret = get_hub_descriptor(hdev, hub->descriptor);
        if (ret < 0) {
                message = "can't read hub descriptor";
                goto fail;
        }

        maxchild = USB_MAXCHILDREN;
        if (hub_is_superspeed(hdev))
                maxchild = min_t(unsigned, maxchild, USB_SS_MAXPORTS);

        if (hub->descriptor->bNbrPorts > maxchild) {
                message = "hub has too many ports!";
                ret = -ENODEV;
                goto fail;
        } else if (hub->descriptor->bNbrPorts == 0) {
                message = "hub doesn't have any ports!";
                ret = -ENODEV;
                goto fail;
        }
 /*
         * Accumulate wHubDelay + 40ns for every hub in the tree of devices.
         * The resulting value will be used for SetIsochDelay() request.
         */
        if (hub_is_superspeed(hdev) || hub_is_superspeedplus(hdev)) {
                u32 delay = __le16_to_cpu(hub->descriptor->u.ss.wHubDelay);

                if (hdev->parent)
                        delay += hdev->parent->hub_delay;

                delay += USB_TP_TRANSMISSION_DELAY;
                hdev->hub_delay = min_t(u32, delay, USB_TP_TRANSMISSION_DELAY_MAX);
        }

        maxchild = hub->descriptor->bNbrPorts;
        dev_info(hub_dev, "%d port%s detected\n", maxchild,
                        (maxchild == 1) ? "" : "s");

        hub->ports = kcalloc(maxchild, sizeof(struct usb_port *), GFP_KERNEL);
        if (!hub->ports) {
                ret = -ENOMEM;
                goto fail;
        }

        wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
        if (hub_is_superspeed(hdev)) {
                unit_load = 150;
                full_load = 900;
        } else {
                unit_load = 100;
                full_load = 500;
        }

        /* FIXME for USB 3.0, skip for now */
        if ((wHubCharacteristics & HUB_CHAR_COMPOUND) &&
                        !(hub_is_superspeed(hdev))) {
                char    portstr[USB_MAXCHILDREN + 1];

                for (i = 0; i < maxchild; i++)
                        portstr[i] = hub->descriptor->u.hs.DeviceRemovable
                                    [((i + 1) / 8)] & (1 << ((i + 1) % 8))
                                ? 'F' : 'R';
                portstr[maxchild] = 0;
                dev_dbg(hub_dev, "compound device; port removable status: %s\n", portstr);
        } else
                dev_dbg(hub_dev, "standalone hub\n");

        switch (wHubCharacteristics & HUB_CHAR_LPSM) {
        case HUB_CHAR_COMMON_LPSM:
                dev_dbg(hub_dev, "ganged power switching\n");
                break;
        case HUB_CHAR_INDV_PORT_LPSM:
                dev_dbg(hub_dev, "individual port power switching\n");
                break;
        case HUB_CHAR_NO_LPSM:
        case HUB_CHAR_LPSM:
                dev_dbg(hub_dev, "no power switching (usb 1.0)\n");
                break;
        }

        switch (wHubCharacteristics & HUB_CHAR_OCPM) {
        case HUB_CHAR_COMMON_OCPM:
                dev_dbg(hub_dev, "global over-current protection\n");
                break;
        case HUB_CHAR_INDV_PORT_OCPM:
                dev_dbg(hub_dev, "individual port over-current protection\n");
                break;
        case HUB_CHAR_NO_OCPM:
        case HUB_CHAR_OCPM:
                dev_dbg(hub_dev, "no over-current protection\n");
                break;
        }
 spin_lock_init(&hub->tt.lock);
        INIT_LIST_HEAD(&hub->tt.clear_list);
        INIT_WORK(&hub->tt.clear_work, hub_tt_work);
        switch (hdev->descriptor.bDeviceProtocol) {
        case USB_HUB_PR_FS:
                break;
        case USB_HUB_PR_HS_SINGLE_TT:
                dev_dbg(hub_dev, "Single TT\n");
                hub->tt.hub = hdev;
                break;
        case USB_HUB_PR_HS_MULTI_TT:
                ret = usb_set_interface(hdev, 0, 1);
                if (ret == 0) {
                        dev_dbg(hub_dev, "TT per port\n");
                        hub->tt.multi = 1;
                } else
                        dev_err(hub_dev, "Using single TT (err %d)\n",
                                ret);
                hub->tt.hub = hdev;
                break;
        case USB_HUB_PR_SS:
                /* USB 3.0 hubs don't have a TT */
                break;
        default:
                dev_dbg(hub_dev, "Unrecognized hub protocol %d\n",
                        hdev->descriptor.bDeviceProtocol);
                break;
        }

        /* Note 8 FS bit times == (8 bits / 12000000 bps) ~= 666ns */
        switch (wHubCharacteristics & HUB_CHAR_TTTT) {
        case HUB_TTTT_8_BITS:
                if (hdev->descriptor.bDeviceProtocol != 0) {
                        hub->tt.think_time = 666;
                        dev_dbg(hub_dev, "TT requires at most %d "
                                        "FS bit times (%d ns)\n",
                                8, hub->tt.think_time);
                }
                break;
        case HUB_TTTT_16_BITS:
                hub->tt.think_time = 666 * 2;
                dev_dbg(hub_dev, "TT requires at most %d "
                                "FS bit times (%d ns)\n",
                        16, hub->tt.think_time);
                break;
        case HUB_TTTT_24_BITS:
                hub->tt.think_time = 666 * 3;
                dev_dbg(hub_dev, "TT requires at most %d "
                                "FS bit times (%d ns)\n",
                        24, hub->tt.think_time);
                break;
        case HUB_TTTT_32_BITS:
                hub->tt.think_time = 666 * 4;
                dev_dbg(hub_dev, "TT requires at most %d "
                                "FS bit times (%d ns)\n",
                        32, hub->tt.think_time);
                break;
        }

        /* probe() zeroes hub->indicator[] */
        if (wHubCharacteristics & HUB_CHAR_PORTIND) {
                hub->has_indicators = 1;
                dev_dbg(hub_dev, "Port indicators are supported\n");
        }

        dev_dbg(hub_dev, "power on to power good time: %dms\n",
                hub->descriptor->bPwrOn2PwrGood * 2);
 /* power budgeting mostly matters with bus-powered hubs,
         * and battery-powered root hubs (may provide just 8 mA).
         */
        ret = usb_get_std_status(hdev, USB_RECIP_DEVICE, 0, &hubstatus);
        if (ret) {
                message = "can't get hub status";
                goto fail;
        }
        hcd = bus_to_hcd(hdev->bus);
        if (hdev == hdev->bus->root_hub) {
                if (hcd->power_budget > 0)
                        hdev->bus_mA = hcd->power_budget;
                else
                        hdev->bus_mA = full_load * maxchild;
                if (hdev->bus_mA >= full_load)
                        hub->mA_per_port = full_load;
                else {
                        hub->mA_per_port = hdev->bus_mA;
                        hub->limited_power = 1;
                }
        } else if ((hubstatus & (1 << USB_DEVICE_SELF_POWERED)) == 0) {
                int remaining = hdev->bus_mA -
                        hub->descriptor->bHubContrCurrent;

                dev_dbg(hub_dev, "hub controller current requirement: %dmA\n",
                        hub->descriptor->bHubContrCurrent);
                hub->limited_power = 1;

                if (remaining < maxchild * unit_load)
                        dev_warn(hub_dev,
                                        "insufficient power available "
                                        "to use all downstream ports\n");
                hub->mA_per_port = unit_load;   /* 7.2.1 */

        } else {        /* Self-powered external hub */
                /* FIXME: What about battery-powered external hubs that
                 * provide less current per port? */
                hub->mA_per_port = full_load;
        }
        if (hub->mA_per_port < full_load)
                dev_dbg(hub_dev, "%umA bus power budget for each child\n",
                                hub->mA_per_port);

        ret = hub_hub_status(hub, &hubstatus, &hubchange);
        if (ret < 0) {
                message = "can't get hub status";
                goto fail;
        }

        /* local power status reports aren't always correct */
        if (hdev->actconfig->desc.bmAttributes & USB_CONFIG_ATT_SELFPOWER)
                dev_dbg(hub_dev, "local power source is %s\n",
                        (hubstatus & HUB_STATUS_LOCAL_POWER)
                        ? "lost (inactive)" : "good");

        if ((wHubCharacteristics & HUB_CHAR_OCPM) == 0)
                dev_dbg(hub_dev, "%sover-current condition exists\n",
                        (hubstatus & HUB_STATUS_OVERCURRENT) ? "" : "no ");
 /* set up the interrupt endpoint
         * We use the EP's maxpacket size instead of (PORTS+1+7)/8
         * bytes as USB2.0[11.12.3] says because some hubs are known
         * to send more data (and thus cause overflow). For root hubs,
         * maxpktsize is defined in hcd.c's fake endpoint descriptors
         * to be big enough for at least USB_MAXCHILDREN ports. */
        pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress);
        maxp = usb_maxpacket(hdev, pipe, usb_pipeout(pipe));

        if (maxp > sizeof(*hub->buffer))
                maxp = sizeof(*hub->buffer);

        hub->urb = usb_alloc_urb(0, GFP_KERNEL);
        if (!hub->urb) {
                ret = -ENOMEM;
                goto fail;
        }

        usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
                hub, endpoint->bInterval);

        /* maybe cycle the hub leds */
        if (hub->has_indicators && blinkenlights)
                hub->indicator[0] = INDICATOR_CYCLE;

        mutex_lock(&usb_port_peer_mutex);
        for (i = 0; i < maxchild; i++) {
                ret = usb_hub_create_port_device(hub, i + 1);
                if (ret < 0) {
                        dev_err(hub->intfdev,
                                "couldn't create port%d device.\n", i + 1);
                        break;
                }
        }
        hdev->maxchild = i;
        for (i = 0; i < hdev->maxchild; i++) {
                struct usb_port *port_dev = hub->ports[i];

                pm_runtime_put(&port_dev->dev);
        }

        mutex_unlock(&usb_port_peer_mutex);
        if (ret < 0)
                goto fail;

        /* Update the HCD's internal representation of this hub before hub_wq
         * starts getting port status changes for devices under the hub.
         */
        if (hcd->driver->update_hub_device) {
                ret = hcd->driver->update_hub_device(hcd, hdev,
                                &hub->tt, GFP_KERNEL);
                if (ret < 0) {
                        message = "can't update HCD hub info";
                        goto fail;
                }
        }

        usb_hub_adjust_deviceremovable(hdev, hub->descriptor);

        hub_activate(hub, HUB_INIT);
        return 0;

fail:
        dev_err(hub_dev, "config failed, %s (err %d)\n",
                        message, ret);
        /* hub_disconnect() frees urb and descriptor */
        return ret;
}
View Code
复制代码

主要功能:

  • 填充hub的相关描述符信息,比如buffer缓冲区、status、descriptor、ports、urb 等
  • 调用get_hub_descriptot获取usb hub描述符,即初始化hub->desctiptor;
  • 通过usb_rcvintpipe函数创建一个接受接收(rcv)中断类型的端点管道(pipe),用来端点和数据缓冲区之间的连接;参数如下:
    • dev: usb_device设备结构体;
    • endpoint:为端点描述符的成员endpoint->bEndpointAddress 
      • 对于控制类型的端点管道使用: usb_sndctrlpipe/usb_rcvctrlpipe;
      • 对于实时类型的端点管道使用: usb_sndisocpipe/usb_sndisocpipe;
      • 对于批量类型的端点管道使用: usb_sndbulkpipe/usb_rcvbulkpipe:
  • 通过usb_alloc_urb函数动态分配一个urb;
  • 调用urb_fill_int_urb填充urb结构;设置urb完成时被调用的完成处理函数为hub_irq;
    • 初始化usb中断 ,用于检测hub端口状态,如果有状态发生改变,则会调用 hub_irq;
  • 激活hub,这里主要进行了urb请求的提交工作,通过usb_submit_urb函数提交urb请求;

代码实在太多了,我已经看不下去了,这里我们挑选几个重要的看一下。

4.3.1 usb_control_msg(举例介绍usb消息格式)

之所以介绍这个函数,是因为usb使用这个函数进行消息的收发,后面源码解读会大量涉及到这个函数。该函书定义在drivers/usb/core/message.c:

复制代码
/* returns status (negative) or length (positive) */
static int usb_internal_control_msg(struct usb_device *usb_dev,
                                    unsigned int pipe,
                                    struct usb_ctrlrequest *cmd,
                                    void *data, int len, int timeout)
{
        struct urb *urb;
        int retv;
        int length;

        urb = usb_alloc_urb(0, GFP_NOIO);
        if (!urb)
                return -ENOMEM;

        usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char *)cmd, data,
                             len, usb_api_blocking_completion, NULL);

        retv = usb_start_wait_urb(urb, timeout, &length);
        if (retv < 0)
                return retv;
        else
                return length;
}
/**
 * usb_control_msg - Builds a control urb, sends it off and waits for completion
 * @dev: pointer to the usb device to send the message to
 * @pipe: endpoint "pipe" to send the message to
 * @request: USB message request value
 * @requesttype: USB message request type value
 * @value: USB message value
 * @index: USB message index value
 * @data: pointer to the data to send
 * @size: length in bytes of the data to send
 * @timeout: time in msecs to wait for the message to complete before timing
 *      out (if 0 the wait is forever)
 *
 * Context: !in_interrupt ()
 *
 * This function sends a simple control message to a specified endpoint and
 * waits for the message to complete, or timeout.
 *
 * Don't use this function from within an interrupt context. If you need
 * an asynchronous message, or need to send a message from within interrupt
 * context, use usb_submit_urb(). If a thread in your driver uses this call,
 * make sure your disconnect() method can wait for it to complete. Since you
 * don't have a handle on the URB used, you can't cancel the request.
 *
 * Return: If successful, the number of bytes transferred. Otherwise, a negative
 * error number.
 */
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
                    __u8 requesttype, __u16 value, __u16 index, void *data,
                    __u16 size, int timeout)
{
        struct usb_ctrlrequest *dr;
        int ret;

        dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_NOIO);
        if (!dr)
                return -ENOMEM;

        dr->bRequestType = requesttype;
        dr->bRequest = request;
        dr->wValue = cpu_to_le16(value);
        dr->wIndex = cpu_to_le16(index);
        dr->wLength = cpu_to_le16(size);

        ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout);

        /* Linger a bit, prior to the next control message. */
        if (dev->quirks & USB_QUIRK_DELAY_CTRL_MSG)
                msleep(200);

        kfree(dr);

        return ret;
}
复制代码

函数参数如下:

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(1380)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤
历史上的今天:
2019-05-16 Spring MVC -- 国际化
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示