7.12.4. 设计说明

image0

整个 USB 系统的软件栈如上图所示,本文仅描述其中的 HCD (Host Controller Driver) 和 DCD (Device Controller Driver)。

7.12.4.1. USB Host Controller Driver

下面以 EHCI 为例,说明 HCD 软件设计思想。

7.12.4.1.1. 源码说明

相关模块

源码路径

EHCI

source\linux-5.10\drivers\usb\host\ehci-aic.c
source\linux-5.10\drivers\usb\host\ehci-hcd.c
source\linux-5.10\drivers\usb\host\ehci-mem.c
source\linux-5.10\drivers\usb\host\ehci-q.c
source\linux-5.10\drivers\usb\host\ehci-timer.c
source\linux-5.10\drivers\usb\host\ehci-hub.c

7.12.4.1.2. 模块架构

image1

从 HCD (Host Controller Driver) 的框架图中可以看到,HCD 主要提供了两大功能:

  1. 普通 URB 数据收发功能。

将 USB Class Driver 下发的 URB,按照硬件控制器要求的格式,按分类发送到硬件 List 当中。

  1. RootHub URB 的处理功能。

对于 RootHub Driver 下发的 ep0 控制命令 URB,系统不会发送到硬件控制器之上,而是转发给 HCD 使用软件来模拟执行。

对于 RootHub Driver 下发的端口状态查询 URB,通过响应中断进行上报。

7.12.4.1.3. 关键流程

7.12.4.1.3.1. 初始化流程

HCD 驱动的入口是 platform 驱动,初始化流程先获取 irq、reg、clk、reset 等资源并进行初始化,最后调用 usb_add_hcd() 向系统中注册。

大致的流程如下:

|-->ehci_platform_init()
    |-->ehci_init_driver()
    |-->platform_driver_register()
        |-->aic_ehci_platform_probe()
            |-->hcd = usb_create_hcd()
            |-->irq = platform_get_irq(dev, 0);
            |-->priv->clks[i] = of_clk_get(dev->dev.of_node, i);
            |-->priv->rst[i] = devm_reset_control_get_shared_by_index(&dev->dev, i);
            |-->hcd->regs = devm_ioremap_resource(&dev->dev, res_mem);
            |-->aic_ehci_platform_power_on()
                |-->reset_control_deassert(priv->rst[i]);
                |-->clk_prepare_enable(priv->clks[i]);
            |-->usb_add_hcd(hcd, irq, IRQF_SHARED);

7.12.4.1.3.2. 普通 URB 处理流程

image2

如上图所示,一个普通 urb 的处理分为两步:

  • urb enqueue。首先调用 hcd 的 .urb_enqueue() 函数,将需要传输的数据插入到硬件控制器的链表当中。

  • urb complete。在链表中的一帧数据传输完成后硬件会产生 complete 中断,在中断服务程序中对相应 urb 发送 complete 信号,让 usb_start_wait_urb() 的流程继续执行。

7.12.4.1.3.3. Roothub URB 处理流程

image3

如上图所示,roothub urb 的处理分为两种类型:

  • ep0 control urb。对于 roothub control urb,HCD 需要使用软件来模拟,实际上 urb 没有发送到硬件控制器中,因为是软件模拟所以无需等待 complete 可以立即释放。

  • 获取端口状态 urb。这类 urb 会阻塞等待端口状态改变,一旦端口状态改变会触发硬件中断,在中断处理中唤醒对应 urb 的 complete 信号,让 usb_start_wait_urb() 的流程继续执行。

7.12.4.1.4. 数据结构

7.12.4.1.4.1. ehci_hc_driver

HCD 核心的数据结构为 hc_driver,EHCI 实现了以下的核心函数:

static const struct hc_driver ehci_hc_driver = {
    .description =          hcd_name,
    .product_desc =         "EHCI Host Controller",
    .hcd_priv_size =        sizeof(struct ehci_hcd),

    /*
    * generic hardware linkage
    */
    .irq =                  ehci_irq,
    .flags =                HCD_MEMORY | HCD_DMA | HCD_USB2 | HCD_BH,

    /*
    * basic lifecycle operations
    */
    .reset =                ehci_setup,
    .start =                ehci_run,
    .stop =                 ehci_stop,
    .shutdown =             ehci_shutdown,

    /*
    * managing i/o requests and associated device resources
    */
    .urb_enqueue =          ehci_urb_enqueue,
    .urb_dequeue =          ehci_urb_dequeue,
    .endpoint_disable =     ehci_endpoint_disable,
    .endpoint_reset =       ehci_endpoint_reset,
    .clear_tt_buffer_complete =     ehci_clear_tt_buffer_complete,

    /*
    * scheduling support
    */
    .get_frame_number =     ehci_get_frame,

    /*
    * root hub support
    */
    .hub_status_data =      ehci_hub_status_data,
    .hub_control =          ehci_hub_control,
    .bus_suspend =          ehci_bus_suspend,
    .bus_resume =           ehci_bus_resume,
    .relinquish_port =      ehci_relinquish_port,
    .port_handed_over =     ehci_port_handed_over,
    .get_resuming_ports =   ehci_get_resuming_ports,

    /*
    * device support
    */
    .free_dev =             ehci_remove_device,
};

7.12.4.1.5. 接口设计

7.12.4.1.5.1. ehci_urb_enqueue

函数原型

int ehci_urb_enqueue (struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags)

功能说明

接收上层传入的 urb,并将其压入 EHCI 的硬件队列。

参数定义

hcd:当前 hcd 控制结构
urb:当前 urb 控制结构
mem_flags:分配内存时使用的标志

