程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

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

函数参数如下:

  • dev:参数dev指向目标设备的usb_device数据结构;
  • pipe:pipe是一个32位无符号正数,其最高两位表示传输的类型(实时、中断、控制、批量),端点号、设备号,以及数据传输方式;
  • request:usb消息的请求值;其实就是命令,所有的命令都是以不同编码值的方式传递给设备的,bRequest就表示usb命令的编码值。可以是usb标准命令,也可以用户自定义命令:
  • requesttype:usb消息请求类型值,其最高位表示传输的方向,最低5位则表明传输对象的类别(设备、接口、端口、其他);
D7 D6 D5 D4 D3 D2 D1 D0

0:主机到设备

1:设备到主机

00:标准请求命令

01:类请求命令
10:用户定义的命令
11:保留值

0:表示接收者为设备

1:表示接收者为接口

2:表示接收者为端点

3:表示接收者为其他

其他值保留

  • value:usb消息的值;2个字节,不同的usb命令其有不同的含义;
  • index:usb消息的索引值;2个字节,不同的usb命令其有不同的含义;它用于向设备传递特定于请求的参数。wIndex字段通常用于在请求中指定端点或接口,格式如下:
D7 D6 D5 D4 D3 D2 D1 D0
Direction Reserved(Reset to zero) Endpoint Number
D15 D14 D13 D12 D11 D10 D9 D8
Reserved(Reset to zero)
  • data:指向要发送的消息指针;
  • size:要发送的消息的字节长度;一般不论是输入还是输出都要求给出准确的数字。当命令不需要传输数据时,此字段设为0;
  • timeout:超时时间;

这个函数用于发送一个简单的控制信息到一个指定的端点并等待信息传输完成或者传输超时,如果成功,返回已经传输成功的字节数,否则返回一个负的错误值。

以获取描述符标准命令举例,我们参考usb spec2.0手册 9.4章节:

bmRequestType bRequest wValue wIndex wLength Data
10000000B GET_DESCRIPTOR Descriptor Type and Descriptot Index Zero or Language ID(refer to Section 9.6.7) Descriptor Length Descriptor

标准请求命令包括:

bRequest Value
GET_STATUS 0
CLEAR_FEATURE 1
Reserved for duture use 2
SET_FEATURE 3
Reserved for duture use 4
SET_ADDRESS 5
GET_DESCRIPTOR 6
SET_DESCRIPTOR 7
GET_CONFIGURATION 8
SET_CONFIGURATION 9
GET_INTERFACE 10
SET_INTERFACE 11
SYNCH_FRAME 12

描述符类型包括:

Descriptor Types Value
DEVICE 1
CONFIGURATION 2
STRING 3
INTERFACE 4
ENDPOINT 5
DEVICE_QUALIFIER 6
OTHER_SPEED_CONFIGURATION 7
INTERFACE_POWER 8

wValue的高字节表示描述符类型,低字节表示描述符的索引,描述符索引用于在一个设备中实现多个相同类型的描述符时选择一个特定的描述符(仅用于配置和字符描述符),从0开始。

  • 100:表示获取设备描述符usb_device_descriptor;
  • 200:表示获取配置描述符usb_config_descriptor;
  • 300:表示获取字符描述符;
  • 301:iSerialNumber;
  • 302:iProduct;
  • 303:iManufacturer;

当mValue为字符描述符时,wIndex指定为语言ID,否者设置为零;

wLength字段指定返回的字节数。如果描述符比wLength字段长,则只返回描述符的初始字节。如果描述符短于wLength字段,当请求进一步数据时,设备通过发送一个短数据包来表示控制传输的结束。短报文定义为小于最大负载大小的报文或零长度的数据包;

命令 bRequestType bRequest wValue wIndex wLength 数据
Get Descriptor  1000 0000B GET_DESCRIPTOR    描述符类型和描述符索引 0或者语言ID 描述符长度 返回的描述符信息
获取设备描述符 0x80 6 0x100 0 0x12  
获取配置描述符 0x80 6 0x200 0 0x9  
获取字符描述符 0x80 6 0x300 0 0xFF  
获取字符描述符 0x80 6 0x301 0x409 0xFF  
获取字符描述符 0x80 6 0x302 0x409 0xFF  
获取字符描述符 0x80 6 0x303 0x409 0xFF  

在获取描述符的命令中,我们可以发现在请求参数中并没有指定目标usb设备的端口号和设备地址,这主要由于此时还未对新设备分配新的设备地址,此时只能通过控制端点0进行数据的收发。

4.3.1 get_hub_descriptor
/* USB 2.0 spec Section 11.24.4.5 */
static int get_hub_descriptor(struct usb_device *hdev,
                struct usb_hub_descriptor *desc)
{
        int i, ret, size;
        unsigned dtype;

        if (hub_is_superspeed(hdev)) {
                dtype = USB_DT_SS_HUB;
                size = USB_DT_SS_HUB_SIZE;
        } else {
                dtype = USB_DT_HUB;
                size = sizeof(struct usb_hub_descriptor);
        }

        for (i = 0; i < 3; i++) {
                ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
                        USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB,
                        dtype << 8, 0, desc, size,
                        USB_CTRL_GET_TIMEOUT);
                if (hub_is_superspeed(hdev)) {
                        if (ret == size)
                                return ret;
                } else if (ret >= USB_DT_HUB_NONVAR_SIZE + 2) {
                        /* Make sure we have the DeviceRemovable field. */
                        size = USB_DT_HUB_NONVAR_SIZE + desc->bNbrPorts / 8 + 1;
                        if (ret < size)
                                return -EMSGSIZE;
                        return ret;
                }
        }
        return -EINVAL;
}

我们来看一下usb_control_msg这个函数的参数:

  • dev:指向了为根hub申请的struct usb_device结构;
  • pipe:usb_rcvctrlpipe(hdev, 0),创建一个接受接收(rcv)中断类型的端点管道(pipe);
  • request:USB_REQ_GET_DESCRIPTOR,请求值wRequest设置为GET_DESCRIPTOR,值为0x06;
  • requestType:USB_DIR_IN | USB_RT_HUB,请求类型值wRequestType设置为0xA0,是Get Hub Descriptor的请求类型(需要注意的是Get Hub Descriptor和Get Descriptor是完全不一样的请求类型),方向为输入,即从usb设备到主机;具体可以参考usb 2.0 spec 11.24.2.5;
#define USB_DIR_IN            0x80        /* to host */
#define USB_RT_HUB    (USB_TYPE_CLASS | USB_RECIP_DEVICE)  //0x10
#define USB_TYPE_CLASS            (0x01 << 5)
#define USB_RECIP_DEVICE        0x00
#define USB_REQ_GET_DESCRIPTOR 0x06
  • value:dtype << 8,dtype = USB_DT_HUB,USB_DT_HUB的值为0x29,所以wValue被设置为0x2900;wValue高字节表示描述符类型,低字节表示描述符索引,所以描述符类型是0x29,描述符索引是0x00;
#define USB_DT_HUB            (USB_TYPE_CLASS | 0x09)
#define USB_TYPE_CLASS            (0x01 << 5)
  • index:wIndex设置为0;
  • data: 将获取到的描述符信息保存到desc;
  • size:表示usb hub描述符的长度;

那该消息发送成功,获取到的hub descriptor格式是什么样的呢?具体可以参考usb 2.0 spec 11.23.2.1;

Offset Field Size Description
0 bDescLength 1 Number of bytes in this descriptor, including this byte
1 bDescriptorType 1 Descriptor Type, value: 29H for hub descriptor
2 bNbrPorts 1

Number of downstream facing ports that this hub
supports

3 wHubCharacteristics 2

