3. UDC驱动核心:UDC(USB Device Controller)驱动的作用、注册与回调机制
UDC(USB Device Controller)驱动是USB Gadget框架中直接与硬件交互的底层软件层。它负责管理USB控制器硬件的寄存器、端点(Endpoint)资源、DMA传输以及中断处理。在Gadget驱动栈中,UDC驱动处于最底层,向上通过标准接口与Gadget Function驱动通信,向下直接操作硬件。
核心认知:UDC驱动是连接硬件与协议栈的桥梁。面试中,面试官通常关注你是否理解UDC驱动的注册流程、回调函数链以及端点生命周期管理。
3.1 UDC驱动的核心职责
UDC驱动需要完成以下关键任务:
- 硬件初始化与去初始化:上电时配置控制器时钟、电源、中断线;关机时释放资源。
- 端点管理:创建、配置、使能/禁用端点(包括控制端点0和功能端点)。
- DMA与数据搬运:管理USB请求(URB/Request)的提交、取消和完成回调。
- 中断处理:处理USB总线事件(复位、挂起、恢复、连接/断开)和端点传输完成中断。
- 状态报告:向Gadget层报告当前连接状态、速度(HS/FS/LS)和帧号。
| 职责领域 | 具体操作 | 对应内核接口/结构体 |
|---|---|---|
| 硬件初始化 | ioremap、时钟使能、中断注册 | platform_driver_register() |
| 端点管理 | 分配端点、设置最大包大小、FIFO配置 | usb_ep_ops 中的 enable/disable |
| 请求处理 | 提交/取消USB请求,处理完成回调 | usb_request、usb_ep_queue() |
| 中断处理 | 解析中断状态寄存器,分发事件 | request_irq() + 自定义handler |
| 状态通知 | 调用 gadget->ops->connect/disconnect |
usb_gadget_ops |
3.2 UDC驱动的注册流程
UDC驱动通常以内核平台驱动(Platform Driver)或PCI驱动形式存在。注册过程分为两个阶段:驱动注册和Gadget注册。
- 编写平台驱动结构体:填充
struct platform_driver的 probe/remove 回调。 - 在probe中:
- 映射硬件寄存器地址(
devm_ioremap_resource)。 - 初始化控制器内部状态(时钟、复位、FIFO)。
- 分配并初始化
struct usb_gadget实例。 - 设置
gadget->ops(指向UDC驱动的操作函数表)。 - 调用
usb_add_gadget(gadget)将Gadget设备注册到内核。
- 映射硬件寄存器地址(
- 在remove中:
- 调用
usb_del_gadget(gadget)注销Gadget设备。 - 释放中断、取消DMA、释放寄存器映射。
- 调用
// 典型的UDC驱动probe函数骨架
static int my_udc_probe(struct platform_device *pdev)
{
struct my_udc *udc;
struct usb_gadget *gadget;
int ret;
udc = devm_kzalloc(&pdev->dev, sizeof(*udc), GFP_KERNEL);
if (!udc)
return -ENOMEM;
udc->regs = devm_ioremap_resource(&pdev->dev,
platform_get_resource(pdev, IORESOURCE_MEM, 0));
if (IS_ERR(udc->regs))
return PTR_ERR(udc->regs);
// 初始化硬件
my_udc_hw_init(udc);
// 初始化Gadget结构体
gadget = &udc->gadget;
gadget->ops = &my_udc_ops; // 绑定操作函数表
gadget->ep0 = &udc->ep0; // 端点0
gadget->name = "my-udc";
gadget->dev.parent = &pdev->dev;
// 注册Gadget设备
ret = usb_add_gadget(gadget);
if (ret)
return ret;
platform_set_drvdata(pdev, udc);
dev_info(&pdev->dev, "UDC probe success\n");
return 0;
}
面试要点:注意
usb_add_gadget() 与 usb_add_gadget_udc() 的区别。前者只注册Gadget设备,后者会额外创建UDC类设备并自动匹配Gadget驱动。新内核推荐使用 usb_add_gadget_udc() 简化流程。
3.3 回调机制:usb_gadget_ops 详解
UDC驱动通过 struct usb_gadget_ops 向Gadget层暴露硬件操作能力。这是面试中最高频的考点之一。
struct usb_gadget_ops {
int (*get_frame)(struct usb_gadget *);
int (*wakeup)(struct usb_gadget *);
int (*set_selfpowered)(struct usb_gadget *, int);
int (*vbus_session)(struct usb_gadget *, int);
int (*vbus_draw)(struct usb_gadget *, unsigned mA);
int (*pullup)(struct usb_gadget *, int);
int (*ioctl)(struct usb_gadget *, unsigned, unsigned long);
void (*get_config_params)(struct usb_gadget *, struct usb_config_params *);
int (*udc_start)(struct usb_gadget *, struct usb_gadget_driver *);
int (*udc_stop)(struct usb_gadget *);
};
| 回调函数 | 触发时机 | 典型实现 |
|---|---|---|
udc_start |
Gadget驱动绑定到UDC时调用 | 使能中断、连接DMA、拉高D+(pullup) |
udc_stop |
Gadget驱动解绑时调用 | 停止所有端点、禁用中断、拉低D+ |
pullup |
软件控制D+/D-上拉电阻 | 写寄存器控制DP/DM上拉开关 |
get_frame |
主机请求获取当前帧号 | 读取SOF帧号寄存器 |
vbus_session |
VBUS检测状态变化 | 通知PM核心,唤醒/休眠控制器 |
vbus_draw |
配置最大VBUS电流 | 设置充电/限流参数 |
常见陷阱:许多面试者混淆
udc_start 和 pullup 的职责。记住:udc_start 是软件层面的绑定和初始化,pullup 是物理层信号控制。主机检测到设备插入依赖 pullup(1),而不是 udc_start。
3.4 端点操作与请求生命周期
UDC驱动还需要实现 struct usb_ep_ops,这是操作具体端点的接口。每个端点对应一个 struct usb_ep 实例。
struct usb_ep_ops {
int (*enable)(struct usb_ep *ep, const struct usb_endpoint_descriptor *desc);
int (*disable)(struct usb_ep *ep);
struct usb_request *(*alloc_request)(struct usb_ep *ep, gfp_t gfp_flags);
void (*free_request)(struct usb_ep *ep, struct usb_request *req);
int (*queue)(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags);
int (*dequeue)(struct usb_ep *ep, struct usb_request *req);
int (*set_halt)(struct usb_ep *ep, int value);
int (*set_wedge)(struct usb_ep *ep);
int (*fifo_status)(struct usb_ep *ep);
void (*fifo_flush)(struct usb_ep *ep);
};
USB请求(struct usb_request)的生命周期如下:
- 分配:Function驱动调用
usb_ep_alloc_request()分配请求对象。 - 填充:设置缓冲区指针、长度、完成回调
complete。 - 提交:调用
usb_ep_queue()将请求提交到UDC硬件队列。 - 传输:UDC硬件执行DMA或PIO传输。
- 完成:传输结束后,UDC中断处理函数调用请求的
complete回调。 - 释放:Function驱动调用
usb_ep_free_request()回收请求。
3.5 面试高频问题与避坑指南
- Q: udc_start 和 pullup 的调用顺序?
A: 先调用udc_start完成软件初始化,再调用pullup(1)通知主机。顺序不可颠倒。 - Q: 如何处理端点STALL条件?
A: 调用usb_ep_set_halt()设置端点Halt状态。注意控制端点处理协议错误时需要自动清除Halt。 - Q: UDC驱动如何支持多个配置?
A: 在udc_start中不预设配置,由Gadget层根据主机请求动态切换。UDC只需提供足够的端点资源。 - Q: 中断上下文中能否调用
usb_ep_queue?
A: 可以,但必须使用GFP_ATOMIC标志。这是UDC驱动中常见的优化手段。
实战建议:阅读内核源码中
drivers/usb/gadget/udc/ 目录下的真实驱动(如 dwc2、dwc3、chipidea),重点关注 udc_start 和端点 queue 的实现。面试时能引用具体驱动代码会大幅加分。