返回值

0,成功; < 0,失败

注意事项

7.12.4.1.5.2. ehci_hub_control

函数原型

int ehci_hub_control (struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength)

功能说明

处理 roothub 相关的 control 命令。

参数定义

hcd:当前 hcd 控制结构
typeReq:setup token 中的对应字段
wValue:setup token 中的对应字段
wIndex:setup token 中的对应字段
buf:setup data 需要的数据
wLength:setup token 中的对应字段

返回值

0,成功; < 0,失败

注意事项

7.12.4.1.5.3. ehci_hub_status_data

函数原型

int ehci_hub_status_data (struct usb_hcd *hcd, char *buf)

功能说明

查询 hub 端口状态。

参数定义

hcd:当前 hcd 控制结构
buf:返回获取的 hub 端口状态

返回值

>0,成功获取端口状态的长度; = 0,失败

注意事项

7.12.4.2. USB Device Controller Driver

Linux 利用 Device Controller Driver 把整个单板模拟成一个 USB Device 设备。

7.12.4.2.1. 源码说明

相关模块

源码路径

AIC UDC

source\linux-5.10\drivers\usb\gadget\udc\aic_udc.c
source\linux-5.10\drivers\usb\gadget\udc\aic_udc.h

7.12.4.2.2. 模块架构

image4

从上述 DCD (Device Controller Driver) 的框架图中可以看到,DCD 主要提供了两大功能:

  1. 普通 ep 的 usb request 处理。

DCD 提供了一个 endpoint 资源池,Gadget Function Driver 可以在这个资源池中分配需要的 endpoint,这部分的 endpoint 就称为 普通 ep。

  1. ep0 的 usb request 处理。

因为 DCD 对外呈现为一个 USB Device,USB Device 的 ep0 是管理通道是需要特殊处理的。对 ep0 传达过来的 control 数据需要在 DCD 层开始解析。

7.12.4.2.3. 关键流程

7.12.4.2.3.1. 初始化流程

DCD 驱动的入口是 platform 驱动,初始化流程先获取 irq、reg、clk、reset 等资源并进行初始化,最后调用 usb_add_gadget_udc() 向系统中注册。

大致的流程如下:

|-->aic_udc_probe()
    |-->gg->regs = devm_ioremap_resource(&dev->dev, res);
    |-->gg->reset = devm_reset_control_get_optional(gg->dev, "aicudc");
    |-->gg->reset_ecc = devm_reset_control_get_optional_shared(gg->dev,"aicudc-ecc");
    |-->gg->clks[i] = of_clk_get(gg->dev->of_node, i);
    |-->aic_gadget_init(gg);
        |-->aic_low_hw_enable()
            |-->clk_prepare_enable(gg->clks[i]);
            |-->reset_control_deassert(gg->reset);
            |-->reset_control_deassert(gg->reset_ecc);
    |-->res = platform_get_resource(dev, IORESOURCE_IRQ, 0);
    |-->gg->irq = res->start;
    |-->usb_add_gadget_udc(gg->dev, &gg->gadget);

7.12.4.2.3.2. ep 分配流程

image5

如上图所示,ep 资源的操作分为两部分:

  • ep 资源池初始化。在 udc 驱动初始化的时候同时初始化了 ep 资源池,这样就决定了当前有多少个 ep 资源可用。

  • ep 资源的分配。gadget composite device 可以配置多个 interface 即 gadget function driver,当 function driver 启用时,会从资源池中分配需要的 ep。如果配置的 function driver 过多,就可能会分配失败。

7.12.4.2.3.3. 普通 ep 的 request 处理

image6

如上图所示,对于普通 ep 的 reuqest 数据收发分为两步:

  • request enqueue。首先调用 udc 的 .queue() 函数,将需要传输的数据插入到硬件控制器对应的 ep 寄存器当中。

  • complete callback。ep 数据收发完成会产生 transfer complete 中断,在中断服务程序中调用 complete 回调函数,结束整个 request 传输。

7.12.4.2.3.4. ep0 的 request 处理

image7

如上图所示,对于 ep0 的 reuqest 数据收发和 普通 ep 基本一样,只是对数据的回调处理稍有不同。

7.12.4.2.4. 数据结构

7.12.4.2.4.1. aic_usb_ep_ops

AIC UDC 驱动核心的数据结构为 usb_ep_ops, 实现了 op 操作的相关函数:

static const struct usb_ep_ops aic_usb_ep_ops = {
    .enable                 = aic_ep_enable,
    .disable                = aic_ep_disable,
    .alloc_request          = aic_ep_alloc_request,
    .free_request           = aic_ep_free_request,
    .queue                  = aic_ep_queue_request,
    .dequeue                = aic_ep_dequeue_request,
    .set_halt               = aic_ep_sethalt,
};

7.12.4.2.5. 接口设计

7.12.4.2.5.1. aic_ep_queue_request

函数原型

int aic_ep_queue_request(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags)

功能说明

接收上层传入的 request,并将其配置到 ep 寄存器中。

参数定义

ep:当前 ep 控制结构
req:当前 request 控制结构
gfp_flags:分配内存时使用的标志

返回值

0,成功; < 0,失败

注意事项

7.12.4.2.5.2. aic_ep0_process_control

函数原型

void aic_ep0_process_control(struct aic_usb_gadget *gg, struct usb_ctrlrequest *ctrl)

功能说明

处理 ep0 接收到的 control 数据包。

参数定义

gg:当前 gadget 控制结构
ctrl:当前 control 数据包

返回值

0,成功; < 0,失败

注意事项