D1...D0: Logical Power Switching Mode
00: Ganged power switching (all ports’ power at
once)
01: Individual port power switching
1X: Reserved. Used only on 1.0 compliant hubs
that implement no power switching
D2: Identifies a Compound Device
0: Hub is not part of a compound device.
1: Hub is part of a compound device.
D4...D3: Over-current Protection Mode
00: Global Over-current Protection. The hub
reports over-current as a summation of all
ports’ current draw, without a breakdown of
individual port over-current status.
01: Individual Port Over-current Protection. The
hub reports over-current on a per-port basis.
Each port has an over-current status.
1X: No Over-current Protection. This option is
allowed only for bus-powered hubs that do not
implement over-current protection.

D6...D5: TT Think TIme
00: TT requires at most 8 FS bit times of inter
transaction gap on a full-/low-speed
downstream bus.
01: TT requires at most 16 FS bit times.
10: TT requires at most 24 FS bit times.
11: TT requires at most 32 FS bit times.
D7: Port Indicators Supported
0: Port Indicators are not supported on its
downstream facing ports and the
PORT_INDICATOR request has no effect.
1: Port Indicators are supported on its
downstream facing ports and the
PORT_INDICATOR request controls the
indicators. See Section 11.5.3.
D15...D8: Reserved

5 bPwrOn2PwrGood 1

Time (in 2 ms intervals) from the time the power-on
sequence begins on a port until power is good on that
port. The USB System Software uses this value to
determine how long to wait before accessing a
powered-on port.

 
6 bHubContrCurrent 1  Maximum current requirements of the Hub Controller

electronics in mA.

7 DeviceRemovable

Variable,
depending
on
number of
ports on
hub

 Indicates if a port has a removable device attached.

This field is reported on byte-granularity. Within a
byte, if no port exists for a given location, the field
representing the port characteristics returns 0.
Bit value definition:
0B - Device is removable.
1B - Device is non-removable
This is a bitmap corresponding to the individual ports
on the hub:
Bit 0: Reserved for future use.
Bit 1: Port 1
Bit 2: Port 2
....
Bit n: Port n (implementation-dependent, up to a
maximum of 255 ports).

Variable PortPwrCtrlMask

Variable,
depending
on
number of
ports on
hub

 This field exists for reasons of compatibility with

software written for 1.0 compliant devices. All bits in
this field should be set to 1B. This field has one bit for
each port on the hub with additional pad bits, if
necessary, to make the number of bits in the field an
integer multiple of 8.

我们不难看出,struct usb_hub_descriptor结构体的成员变量和hub描述符的信息是一致的。

4.3.2 hub_hub_status
/*
 * USB 2.0 spec Section 11.24.2.6
 */
static int get_hub_status(struct usb_device *hdev,
                struct usb_hub_status *data)
{
        int i, status = -ETIMEDOUT;

        for (i = 0; i < USB_STS_RETRIES &&
                        (status == -ETIMEDOUT || status == -EPIPE); i++) {
                status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
                        USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB, 0, 0,
                        data, sizeof(*data), USB_STS_TIMEOUT);
        }
        return status;
}

static int hub_hub_status(struct usb_hub *hub,
                u16 *status, u16 *change)
{
        int ret;

        mutex_lock(&hub->status_mutex);
        ret = get_hub_status(hub->hdev, &hub->status->hub);
        if (ret < 0) {
                if (ret != -ENODEV)
                        dev_err(hub->intfdev,
                                "%s failed (err = %d)\n", __func__, ret);
        } else {
                *status = le16_to_cpu(hub->status->hub.wHubStatus);
                *change = le16_to_cpu(hub->status->hub.wHubChange);
                ret = 0;
        }
        mutex_unlock(&hub->status_mutex);
        return ret;
}

这里主要就是获取usb hub的状态,我就不细究了,毕竟usb协议是很复杂的东西,感兴趣去阅读USB 2.0 spec Section 11.24.2.6。

bmRequestType bRequest wValue wIndex wLength Data
10100000B GET_STATE Zero Zero Four Hub status or Change status
4.3.3 usb_fill_int_urb

直接上usb_fill_int_urb的代码,有兴趣自行研究:

/**
 * usb_fill_int_urb - macro to help initialize a interrupt urb
 * @urb: pointer to the urb to initialize.
 * @dev: pointer to the struct usb_device for this urb.
 * @pipe: the endpoint pipe
 * @transfer_buffer: pointer to the transfer buffer
 * @buffer_length: length of the transfer buffer
 * @complete_fn: pointer to the usb_complete_t function
 * @context: what to set the urb context to.
 * @interval: what to set the urb interval to, encoded like
 *      the endpoint descriptor's bInterval value.
 *
 * Initializes a interrupt urb with the proper information needed to submit
 * it to a device.
 *
 * Note that High Speed and SuperSpeed(+) interrupt endpoints use a logarithmic
 * encoding of the endpoint interval, and express polling intervals in
 * microframes (eight per millisecond) rather than in frames (one per
 * millisecond).
 *
 * Wireless USB also uses the logarithmic encoding, but specifies it in units of
 * 128us instead of 125us.  For Wireless USB devices, the interval is passed
 * through to the host controller, rather than being translated into microframe
 * units.
 */
static inline void usb_fill_int_urb(struct urb *urb,
                                    struct usb_device *dev,
                                    unsigned int pipe,
                                    void *transfer_buffer,
                                    int buffer_length,
                                    usb_complete_t complete_fn,
                                    void *context,
                                    int interval)
{
        urb->dev = dev;
        urb->pipe = pipe;
        urb->transfer_buffer = transfer_buffer;
        urb->transfer_buffer_length = buffer_length;
        urb->complete = complete_fn;
        urb->context = context;

        if (dev->speed == USB_SPEED_HIGH || dev->speed >= USB_SPEED_SUPER) {
                /* make sure interval is within allowed range */
                interval = clamp(interval, 1, 16);

                urb->interval = 1 << (interval - 1);
        } else {
                urb->interval = interval;
        }

        urb->start_frame = -1;
}
View Code

这里主要就是初始化urb结构:

  • 初始化urb指向的usb_device设备;
  • 初始化pipe;
  • 初始化数据缓冲区transfer_buffer,即urb完成时接收到的数据保存到*hub->buffer;
  • 设置urb完成时被调用的完成处理函数为hub_irq;
  • 设置urb被轮询到的时间间隔interval,也就是说每过interval,发一次urb请求;

更多urb相关内容参考:usb host 驱动之 urb

