Linux内核USB2.0控制器初始化代码分析
内核版本:4.1.15版本
1、设备树
本文分析的imx6ull的USB代码。
usbotg1: usb@02184000 {
compatible = "fsl,imx6ul-usb", "fsl,imx27-usb";
reg = <0x02184000 0x200>;
interrupts = <GIC_SPI 43 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_USBOH3>;
fsl,usbphy = <&usbphy1>;
fsl,usbmisc = <&usbmisc 0>;
fsl,anatop = <&anatop>;
ahb-burst-config = <0x0>;
tx-burst-size-dword = <0x10>;
rx-burst-size-dword = <0x10>;
status = "disabled";
};
2、USB初始化分析
IMX6ULL的USB初始化驱动是一个platform_driver,设备树匹配的属性为“fsl,imx6ul-usb”,入口函数为ci_hdrc_imx_probe。
static const struct of_device_id ci_hdrc_imx_dt_ids[] = {
{ .compatible = "fsl,imx28-usb", .data = &imx28_usb_data},
{ .compatible = "fsl,imx27-usb", .data = &imx27_usb_data},
{ .compatible = "fsl,imx6q-usb", .data = &imx6q_usb_data},
{ .compatible = "fsl,imx6sl-usb", .data = &imx6sl_usb_data},
{ .compatible = "fsl,imx6sx-usb", .data = &imx6sx_usb_data},
{ .compatible = "fsl,imx6ul-usb", .data = &imx6ul_usb_data},
{ .compatible = "fsl,imx7d-usb", .data = &imx7d_usb_data},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids);
static struct platform_driver ci_hdrc_imx_driver = {
.probe = ci_hdrc_imx_probe,
.remove = ci_hdrc_imx_remove,
.driver = {
.name = "imx_usb",
.of_match_table = ci_hdrc_imx_dt_ids,
.pm = &ci_hdrc_imx_pm_ops,
},
};
module_platform_driver(ci_hdrc_imx_driver);
ci_hdrc_imx_probe函数的主要工作如下:
struct ci_hdrc_imx_data *data;
struct ci_hdrc_platform_data pdata = {
.name = dev_name(&pdev->dev),
.capoffset = DEF_CAPOFFSET,
.notify_event = ci_hdrc_imx_notify_event,
};
data->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "fsl,usbphy", 0);
pdata.usb_phy = data->phy;
data->ci_pdev = ci_hdrc_add_device(&pdev->dev,
pdev->resource, pdev->num_resources,
&pdata);
第8~9行:获取phy,并将值赋值给pdata。
第11行:ci_hdrc_add_device()创建一个新的设备,这个函数很重要,是USB的核心。
struct platform_device *ci_hdrc_add_device(struct device *dev,
struct resource *res, int nres,
struct ci_hdrc_platform_data *platdata)
{
struct platform_device *pdev;
int id, ret;
ret = ci_get_platdata(dev, platdata);
id = ida_simple_get(&ci_ida, 0, 0, GFP_KERNEL);
pdev = platform_device_alloc("ci_hdrc", id);
pdev->dev.parent = dev;
pdev->dev.dma_mask = dev->dma_mask;
pdev->dev.dma_parms = dev->dma_parms;
dma_set_coherent_mask(&pdev->dev, dev->coherent_dma_mask);
ret = platform_device_add_resources(pdev, res, nres);
ret = platform_device_add_data(pdev, platdata, sizeof(*platdata));
ret = platform_device_add(pdev);
}
第8行:对platdata进行配置。
第10行:获取一个新ID。
第12行:注册一个名为ci_hdrc的设备。
第17行:设置DMA。
3、USB核心初始化分析
3.1数据结构分析
struct ci_hdrc是USB OTG控制器的核心数据结构,所有工作都围绕此数据结构展开。该数据结构的意义如下面的代码所示,省略了一些不太重要的内容。
[drivers\usb\chipidea\ci.h]
struct ci_hdrc {
struct device *dev;
spinlock_t lock;
struct hw_bank hw_bank;
int irq;
struct ci_role_driver *roles[CI_ROLE_END];
enum ci_role role;
bool is_otg;
struct usb_otg otg;
struct otg_fsm fsm;
struct hrtimer otg_fsm_hrtimer;
ktime_t hr_timeouts[NUM_OTG_FSM_TIMERS];
unsigned enabled_otg_timer_bits;
enum otg_fsm_timer next_otg_timer;
struct timer_list hnp_polling_timer;
struct work_struct hnp_polling_work;
struct work_struct work;
struct workqueue_struct *wq;
struct dma_pool *qh_pool;
struct dma_pool *td_pool;
struct usb_gadget gadget;
struct usb_gadget_driver *driver;
unsigned hw_ep_max;
struct ci_hw_ep ci_hw_ep[ENDPT_MAX];
u32 ep0_dir;
struct ci_hw_ep *ep0out, *ep0in;
struct usb_request *status;
bool setaddr;
u8 address;
u8 remote_wakeup;
u8 suspended;
u8 test_mode;
struct ci_hdrc_platform_data *platdata;
int vbus_active;
struct phy *phy;
/* old usb_phy interface */
struct usb_phy *usb_phy;
struct usb_hcd *hcd;
struct dentry *debugfs;
bool id_event;
bool b_sess_valid_event;
bool vbus_glitch_check_event;
bool imx28_write_fix;
bool supports_runtime_pm;
bool in_lpm;
bool wakeup_int;
enum ci_revision rev;
/* register save area for suspend&resume */
u32 pm_command;
u32 pm_status;
u32 pm_intr_enable;
u32 pm_frame_index;
u32 pm_segment;
u32 pm_frame_list;
u32 pm_async_next;
u32 pm_configured_flag;
u32 pm_portsc;
u32 pm_usbmode;
struct work_struct power_lost_work;
};
3.2驱动分析
可以根据上一节设备名ci_hdrc找到注册驱动。
static struct platform_driver ci_hdrc_driver = {
.probe = ci_hdrc_probe,
.remove = ci_hdrc_remove,
.driver = {
.name = "ci_hdrc",
.pm = &ci_pm_ops,
},
};
static int __init ci_hdrc_platform_register(void)
{
ci_hdrc_host_driver_init();
return platform_driver_register(&ci_hdrc_driver);
}
module_init(ci_hdrc_platform_register);
static void __exit ci_hdrc_platform_unregister(void)
{
platform_driver_unregister(&ci_hdrc_driver);
}
module_exit(ci_hdrc_platform_unregister);
现在我们来看proc函数:
struct device *dev = &pdev->dev;
struct ci_hdrc *ci;
struct resource *res;
void __iomem *base;
int ret;
enum usb_dr_mode dr_mode;
if (!dev_get_platdata(dev))
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(dev, res);
ci = devm_kzalloc(dev, sizeof(*ci), GFP_KERNEL);
ci->dev = dev;
ci->platdata = dev_get_platdata(dev);
ret = hw_device_init(ci, base);
if (ci->platdata->phy) {
ci->phy = ci->platdata->phy;
} else if (ci->platdata->usb_phy) {
ci->usb_phy = ci->platdata->usb_phy;
} else {
ci->phy = devm_phy_get(dev->parent, "usb-phy");
ci->usb_phy = devm_usb_get_phy(dev->parent, USB_PHY_TYPE_USB2);
/* if both generic PHY and USB PHY layers aren't enabled */
if (PTR_ERR(ci->phy) == -ENOSYS &&
PTR_ERR(ci->usb_phy) == -ENXIO)
if (IS_ERR(ci->phy) && IS_ERR(ci->usb_phy))
if (IS_ERR(ci->phy))
ci->phy = NULL;
else if (IS_ERR(ci->usb_phy))
ci->usb_phy = NULL;
}
ret = ci_usb_phy_init(ci);
ci->irq = platform_get_irq(pdev, 0);
ci_get_otg_capable(ci);
dr_mode = ci->platdata->dr_mode;
/* initialize role(s) before the interrupt is requested */
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
ret = ci_hdrc_host_init(ci);
}
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
ret = ci_hdrc_gadget_init(ci);
}
if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET])
if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) {
ret = ci_hdrc_otg_init(ci);
}
platform_set_drvdata(pdev, ci);
ret = devm_request_irq(dev, ci->irq, ci_irq, IRQF_SHARED,
ci->platdata->name, ci);
if (ci->supports_runtime_pm) {
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
pm_runtime_mark_last_busy(ci->dev);
pm_runtime_use_autosuspend(&pdev->dev);
}
第2行:ci就是重要的结构体,这个函数的主要作用就是对其进行赋值。
第8行:安全性检查,看是否含有私有数据。
第10~11行:获取地址空间,并对其重映射。
第13行:为ci分配地址空间。
第16行:将dev传来的私有数据进行赋值给ci。
第18行:硬件初始化。
第20~38行:获取PHY信息。
第40行:初始化PHY。
第42行:获取中断号。
第44行:判断是否是OTG。
第46~58行:根据不同的模式进行初始化。49行ci_hdrc_host_init()是主机进行初始化,53行ci_hdrc_gadget_init()是gadget进行初始化,这两段很重要,以后会专门进行讲解。57行是对OTG模式进行初始化,会创建一个内核线程。
第60行:设置私有数据。
第61行:请求ci_irq中断。
第64~70行:电源管理函数。
4、设备控制器驱动框架分析
USB控制器可以呈现两种不同的状态。USB作为HOST时,称为USB主机控制器,使用的是USB主机控制器驱动。USB作为Device时,称为USB设备控制器驱动,使用的UDC驱动。本次我们分析的是USB作为Device时的驱动框架。
设备控制区驱动可以分为5层。最上层的是Gadget Function驱动,所代表的是具体的设备,如U盘。USB摄像头等。接下来是Gadget Function API层,该层为一个抽象层,向上和向下提供统一的API,提高了驱动的兼容性。Composite层是一个可选的中间层,可通过一种配置或多种配置支持多种设备。目前最流行的是使用基于Composite和configfs实现的USB gadget configfs,可在用户空间灵活的配置USB设备。UDC驱动直接访问硬件,控制USB设备与USB主机之间的通信。USB设备控制器通过USB线缆连接USB主机控制器,负责USB数据的发送和接收。
编写gadget框架的关键在于理解udc、gadget和composite之间的关系和架构层次。在实际应用中gadget是不需要我们编写的,而我们需要编写的是composite层,以及对udc层进行修改。
4.1 底层硬件操作UDC驱动建立
int ci_hdrc_gadget_init(struct ci_hdrc *ci)
{
struct ci_role_driver *rdrv;
rdrv = devm_kzalloc(ci->dev, sizeof(struct ci_role_driver), GFP_KERNEL);
rdrv->start = udc_id_switch_for_device;
rdrv->stop = udc_id_switch_for_host;
rdrv->irq = udc_irq;
rdrv->suspend = udc_suspend;
rdrv->resume = udc_resume;
rdrv->name = "gadget";
ci->roles[CI_ROLE_GADGET] = rdrv;
return udc_start(ci);
}
第5行:为rdrv分配地址。
第7~12行:设置各种函数。
第14行:udc进行设置,这个是重中之重。
static int udc_start(struct ci_hdrc *ci)
{
struct device *dev = ci->dev;
struct usb_otg_caps *otg_caps = &ci->platdata->ci_otg_caps;
int retval = 0;
spin_lock_init(&ci->lock);
ci->gadget.ops = &usb_gadget_ops;
ci->gadget.speed = USB_SPEED_UNKNOWN;
ci->gadget.max_speed = USB_SPEED_HIGH;
ci->gadget.name = ci->platdata->name;
ci->gadget.otg_caps = otg_caps;
INIT_LIST_HEAD(&ci->gadget.ep_list);
/* alloc resources */
ci->qh_pool = dma_pool_create("ci_hw_qh", dev,
sizeof(struct ci_hw_qh),
64, CI_HDRC_PAGE_SIZE);
ci->td_pool = dma_pool_create("ci_hw_td", dev,
sizeof(struct ci_hw_td),
64, CI_HDRC_PAGE_SIZE);
retval = init_eps(ci);
ci->gadget.ep0 = &ci->ep0in->ep;
retval = usb_add_gadget_udc(dev, &ci->gadget);
}
第9~13行:设置gadget。
第18~24行:创建DMA空间。
第26行:设置gadget的endpoint。
第30行:注册udc。
int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget)
{
return usb_add_gadget_udc_release(parent, gadget, NULL);
}
int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget,
void (*release)(struct device *dev))
{
mutex_lock(&udc_lock);
list_add_tail(&udc->list, &udc_list);
/* pick up one of pending gadget drivers */
list_for_each_entry(driver, &gadget_driver_pending_list, pending) {
if (!driver->udc_name || strcmp(driver->udc_name,
dev_name(&udc->dev)) == 0) {
ret = udc_bind_to_driver(udc, driver);
if (ret)
goto err4;
list_del(&driver->pending);
break;
}
}
mutex_unlock(&udc_lock);
}
这段代码的主要含义是把 Device 加入到 udc_list 链表,然后尝试和 gadget_driver_pending_list 链表中的 Driver 进行 match()。
4.2 UDC开始
如果match成功,就会调用usb_gadget_udc_start(),这个函数实际就是ci_udc_start(),以下就是函数内容:
static int ci_udc_start(struct usb_gadget *gadget,
struct usb_gadget_driver *driver)
{
struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget);
ci->ep0out->ep.desc = &ctrl_endpt_out_desc;
retval = usb_ep_enable(&ci->ep0out->ep);
ci->ep0in->ep.desc = &ctrl_endpt_in_desc;
retval = usb_ep_enable(&ci->ep0in->ep);
ci->driver = driver;
/* Start otg fsm for B-device */
if (ci_otg_is_fsm_mode(ci)) {
if (ci->fsm.id)
ci_hdrc_otg_fsm_start(ci);
}
if (ci->vbus_active)
ci_hdrc_gadget_connect(&ci->gadget, 1);
}
第6行:设置端点长度,方向,类型等。
第7行:usb_ep_enable()是在init_eps()初始化端点中完成的api赋值,函数为ep_enable()。
第9行:将composite层提供的usb_gadget_driver结构体地址保存到ci_hdrc结构体中。
4.3 上层操作
Host要分配地址,把地址分配给设备,这部分有usb_gadget实现。
Host要读取各种描述符,这些都由上层程序提供。
上层的描述符通过中间层将底层的usb_gadget传回给Host,中间层就是usb_gadget_driver。实现一些通用的USB访问方法,比如Host访问描述符时,由usb_gadget_driver提供。
通过提供描述符还可以分为两层:
usb_composite_driver:提供设备、配置描述符。
function driver:提供接口、端点描述符。
目前存在两种风格的 Gadget Driver,其中包括:
- Legacy。这是早期风格的 Gadget Driver,只能通过静态编译的方式指定使用哪些 Function。
- Configfs。这是目前流行的 Gadget Driver,可以通过 configfs 文件系统,不用重新编译内核,动态的配置需要使用的 Function。
4.3.1 configfs
举例创建一个ACM Funtion:
//1 挂载configfs文件系统
mount -t configfs none /sys/kernerl/config
cd /sys/kernerl/config/usb_gadget
//2 创建g1目录,实例一个gadget模板
mkdir g1
cd g1
//3 定义产品的VID、PID
echo "0x1d6b" > idVendor
echo "0x0104" > idProduct
//4 实例化英语语言ID
mkdir strings/0x409
ls strings/0x409/
//5 将开发商、产品和序列号字符串写入内核
echo "0123456789" > strings/0x409/serialnumber
echo "AAAA Inc." > strings/0x409/manufacturer
echo "Bar Gadget" > strings/0x409/product
//6 创建功能(Fuction)实例,注意,一个功能如果有多个实例的话,扩展名必须用数字编号
mkdir functions/acm.GS0
//7 创建配置实例
mkdir configs/c.1
ls configs/c.1
//8 定义配置描述符使用的字符串
mkdir configs/c.1/strings/0x409
ls configs/c.1/strings/0x409/
echo "ACM" > configs/c.1/strings/0x409/configuration
//9 捆绑功能 `Function` 实例到 `Configuration` 配置c.1
ln -s functions/acm.GS0 configs/c.1
//10 查找本机可获得的UDC实例
# ls /sys/class/udc/
10200000.usb
//11 将gadget驱动注册到UDC上,插上USB线到电脑上,电脑就会枚举USB设备
echo "10200000.usb" > UDC
4.3.2 Legacy实例分析
以zero实例进行分析。
入口函数:
static struct usb_composite_driver zero_driver = {
.name = "zero",
.dev = &device_desc,
.strings = dev_strings,
.max_speed = USB_SPEED_SUPER,
.bind = zero_bind,
.unbind = zero_unbind,
.suspend = zero_suspend,
.resume = zero_resume,
};
module_usb_composite_driver(zero_driver);
进入module_usb_composite_driver函数,找到probe函数:
#define module_usb_composite_driver(__usb_composite_driver) \
module_driver(__usb_composite_driver, usb_composite_probe, \
usb_composite_unregister)
下图为主要流程:
在4.1(底层硬件操作UDC驱动建立)中也出现了udc_bind_to_driver,在gadget device建立时,会把device加入到udc_list 链表,然后尝试和 udc_bind_to_driver链表中的 Driver 进行 match()。
而在此处是gadget driver创建时,首先尝试和 udc_list 链表中的 Device 进行 match(),match() 不成功则把 Driver 加入到 udc_bind_to_driver链表中。
USB Function驱动和UDC绑定成功后,通过调用udc_bind_to_driver()中的usb_gadget_udc_start(udc)接口开启USB设备控制器,接收主机发送的请求。
4.4 从获取描述符的角度理解Gadget框架
核心函数为:
ci_hdrc_probe
devm_request_irq(dev, ci->irq, ci_irq, IRQF_SHARED,
ci->platdata->name, ci);
中断函数的处理流程:
ci_irq
/* Handle device/host interrupt */
if (ci->role != CI_ROLE_END)
ret = ci_role(ci)->irq(ci); // udc_irq,这个irq在ci_hdrc_gadget_init时被初始化
rdrv->irq = udc_irq;
isr_tr_complete_handler(ci);
isr_setup_packet_handler(ci);
函数isr_setup_packet_handler是处理endpoint 0接收到的控制传输的关键。
参考资料
USB总线
IMX6ULL内核源码