在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主机控制器结构图
![](https://i-blog.csdnimg.cn/blog_migrate/a9386680fa9af58ad49f71834702ced6.png)
1.3 Mini2440 usb接线图
我使用的开发板为Mini2440,我们来看一下开发板的接线图:
![](https://i-blog.csdnimg.cn/blog_migrate/ab6444845b1489781a3358eeca5de3e4.png)
Mini2440开发板只有一个usb主机接口和一个USB设备接口,均为usb全速接口。其中usb设备接口是用来和PC接连接,usb主机接口是用来连接usb外设的。这里的usb主机/设备是针对于开发板来说的。
其中usb设备的D+是由GPC5来控制的,如果GPC5 输出高电平,则D+ 上相当于通过上拉电阻接到+5V ,则设备启用;如果GPC5 输出低电平,则D+ 上相当于通过下拉电阻接到GND ,则设备禁用。
1.4 USB总线物理拓扑结构
USB总线可能呈现出树状的拓扑接口吗,USB标准上说USB总线物理拓扑是一种分层星型结构,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/77eaf0f6a56029fdf038f2fd13062985.png)
从树结构我们可以看到:
- 树的根节点是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等:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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))));
};
部分参数含义:
- 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::
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
/*
* 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
};
其中部分参数含义如下:
-
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主机控制器操作的函数指针:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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);
};
其中部分参数含义:
- 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 =