4.3.4 hub_activate
static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{
        struct usb_device *hdev = hub->hdev;
        struct usb_hcd *hcd;
        int ret;
        int port1;
        int status;
        bool need_debounce_delay = false;
        unsigned delay;

        /* Continue a partial initialization */
        if (type == HUB_INIT2 || type == HUB_INIT3) {
                device_lock(&hdev->dev);

                /* Was the hub disconnected while we were waiting? */
                if (hub->disconnected)
                        goto disconnected;
                if (type == HUB_INIT2)
                        goto init2;
                goto init3;
        }
        kref_get(&hub->kref);

        /* The superspeed hub except for root hub has to use Hub Depth
         * value as an offset into the route string to locate the bits
         * it uses to determine the downstream port number. So hub driver
         * should send a set hub depth request to superspeed hub after
         * the superspeed hub is set configuration in initialization or
         * reset procedure.
         *
         * After a resume, port power should still be on.
         * For any other type of activation, turn it on.
         */
        if (type != HUB_RESUME) {
                if (hdev->parent && hub_is_superspeed(hdev)) {
                        ret = usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
                                        HUB_SET_DEPTH, USB_RT_HUB,
                                        hdev->level - 1, 0, NULL, 0,
                                        USB_CTRL_SET_TIMEOUT);
                        if (ret < 0)
                                dev_err(hub->intfdev,
                                                "set hub depth failed\n");
                }
 /* Speed up system boot by using a delayed_work for the
                 * hub's initial power-up delays.  This is pretty awkward
                 * and the implementation looks like a home-brewed sort of
                 * setjmp/longjmp, but it saves at least 100 ms for each
                 * root hub (assuming usbcore is compiled into the kernel
                 * rather than as a module).  It adds up.
                 *
                 * This can't be done for HUB_RESUME or HUB_RESET_RESUME
                 * because for those activation types the ports have to be
                 * operational when we return.  In theory this could be done
                 * for HUB_POST_RESET, but it's easier not to.
                 */
                if (type == HUB_INIT) {
                        delay = hub_power_on_good_delay(hub);

                        hub_power_on(hub, false);
                        INIT_DELAYED_WORK(&hub->init_work, hub_init_func2);
                        queue_delayed_work(system_power_efficient_wq,
                                        &hub->init_work,
                                        msecs_to_jiffies(delay));

                        /* Suppress autosuspend until init is done */
                        usb_autopm_get_interface_no_resume(
                                        to_usb_interface(hub->intfdev));
                        return;         /* Continues at init2: below */
                } else if (type == HUB_RESET_RESUME) {
                        /* The internal host controller state for the hub device
                         * may be gone after a host power loss on system resume.
                         * Update the device's info so the HW knows it's a hub.
                         */
                        hcd = bus_to_hcd(hdev->bus);
                        if (hcd->driver->update_hub_device) {
                                ret = hcd->driver->update_hub_device(hcd, hdev,
                                                &hub->tt, GFP_NOIO);
                                if (ret < 0) {
                                        dev_err(hub->intfdev,
                                                "Host not accepting hub info update\n");
                                        dev_err(hub->intfdev,
                                                "LS/FS devices and hubs may not work under this hub\n");
                                }
                        }
                        hub_power_on(hub, true);
                } else {
                        hub_power_on(hub, true);
                }
  }
 init2:

        /*
         * Check each port and set hub->change_bits to let hub_wq know
         * which ports need attention.
         */
        for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
                struct usb_port *port_dev = hub->ports[port1 - 1];
                struct usb_device *udev = port_dev->child;
                u16 portstatus, portchange;

                portstatus = portchange = 0;
                status = hub_port_status(hub, port1, &portstatus, &portchange);
                if (status)
                        goto abort;

                if (udev || (portstatus & USB_PORT_STAT_CONNECTION))
                        dev_dbg(&port_dev->dev, "status %04x change %04x\n",
                                        portstatus, portchange);

                /*
                 * After anything other than HUB_RESUME (i.e., initialization
                 * or any sort of reset), every port should be disabled.
                 * Unconnected ports should likewise be disabled (paranoia),
                 * and so should ports for which we have no usb_device.
                 */
                if ((portstatus & USB_PORT_STAT_ENABLE) && (
                                type != HUB_RESUME ||
                                !(portstatus & USB_PORT_STAT_CONNECTION) ||
                                !udev ||
                                udev->state == USB_STATE_NOTATTACHED)) {
                        /*
                         * USB3 protocol ports will automatically transition
                         * to Enabled state when detect an USB3.0 device attach.
                         * Do not disable USB3 protocol ports, just pretend
                         * power was lost
                         */
                        portstatus &= ~USB_PORT_STAT_ENABLE;
                        if (!hub_is_superspeed(hdev))
                                usb_clear_port_feature(hdev, port1,
                                                   USB_PORT_FEAT_ENABLE);
                }

                /* Make sure a warm-reset request is handled by port_event */
                if (type == HUB_RESUME &&
                    hub_port_warm_reset_required(hub, port1, portstatus))
                        set_bit(port1, hub->event_bits);

                /*
                 * Add debounce if USB3 link is in polling/link training state.
                 * Link will automatically transition to Enabled state after
                 * link training completes.
                 */
                if (hub_is_superspeed(hdev) &&
                    ((portstatus & USB_PORT_STAT_LINK_STATE) ==
                                                USB_SS_PORT_LS_POLLING))
                        need_debounce_delay = true;

                /* Clear status-change flags; we'll debounce later */
                if (portchange & USB_PORT_STAT_C_CONNECTION) {
                        need_debounce_delay = true;
                        usb_clear_port_feature(hub->hdev, port1,
                                        USB_PORT_FEAT_C_CONNECTION);
                }
    if (portchange & USB_PORT_STAT_C_ENABLE) {
                        need_debounce_delay = true;
                        usb_clear_port_feature(hub->hdev, port1,
                                        USB_PORT_FEAT_C_ENABLE);
                }
                if (portchange & USB_PORT_STAT_C_RESET) {
                        need_debounce_delay = true;
                        usb_clear_port_feature(hub->hdev, port1,
                                        USB_PORT_FEAT_C_RESET);
                }
                if ((portchange & USB_PORT_STAT_C_BH_RESET) &&
                                hub_is_superspeed(hub->hdev)) {
                        need_debounce_delay = true;
                        usb_clear_port_feature(hub->hdev, port1,
                                        USB_PORT_FEAT_C_BH_PORT_RESET);
                }
                /* We can forget about a "removed" device when there's a
                 * physical disconnect or the connect status changes.
                 */
                if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
                                (portchange & USB_PORT_STAT_C_CONNECTION))
                        clear_bit(port1, hub->removed_bits);

                if (!udev || udev->state == USB_STATE_NOTATTACHED) {
                        /* Tell hub_wq to disconnect the device or
                         * check for a new connection or over current condition.
                         * Based on USB2.0 Spec Section 11.12.5,
                         * C_PORT_OVER_CURRENT could be set while
                         * PORT_OVER_CURRENT is not. So check for any of them.
                         */
                        if (udev || (portstatus & USB_PORT_STAT_CONNECTION) ||
                            (portstatus & USB_PORT_STAT_OVERCURRENT) ||
                            (portchange & USB_PORT_STAT_C_OVERCURRENT))
                                set_bit(port1, hub->change_bits);

                } else if (portstatus & USB_PORT_STAT_ENABLE) {
                        bool port_resumed = (portstatus &
                                        USB_PORT_STAT_LINK_STATE) ==
                                USB_SS_PORT_LS_U0;
                        /* The power session apparently survived the resume.
                         * If there was an overcurrent or suspend change
                         * (i.e., remote wakeup request), have hub_wq
                         * take care of it.  Look at the port link state
                         * for USB 3.0 hubs, since they don't have a suspend
                         * change bit, and they don't set the port link change
                         * bit on device-initiated resume.
                         */
                        if (portchange || (hub_is_superspeed(hub->hdev) &&
                                                port_resumed))
                                set_bit(port1, hub->change_bits);

                } else if (udev->persist_enabled) {
#ifdef CONFIG_PM
                        udev->reset_resume = 1;
#endif
                        /* Don't set the change_bits when the device
                         * was powered off.
                         */
                        if (test_bit(port1, hub->power_bits))
                                set_bit(port1, hub->change_bits);

                } else {
                        /* The power session is gone; tell hub_wq */
                        usb_set_device_state(udev, USB_STATE_NOTATTACHED);
                        set_bit(port1, hub->change_bits);
                }
        }

        /* If no port-status-change flags were set, we don't need any
         * debouncing.  If flags were set we can try to debounce the
         * ports all at once right now, instead of letting hub_wq do them
         * one at a time later on.
         *
         * If any port-status changes do occur during this delay, hub_wq
         * will see them later and handle them normally.
         */
        if (need_debounce_delay) {
                delay = HUB_DEBOUNCE_STABLE;

                /* Don't do a long sleep inside a workqueue routine */
                if (type == HUB_INIT2) {
                        INIT_DELAYED_WORK(&hub->init_work, hub_init_func3);
                        queue_delayed_work(system_power_efficient_wq,
                                        &hub->init_work,
                                        msecs_to_jiffies(delay));
                        device_unlock(&hdev->dev);
                        return;         /* Continues at init3: below */
                } else {
                        msleep(delay);
                }
        }
 init3:
        hub->quiescing = 0;

        status = usb_submit_urb(hub->urb, GFP_NOIO);
        if (status < 0)
                dev_err(hub->intfdev, "activate --> %d\n", status);
        if (hub->has_indicators && blinkenlights)
                queue_delayed_work(system_power_efficient_wq,
                                &hub->leds, LED_CYCLE_PERIOD);

        /* Scan all ports that need attention */
        kick_hub_wq(hub);
 abort:
        if (type == HUB_INIT2 || type == HUB_INIT3) {
                /* Allow autosuspend if it was suppressed */
 disconnected:
                usb_autopm_put_interface_async(to_usb_interface(hub->intfdev));
                device_unlock(&hdev->dev);
        }

        kref_put(&hub->kref, hub_release);
}
View Code

