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,在该文件里构建了fb_info结构体。我们可以在该文件定位到驱动模块的入口和出口:

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 =
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Graceful_scenery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值