4.4 hub_irq

hub_irq主要任务就是对新插入的usb设备进行识别,并为其注册usb,匹配驱动。

定时器每隔一定时间执行一次,检测usb hub段口有没有发生改变,当usb设备插入时,D+或者D-会被拉高,将会触发hub_irq执行

/* completion function, fires on port status changes and various faults */
static void hub_irq(struct urb *urb)
{
        struct usb_hub *hub = urb->context;
        int status = urb->status;
        unsigned i;
        unsigned long bits;

        switch (status) {
        case -ENOENT:           /* synchronous unlink */
        case -ECONNRESET:       /* async unlink */
        case -ESHUTDOWN:        /* hardware going away */
                return;

        default:                /* presumably an error */
                /* Cause a hub reset after 10 consecutive errors */
                dev_dbg(hub->intfdev, "transfer --> %d\n", status);
                if ((++hub->nerrors < 10) || hub->error)
                        goto resubmit;
                hub->error = status;
                /* FALL THROUGH */

        /* let hub_wq handle things */
        case 0:                 /* we got data:  port status changed */
                bits = 0;
                for (i = 0; i < urb->actual_length; ++i)  // 读取中断传输的数据
                        bits |= ((unsigned long) ((*hub->buffer)[i]))
                                        << (i*8);
                hub->event_bits[0] = bits;
                break;
        }

        hub->nerrors = 0;

        /* Something happened, let hub_wq figure it out */
        kick_hub_wq(hub);

resubmit:
        hub_resubmit_irq_urb(hub);
}

首先获取urb的状态,如果urb->status为0,意味着对于一个urb请求,数据被成功发送;然后从hub->buffer缓冲区获取数据,并进行移位处理,存放到hub->event_bit[0],这实际上获取的是发生状态变化的端口号;

最后调用kick_hub_wq:

static void kick_hub_wq(struct usb_hub *hub)
{
        struct usb_interface *intf;

        if (hub->disconnected || work_pending(&hub->events))
                return;

        /*
         * Suppress autosuspend until the event is proceed.
         *
         * Be careful and make sure that the symmetric operation is
         * always called. We are here only when there is no pending
         * work for this hub. Therefore put the interface either when
         * the new work is called or when it is canceled.
         */
        intf = to_usb_interface(hub->intfdev);
        usb_autopm_get_interface_no_resume(intf);
        kref_get(&hub->kref);

        if (queue_work(hub_wq, &hub->events))
                return;

        /* the work has already been scheduled */
        usb_autopm_put_interface_async(intf);
        kref_put(&hub->kref, hub_release);
}

在kick_hub_wq中通过queue_work将工作hub->evenst添加到工作队列hub_wq中,从而使得工作hub_event()被内核工作线程所执行。

把work_struct加入工作队列,此函数一经成功调用,同一个对象将不能重复入队列,只有等工作执行完毕后(即从hub_event函数中返回后),才能再次入工作队列。

由于usb事件会不断地发生,这样可以有效的避免函数重入的问题,也就是说不会有多个处理函数同时在不同的CPU上调度执行。

4.4.1  hub_event

hub_event定义在drivers/usb/core/hub.c:

static void hub_event(struct work_struct *work)
{
        struct usb_device *hdev;
        struct usb_interface *intf;
        struct usb_hub *hub;
        struct device *hub_dev;
        u16 hubstatus;
        u16 hubchange;
        int i, ret;

        hub = container_of(work, struct usb_hub, events);  // 根hub usb_hub对象
        hdev = hub->hdev;                // 根hub   usb_device对象
        hub_dev = hub->intfdev;          // usb接口的设备基类 
        intf = to_usb_interface(hub_dev);

        dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
                        hdev->state, hdev->maxchild,
                        /* NOTE: expects max 15 ports... */
                        (u16) hub->change_bits[0],
                        (u16) hub->event_bits[0]);

        /* Lock the device, then check to see if we were
         * disconnected while waiting for the lock to succeed. */
        usb_lock_device(hdev);
        if (unlikely(hub->disconnected))
                goto out_hdev_lock;

        /* If the hub has died, clean up after it */
        if (hdev->state == USB_STATE_NOTATTACHED) {
                hub->error = -ENODEV;
                hub_quiesce(hub, HUB_DISCONNECT);
                goto out_hdev_lock;
        }

        /* Autoresume */
        ret = usb_autopm_get_interface(intf);
        if (ret) {
                dev_dbg(hub_dev, "Can't autoresume: %d\n", ret);
                goto out_hdev_lock;
        }

        /* If this is an inactive hub, do nothing */
        if (hub->quiescing)
                goto out_autopm;

        if (hub->error) {
                dev_dbg(hub_dev, "resetting for error %d\n", hub->error);

                ret = usb_reset_device(hdev);
                if (ret) {
                        dev_dbg(hub_dev, "error resetting hub: %d\n", ret);
                        goto out_autopm;
                }

                hub->nerrors = 0;
                hub->error = 0;
        }
        /* deal with port status changes */
        for (i = 1; i <= hdev->maxchild; i++) {   // 处理变化的端口
                struct usb_port *port_dev = hub->ports[i - 1];

                if (test_bit(i, hub->event_bits)  
                                || test_bit(i, hub->change_bits)
                                || test_bit(i, hub->wakeup_bits)) {
                        /*
                         * The get_noresume and barrier ensure that if
                         * the port was in the process of resuming, we
                         * flush that work and keep the port active for
                         * the duration of the port_event().  However,
                         * if the port is runtime pm suspended
                         * (powered-off), we leave it in that state, run
                         * an abbreviated port_event(), and move on.
                         */
                        pm_runtime_get_noresume(&port_dev->dev);
                        pm_runtime_barrier(&port_dev->dev);
                        usb_lock_port(port_dev);
                        port_event(hub, i);
                        usb_unlock_port(port_dev);
                        pm_runtime_put_sync(&port_dev->dev);
                }
        }

        /* deal with hub status changes */
        if (test_and_clear_bit(0, hub->event_bits) == 0)   // 第0位为0返回true
                ;       /* do nothing */
        else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0)
                dev_err(hub_dev, "get_hub_status failed\n");
        else {
                if (hubchange & HUB_CHANGE_LOCAL_POWER) {
                        dev_dbg(hub_dev, "power change\n");
                        clear_hub_feature(hdev, C_HUB_LOCAL_POWER);
                        if (hubstatus & HUB_STATUS_LOCAL_POWER)
                                /* FIXME: Is this always true? */
                                hub->limited_power = 1;
                        else
                                hub->limited_power = 0;
                }
                if (hubchange & HUB_CHANGE_OVERCURRENT) {
                        u16 status = 0;
                        u16 unused;

                        dev_dbg(hub_dev, "over-current change\n");
                        clear_hub_feature(hdev, C_HUB_OVER_CURRENT);
                        msleep(500);    /* Cool down */
                        hub_power_on(hub, true);
                        hub_hub_status(hub, &status, &unused);
                        if (status & HUB_STATUS_OVERCURRENT)
                                dev_err(hub_dev, "over-current condition\n");
                }
        }

out_autopm:
        /* Balance the usb_autopm_get_interface() above */
        usb_autopm_put_interface_no_suspend(intf);
out_hdev_lock:
        usb_unlock_device(hdev);

        /* Balance the stuff in kick_hub_wq() and allow autosuspend */
        usb_autopm_put_interface(intf);
        kref_put(&hub->kref, hub_release);
}
View Code

这里主要是对变化的端口进行一个处理,处理函数是port_event。

4.4.2 port_event
static void port_event(struct usb_hub *hub, int port1)
                __must_hold(&port_dev->status_lock)
{
        int connect_change;
        struct usb_port *port_dev = hub->ports[port1 - 1];
        struct usb_device *udev = port_dev->child;
        struct usb_device *hdev = hub->hdev;
        u16 portstatus, portchange;

        connect_change = test_bit(port1, hub->change_bits);
        clear_bit(port1, hub->event_bits);
        clear_bit(port1, hub->wakeup_bits);

        if (hub_port_status(hub, port1, &portstatus, &portchange) < 0)
                return;

        if (portchange & USB_PORT_STAT_C_CONNECTION) {
                usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION);
                connect_change = 1;
        }

        if (portchange & USB_PORT_STAT_C_ENABLE) {
                if (!connect_change)
                        dev_dbg(&port_dev->dev, "enable change, status %08x\n",
                                        portstatus);
                usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);

                /*
                 * EM interference sometimes causes badly shielded USB devices
                 * to be shutdown by the hub, this hack enables them again.
                 * Works at least with mouse driver.
                 */
                if (!(portstatus & USB_PORT_STAT_ENABLE)
                    && !connect_change && udev) {
                        dev_err(&port_dev->dev, "disabled by hub (EMI?), re-enabling...\n");
                        connect_change = 1;
                }
        }

        if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
                u16 status = 0, unused;
                port_dev->over_current_count++;
                port_over_current_notify(port_dev);

                dev_dbg(&port_dev->dev, "over-current change #%u\n",
                        port_dev->over_current_count);
                usb_clear_port_feature(hdev, port1,
                                USB_PORT_FEAT_C_OVER_CURRENT);
                msleep(100);    /* Cool down */
                hub_power_on(hub, true);
                hub_port_status(hub, port1, &status, &unused);
                if (status & USB_PORT_STAT_OVERCURRENT)
                        dev_err(&port_dev->dev, "over-current condition\n");
        }
 if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
                u16 status = 0, unused;
                port_dev->over_current_count++;
                port_over_current_notify(port_dev);

                dev_dbg(&port_dev->dev, "over-current change #%u\n",
                        port_dev->over_current_count);
                usb_clear_port_feature(hdev, port1,
                                USB_PORT_FEAT_C_OVER_CURRENT);
                msleep(100);    /* Cool down */
                hub_power_on(hub, true);
                hub_port_status(hub, port1, &status, &unused);
                if (status & USB_PORT_STAT_OVERCURRENT)
                        dev_err(&port_dev->dev, "over-current condition\n");
        }

        if (portchange & USB_PORT_STAT_C_RESET) {
                dev_dbg(&port_dev->dev, "reset change\n");
                usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_RESET);
        }
        if ((portchange & USB_PORT_STAT_C_BH_RESET)
            && hub_is_superspeed(hdev)) {
                dev_dbg(&port_dev->dev, "warm reset change\n");
                usb_clear_port_feature(hdev, port1,
                                USB_PORT_FEAT_C_BH_PORT_RESET);
        }
        if (portchange & USB_PORT_STAT_C_LINK_STATE) {
                dev_dbg(&port_dev->dev, "link state change\n");
                usb_clear_port_feature(hdev, port1,
                                USB_PORT_FEAT_C_PORT_LINK_STATE);
        }
        if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) {
                dev_warn(&port_dev->dev, "config error\n");
                usb_clear_port_feature(hdev, port1,
                                USB_PORT_FEAT_C_PORT_CONFIG_ERROR);
        }

        /* skip port actions that require the port to be powered on */
        if (!pm_runtime_active(&port_dev->dev))
                return;

        if (hub_handle_remote_wakeup(hub, port1, portstatus, portchange))
                connect_change = 1;

        /*
         * Warm reset a USB3 protocol port if it's in
         * SS.Inactive state.
         */
        if (hub_port_warm_reset_required(hub, port1, portstatus)) {
                dev_dbg(&port_dev->dev, "do warm reset\n");
                if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)
                                || udev->state == USB_STATE_NOTATTACHED) {
                        if (hub_port_reset(hub, port1, NULL,
                                        HUB_BH_RESET_TIME, true) < 0)
                                hub_port_disable(hub, port1, 1); 
                } else {
                        usb_unlock_port(port_dev);
                        usb_lock_device(udev);
                        usb_reset_device(udev);
                        usb_unlock_device(udev);
                        usb_lock_port(port_dev);
                        connect_change = 0;
                }
        }
 if (connect_change)
                hub_port_connect_change(hub, port1, portstatus, portchange);
}
View Code
4.4.3 hub_port_connect_change
/* Handle physical or logical connection change events.
 * This routine is called when:
 *      a port connection-change occurs;
 *      a port enable-change occurs (often caused by EMI);
 *      usb_reset_and_verify_device() encounters changed descriptors (as from
 *              a firmware download)
 * caller already locked the hub
 */
static void hub_port_connect_change(struct usb_hub *hub, int port1,
                                        u16 portstatus, u16 portchange)
                __must_hold(&port_dev->status_lock)
{
        struct usb_port *port_dev = hub->ports[port1 - 1];
        struct usb_device *udev = port_dev->child;
        int status = -ENODEV;

        dev_dbg(&port_dev->dev, "status %04x, change %04x, %s\n", portstatus,
                        portchange, portspeed(hub, portstatus));

        if (hub->has_indicators) {
                set_port_led(hub, port1, HUB_LED_AUTO);
                hub->indicator[port1-1] = INDICATOR_AUTO;
        }

#ifdef  CONFIG_USB_OTG
        /* during HNP, don't repeat the debounce */
        if (hub->hdev->bus->is_b_host)
                portchange &= ~(USB_PORT_STAT_C_CONNECTION |
                                USB_PORT_STAT_C_ENABLE);
#endif

        /* Try to resuscitate an existing device */
        if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
                        udev->state != USB_STATE_NOTATTACHED) {
                if (portstatus & USB_PORT_STAT_ENABLE) {
                        status = 0;             /* Nothing to do */
#ifdef CONFIG_PM
                } else if (udev->state == USB_STATE_SUSPENDED &&
                                udev->persist_enabled) {
                        /* For a suspended device, treat this as a
                         * remote wakeup event.
                         */
                        usb_unlock_port(port_dev);
                        status = usb_remote_wakeup(udev);
                        usb_lock_port(port_dev);
#endif
                } else {
                        /* Don't resuscitate */;
                }
        }
        clear_bit(port1, hub->change_bits);

        /* successfully revalidated the connection */
        if (status == 0)
                return;

        usb_unlock_port(port_dev);
        hub_port_connect(hub, port1, portstatus, portchange);
        usb_lock_port(port_dev);
}
View Code
4.4.4 hub_port_connect
static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
                u16 portchange)
{
        int status = -ENODEV;
        int i;
        unsigned unit_load;
        struct usb_device *hdev = hub->hdev;
        struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
        struct usb_port *port_dev = hub->ports[port1 - 1];
        struct usb_device *udev = port_dev->child;
        static int unreliable_port = -1;

        /* Disconnect any existing devices under this port */
        if (udev) {
                if (hcd->usb_phy && !hdev->parent)
                        usb_phy_notify_disconnect(hcd->usb_phy, udev->speed);
                usb_disconnect(&port_dev->child);
        }

        /* We can forget about a "removed" device when there's a physical
         * disconnect or the connect status changes.
         */
        if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
                        (portchange & USB_PORT_STAT_C_CONNECTION))
                clear_bit(port1, hub->removed_bits);

        if (portchange & (USB_PORT_STAT_C_CONNECTION |
                                USB_PORT_STAT_C_ENABLE)) {
                status = hub_port_debounce_be_stable(hub, port1);
                if (status < 0) {
                        if (status != -ENODEV &&
                                port1 != unreliable_port &&
                                printk_ratelimit())
                                dev_err(&port_dev->dev, "connect-debounce failed\n");
                        portstatus &= ~USB_PORT_STAT_CONNECTION;
                        unreliable_port = port1;
                } else {
                        portstatus = status;
                }
        }

        /* Return now if debouncing failed or nothing is connected or
         * the device was "removed".
         */
        if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
                        test_bit(port1, hub->removed_bits)) {

                /*
                 * maybe switch power back on (e.g. root hub was reset)
                 * but only if the port isn't owned by someone else.
                 */
                if (hub_is_port_power_switchable(hub)
                                && !port_is_power_on(hub, portstatus)
                                && !port_dev->port_owner)
                        set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);

                if (portstatus & USB_PORT_STAT_ENABLE)
                        goto done;
                return;
        }
        if (hub_is_superspeed(hub->hdev))
                unit_load = 150;
        else
                unit_load = 100;
 status = 0;
        for (i = 0; i < SET_CONFIG_TRIES; i++) {

                /* reallocate for each attempt, since references
                 * to the previous one can escape in various ways
                 */
                udev = usb_alloc_dev(hdev, hdev->bus, port1);     // 为新的usb设备动态申请usb_device结构
                if (!udev) {
                        dev_err(&port_dev->dev,
                                        "couldn't allocate usb_device\n");
                        goto done;
                }

                usb_set_device_state(udev, USB_STATE_POWERED);  // 设置usb设备的状态标志
                udev->bus_mA = hub->mA_per_port;
                udev->level = hdev->level + 1;
                udev->wusb = hub_is_wusb(hub);

                /* Devices connected to SuperSpeed hubs are USB 3.0 or later */
                if (hub_is_superspeed(hub->hdev))
                        udev->speed = USB_SPEED_SUPER;
                else
                        udev->speed = USB_SPEED_UNKNOWN;

                choose_devnum(udev);                          // 给新的usb设备分配一个地址编号   在bus->devnum_next~128之间循环查找下一个非0值
                if (udev->devnum <= 0) {
                        status = -ENOTCONN;     /* Don't retry */
                        goto loop;
                }

                /* reset (non-USB 3.0 devices) and get descriptor */
                usb_lock_port(port_dev);
                status = hub_port_init(hub, udev, port1, i);
                usb_unlock_port(port_dev);
                if (status < 0)
                        goto loop;

                if (udev->quirks & USB_QUIRK_DELAY_INIT)
                        msleep(2000);

                /* consecutive bus-powered hubs aren't reliable; they can
                 * violate the voltage drop budget.  if the new child has
                 * a "powered" LED, users should notice we didn't enable it
                 * (without reading syslog), even without per-port LEDs
                 * on the parent.
                 */
 if (udev->descriptor.bDeviceClass == USB_CLASS_HUB
                                && udev->bus_mA <= unit_load) {
                        u16     devstat;

                        status = usb_get_std_status(udev, USB_RECIP_DEVICE, 0,
                                        &devstat);
                        if (status) {
                                dev_dbg(&udev->dev, "get status %d ?\n", status);
                                goto loop_disable;
                        }
                        if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) {
                                dev_err(&udev->dev,
                                        "can't connect bus-powered hub "
                                        "to this port\n");
                                if (hub->has_indicators) {
                                        hub->indicator[port1-1] =
                                                INDICATOR_AMBER_BLINK;
                                        queue_delayed_work(
                                                system_power_efficient_wq,
                                                &hub->leds, 0);
                                }
                                status = -ENOTCONN;     /* Don't retry */
                                goto loop_disable;
                        }
                }

                /* check for devices running slower than they could */
                if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200
                                && udev->speed == USB_SPEED_FULL
                                && highspeed_hubs != 0)
                        check_highspeed(hub, udev, port1);

                /* Store the parent's children[] pointer.  At this point
                 * udev becomes globally accessible, although presumably
                 * no one will look at it until hdev is unlocked.
                 */
                status = 0;

                mutex_lock(&usb_port_peer_mutex);

                /* We mustn't add new devices if the parent hub has
                 * been disconnected; we would race with the
                 * recursively_mark_NOTATTACHED() routine.
                 */
                spin_lock_irq(&device_state_lock);
                if (hdev->state == USB_STATE_NOTATTACHED)
                        status = -ENOTCONN;
                else
                        port_dev->child = udev;
                spin_unlock_irq(&device_state_lock);
                mutex_unlock(&usb_port_peer_mutex);
  /* Run it through the hoops (find a driver, etc) */
                if (!status) {
                        status = usb_new_device(udev);  // 注册usb设备到内核,并匹配对应的usb设备驱动
                        if (status) {
                                mutex_lock(&usb_port_peer_mutex);
                                spin_lock_irq(&device_state_lock);
                                port_dev->child = NULL;
                                spin_unlock_irq(&device_state_lock);
                                mutex_unlock(&usb_port_peer_mutex);
                        } else {
                                if (hcd->usb_phy && !hdev->parent)
                                        usb_phy_notify_connect(hcd->usb_phy,
                                                        udev->speed);
                        }
                }

                if (status)
                        goto loop_disable;

                status = hub_power_remaining(hub);
                if (status)
                        dev_dbg(hub->intfdev, "%dmA power budget left\n", status);

                return;

loop_disable:
                hub_port_disable(hub, port1, 1);
loop:
                usb_ep0_reinit(udev);
                release_devnum(udev);
                hub_free_dev(udev);
                usb_put_dev(udev);
                if ((status == -ENOTCONN) || (status == -ENOTSUPP))
                        break;

                /* When halfway through our retry count, power-cycle the port */
                if (i == (SET_CONFIG_TRIES / 2) - 1) {
                        dev_info(&port_dev->dev, "attempt power cycle\n");
                        usb_hub_set_port_power(hdev, hub, port1, false);
                        msleep(2 * hub_power_on_good_delay(hub));
                        usb_hub_set_port_power(hdev, hub, port1, true);
                        msleep(hub_power_on_good_delay(hub));
                }
        }
        if (hub->hdev->parent ||
                        !hcd->driver->port_handed_over ||
                        !(hcd->driver->port_handed_over)(hcd, port1)) {
                if (status != -ENOTCONN && status != -ENODEV)
                        dev_err(&port_dev->dev,
                                        "unable to enumerate USB device\n");
        }

done:
        hub_port_disable(hub, port1, 1);
        if (hcd->driver->relinquish_port && !hub->hdev->parent) {
                if (status != -ENOTCONN && status != -ENODEV)
                        hcd->driver->relinquish_port(hcd, port1);
        }
}
View Code

最终调用usb_alloc_dev()和usb_new_device(udev),分配并初始化,注册设备,设备的初始化以及调用设备接口驱动的过程就和根hub一样了.

4.4.5 hub_port_init

我们再来看看hub_port_init函数是如何来实现连接USB设备的:

/* Reset device, (re)assign address, get device descriptor.
 * Device connection must be stable, no more debouncing needed.
 * Returns device in USB_STATE_ADDRESS, except on error.
 *
 * If this is called for an already-existing device (as part of
 * usb_reset_and_verify_device), the caller must own the device lock and
 * the port lock.  For a newly detected device that is not accessible
 * through any global pointers, it's not necessary to lock the device,
 * but it is still necessary to lock the port.
 */
static int
hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
                int retry_counter)
{
        struct usb_device       *hdev = hub->hdev;
        struct usb_hcd          *hcd = bus_to_hcd(hdev->bus);
        struct usb_port         *port_dev = hub->ports[port1 - 1];
        int                     retries, operations, retval, i;
        unsigned                delay = HUB_SHORT_RESET_TIME;
        enum usb_device_speed   oldspeed = udev->speed;
        const char              *speed;
        int                     devnum = udev->devnum;
        const char              *driver_name;

        /* root hub ports have a slightly longer reset period
         * (from USB 2.0 spec, section 7.1.7.5)
         */
        if (!hdev->parent) {
                delay = HUB_ROOT_RESET_TIME;
                if (port1 == hdev->bus->otg_port)
                        hdev->bus->b_hnp_enable = 0;
        }

        /* Some low speed devices have problems with the quick delay, so */
        /*  be a bit pessimistic with those devices. RHbug #23670 */
        if (oldspeed == USB_SPEED_LOW)
                delay = HUB_LONG_RESET_TIME;

        mutex_lock(hcd->address0_mutex);

        /* Reset the device; full speed may morph to high speed */
        /* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */
        retval = hub_port_reset(hub, port1, udev, delay, false);
        if (retval < 0)         /* error or disconnect */
                goto fail;
        /* success, speed is known */

        retval = -ENODEV;

        /* Don't allow speed changes at reset, except usb 3.0 to faster */
        if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed &&
            !(oldspeed == USB_SPEED_SUPER && udev->speed > oldspeed)) {
                dev_dbg(&udev->dev, "device reset changed speed!\n");
                goto fail;
        }
        oldspeed = udev->speed;
 /* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...
         * it's fixed size except for full speed devices.
         * For Wireless USB devices, ep0 max packet is always 512 (tho
         * reported as 0xff in the device descriptor). WUSB1.0[4.8.1].
         */
        switch (udev->speed) {
        case USB_SPEED_SUPER_PLUS:
        case USB_SPEED_SUPER:
        case USB_SPEED_WIRELESS:        /* fixed at 512 */
                udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);
                break;
        case USB_SPEED_HIGH:            /* fixed at 64 */
                udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
                break;
        case USB_SPEED_FULL:            /* 8, 16, 32, or 64 */
                /* to determine the ep0 maxpacket size, try to read
                 * the device descriptor to get bMaxPacketSize0 and
                 * then correct our initial guess.
                 */
                udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
                break;
        case USB_SPEED_LOW:             /* fixed at 8 */
                udev->ep0.desc.wMaxPacketSize = cpu_to_le16(8);
                break;
        default:
                goto fail;
        }

        if (udev->speed == USB_SPEED_WIRELESS)
                speed = "variable speed Wireless";
        else
                speed = usb_speed_string(udev->speed);

        /*
         * The controller driver may be NULL if the controller device
         * is the middle device between platform device and roothub.
         * This middle device may not need a device driver due to
         * all hardware control can be at platform device driver, this
         * platform device is usually a dual-role USB controller device.
         */
        if (udev->bus->controller->driver)
                driver_name = udev->bus->controller->driver->name;
        else
                driver_name = udev->bus->sysdev->driver->name;
 if (udev->speed < USB_SPEED_SUPER)
                dev_info(&udev->dev,
                                "%s %s USB device number %d using %s\n",
                                (udev->config) ? "reset" : "new", speed,
                                devnum, driver_name);

        /* Set up TT records, if needed  */
        if (hdev->tt) {
                udev->tt = hdev->tt;
                udev->ttport = hdev->ttport;
        } else if (udev->speed != USB_SPEED_HIGH
                        && hdev->speed == USB_SPEED_HIGH) {
                if (!hub->tt.hub) {
                        dev_err(&udev->dev, "parent hub has no TT\n");
                        retval = -EINVAL;
                        goto fail;
                }
                udev->tt = &hub->tt;
                udev->ttport = port1;
        }

        /* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way?
         * Because device hardware and firmware is sometimes buggy in
         * this area, and this is how Linux has done it for ages.
         * Change it cautiously.
         *
         * NOTE:  If use_new_scheme() is true we will start by issuing
         * a 64-byte GET_DESCRIPTOR request.  This is what Windows does,
         * so it may help with some non-standards-compliant devices.
         * Otherwise we start with SET_ADDRESS and then try to read the
         * first 8 bytes of the device descriptor to get the ep0 maxpacket
         * value.
         */
        for (retries = 0; retries < GET_DESCRIPTOR_TRIES; (++retries, msleep(100))) {
                bool did_new_scheme = false;

                if (use_new_scheme(udev, retry_counter, port_dev)) {
                        struct usb_device_descriptor *buf;
                        int r = 0;

                        did_new_scheme = true;
                        retval = hub_enable_device(udev);
                        if (retval < 0) {
                                dev_err(&udev->dev,
                                        "hub failed to enable device, error %d\n",
                                        retval);
                                goto fail;
                        }

#define GET_DESCRIPTOR_BUFSIZE  64
                        buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);
                        if (!buf) {
                                retval = -ENOMEM;
                                continue;
                        }

                        /* Retry on all errors; some devices are flakey.
                         * 255 is for WUSB devices, we actually need to use
                         * 512 (WUSB1.0[4.8.1]).
                         */
  for (operations = 0; operations < 3; ++operations) {
                                buf->bMaxPacketSize0 = 0;
                                r = usb_control_msg(udev, usb_rcvaddr0pipe(),
                                        USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
                                        USB_DT_DEVICE << 8, 0,
                                        buf, GET_DESCRIPTOR_BUFSIZE,
                                        initial_descriptor_timeout);
                                switch (buf->bMaxPacketSize0) {
                                case 8: case 16: case 32: case 64: case 255:
                                        if (buf->bDescriptorType ==
                                                        USB_DT_DEVICE) {
                                                r = 0;
                                                break;
                                        }
                                        /* FALL THROUGH */
                                default:
                                        if (r == 0)
                                                r = -EPROTO;
                                        break;
                                }
                                /*
                                 * Some devices time out if they are powered on
                                 * when already connected. They need a second
                                 * reset. But only on the first attempt,
                                 * lest we get into a time out/reset loop
                                 */
                                if (r == 0 || (r == -ETIMEDOUT &&
                                                retries == 0 &&
                                                udev->speed > USB_SPEED_FULL))
                                        break;
                        }
                        udev->descriptor.bMaxPacketSize0 =
                                        buf->bMaxPacketSize0;
                        kfree(buf);

                        retval = hub_port_reset(hub, port1, udev, delay, false);
                        if (retval < 0)         /* error or disconnect */
                                goto fail;
                        if (oldspeed != udev->speed) {
                                dev_dbg(&udev->dev,
                                        "device reset changed speed!\n");
                                retval = -ENODEV;
                                goto fail;
                        }
                        if (r) {
                                if (r != -ENODEV)
                                        dev_err(&udev->dev, "device descriptor read/64, error %d\n",
                                                        r);
                                retval = -EMSGSIZE;
                                continue;
                        }
#undef GET_DESCRIPTOR_BUFSIZE
                }

                /*
                 * If device is WUSB, we already assigned an
                 * unauthorized address in the Connect Ack sequence;
                 * authorization will assign the final address.
                 */
 if (udev->wusb == 0) {
                        for (operations = 0; operations < SET_ADDRESS_TRIES; ++operations) {
                                retval = hub_set_address(udev, devnum);
                                if (retval >= 0)
                                        break;
                                msleep(200);
                        }
                        if (retval < 0) {
                                if (retval != -ENODEV)
                                        dev_err(&udev->dev, "device not accepting address %d, error %d\n",
                                                        devnum, retval);
                                goto fail;
                        }
                        if (udev->speed >= USB_SPEED_SUPER) {
                                devnum = udev->devnum;
                                dev_info(&udev->dev,
                                                "%s SuperSpeed%s%s USB device number %d using %s\n",
                                                (udev->config) ? "reset" : "new",
                                         (udev->speed == USB_SPEED_SUPER_PLUS) ?
                                                        "Plus Gen 2" : " Gen 1",
                                         (udev->rx_lanes == 2 && udev->tx_lanes == 2) ?
                                                        "x2" : "",
                                         devnum, driver_name);
                        }

                        /* cope with hardware quirkiness:
                         *  - let SET_ADDRESS settle, some device hardware wants it
                         *  - read ep0 maxpacket even for high and low speed,
                         */
                        msleep(10);
                        /* use_new_scheme() checks the speed which may have
                         * changed since the initial look so we cache the result
                         * in did_new_scheme
                         */
                        if (did_new_scheme)
                                break;
                }

                retval = usb_get_device_descriptor(udev, 8);
                if (retval < 8) {
                        if (retval != -ENODEV)
                                dev_err(&udev->dev,
                                        "device descriptor read/8, error %d\n",
                                        retval);
                        if (retval >= 0)
                                retval = -EMSGSIZE;
                } else {
                        u32 delay;

                        retval = 0;

                        delay = udev->parent->hub_delay;
                        udev->hub_delay = min_t(u32, delay,
                                                USB_TP_TRANSMISSION_DELAY_MAX);
                        retval = usb_set_isoch_delay(udev);
                        if (retval) {
                                dev_dbg(&udev->dev,
                                        "Failed set isoch delay, error %d\n",
                                        retval);
                                retval = 0;
                        }
                        break;
                }
        }
 if (retval)
                goto fail;

        /*
         * Some superspeed devices have finished the link training process
         * and attached to a superspeed hub port, but the device descriptor
         * got from those devices show they aren't superspeed devices. Warm
         * reset the port attached by the devices can fix them.
         */
        if ((udev->speed >= USB_SPEED_SUPER) &&
                        (le16_to_cpu(udev->descriptor.bcdUSB) < 0x0300)) {
                dev_err(&udev->dev, "got a wrong device descriptor, "
                                "warm reset device\n");
                hub_port_reset(hub, port1, udev,
                                HUB_BH_RESET_TIME, true);
                retval = -EINVAL;
                goto fail;
        }

        if (udev->descriptor.bMaxPacketSize0 == 0xff ||
                        udev->speed >= USB_SPEED_SUPER)
                i = 512;
        else
                i = udev->descriptor.bMaxPacketSize0;
        if (usb_endpoint_maxp(&udev->ep0.desc) != i) {
                if (udev->speed == USB_SPEED_LOW ||
                                !(i == 8 || i == 16 || i == 32 || i == 64)) {
                        dev_err(&udev->dev, "Invalid ep0 maxpacket: %d\n", i);
                        retval = -EMSGSIZE;
                        goto fail;
                }
                if (udev->speed == USB_SPEED_FULL)
                        dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i);
                else
                        dev_warn(&udev->dev, "Using ep0 maxpacket: %d\n", i);
                udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i);
                usb_ep0_reinit(udev);
        }

        retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
        if (retval < (signed)sizeof(udev->descriptor)) {
                if (retval != -ENODEV)
                        dev_err(&udev->dev, "device descriptor read/all, error %d\n",
                                        retval);
                if (retval >= 0)
                        retval = -ENOMSG;
                goto fail;
        }

        usb_detect_quirks(udev);

        if (udev->wusb == 0 && le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201) {
                retval = usb_get_bos_descriptor(udev);
                if (!retval) {
                        udev->lpm_capable = usb_device_supports_lpm(udev);
                        usb_set_lpm_parameters(udev);
                }
        }

        retval = 0;
        /* notify HCD that we have a device connected and addressed */
        if (hcd->driver->update_device)
                hcd->driver->update_device(hcd, udev);
        hub_set_initial_usb2_lpm_policy(udev);
fail:
        if (retval) {
                hub_port_disable(hub, port1, 0);
                update_devnum(udev, devnum);    /* for disconnect processing */
        }
        mutex_unlock(hcd->address0_mutex);
        return retval;
}
View Code

这里调用hub_set_address()函数主要是用来告诉usb设备新的地址编号:

retval = hub_set_address(udev, devnum);
4.4.6 hub_set_address
static int hub_set_address(struct usb_device *udev, int devnum)
{
        int retval;
        struct usb_hcd *hcd = bus_to_hcd(udev->bus);

        /*
         * The host controller will choose the device address,
         * instead of the core having chosen it earlier
         */
        if (!hcd->driver->address_device && devnum <= 1)
                return -EINVAL;
        if (udev->state == USB_STATE_ADDRESS)
                return 0;
        if (udev->state != USB_STATE_DEFAULT)
                return -EINVAL;
        if (hcd->driver->address_device)
                retval = hcd->driver->address_device(hcd, udev);
        else
                retval = usb_control_msg(udev, usb_sndaddr0pipe(),    // 等待传输完成
                                USB_REQ_SET_ADDRESS, 0, devnum, 0,
                                NULL, 0, USB_CTRL_SET_TIMEOUT);
        if (retval == 0) {               // 设置新的地址,传输完成,返回0
                update_devnum(udev, devnum); 
                /* Device now using proper address. */
                usb_set_device_state(udev, USB_STATE_ADDRESS);   // 设置状态标志位
                usb_ep0_reinit(udev);
        }
        return retval;
}

usb_control_msg参数:

  • dev:指向目标设备的struct usb_device结构;
  • pipe:使用的管道为usb_sndaddr0pipe(),也就是默认的地址0加上控制端点号0;
  • request:USB_REQ_SET_ADDRESS,请求值设置为SET_ADDRESS,值为5;
  • requestType:设置为0;
  • value:devnum表示要设置目标设备的设备地址;
  • data:设置为NULL;
  • size:设置为0;
  • timeout:允许等待传输完成的时间设置为5秒,USB_CTRL_SET_TIMEOUT定义为5000。

具体参考usb 2.0 spec 9.4.6章节:

bmRequestType bRequest wValue wIndex wLength Data
00000000B SET_ADDRESS Device Address Zero Zero None

 

参考文章

[1USB主机控制器驱动——OHCI分析

[2]gpio_request 原形代码

[3]【linux驱动】USB子系统分析

[4]Linux工作队列workqueue源码分析

[5]usb hub驱动

[6]linux usb root_hub中断传输

[7]linux usb root_hub中断传输(hub_irq触发原理部分很详细)
posted @ 2022-05-16 22:30  大奥特曼打小怪兽  阅读(1004)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步