UDC驱动的接口都定义在drivers/usb/gadget/udc/core.c文件中。USB Function驱动通过调用这些接口匹配及访问USB设备控制器,而底层USB控制器驱动要实现这些接口定义的功能。下面分析一下主要的UDC驱动接口调用流程。
Composite层通过调用UDC core层的usb_udc_attach_driver和usb_gadget_probe_driver接口将Function驱动和UDC驱动绑定。前者通过UDC设备的名称匹配,通常是configfs配置的USB Function驱动使用,后者直接匹配udc_list链表中的第一个UDC驱动,通常是legacy类型的USB Function驱动使用,如g_audio驱动。使用usb_gadget_unregister_driver函数解除Function驱动和UDC驱动的绑定。具体的绑定过程,在分析具体的Function驱动时说明。
[drivers/usb/gadget/udc/core.c]
int usb_udc_attach_driver(const char *name, struct usb_gadget_driver *driver);
int usb_gadget_probe_driver(struct usb_gadget_driver *driver);
int usb_gadget_unregister_driver(struct usb_gadget_driver *driver);
底层的USB设备控制器驱动(dwc3 gadget)使用usb_add_gadget_udc函数将自身加入到Core UDC Framework中,使用usb_del_gadget_udc从Core UDC Framework删除。加入过程在UDC驱动初始化的时候已经分析过了,这里不再赘述。
[include/linux/usb/gadget.h]
int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget);
void usb_del_gadget_udc(struct usb_gadget *gadget);
下面两个函数用来开启和停止UDC,Function驱动和UDC驱动绑定的时候通过调用usb_udc_attach_driver或usb_gadget_probe_driver间接调用usb_gadget_udc_start开启UDC,Function驱动和UDC驱动解除绑定的时候通过调用usb_gadget_unregister_driver间接调用usb_gadget_udc_stop停止UDC。
[drivers/usb/gadget/udc/core.c]
static inline int usb_gadget_udc_start(struct usb_udc *udc);
static inline void usb_gadget_udc_stop(struct usb_udc *udc);
[include/linux/usb/gadget.h]
int usb_udc_attach_driver(const char *name, struct usb_gadget_driver *driver);
int usb_gadget_probe_driver(struct usb_gadget_driver *driver);
int usb_gadget_unregister_driver(struct usb_gadget_driver *driver);
USB Function驱动和UDC绑定成功后,通过调用usb_gadget_udc_start接口开启USB设备控制器,接收主机发送的请求,其内部调用的dwc3设备控制器驱动的dwc3_gadget_start函数。主要的工作是设置相关参数并使能USB设备控制器,具体如下:
USB Function驱动和UDC解除绑定成功后会调用usb_gadget_udc_stop函数停止USB设备控制器。主要的工作是清除USB控制器上的USB请求,然后关闭端点,具体如下:
端点0的端点描述符定义在dwc3的设备控制器驱动中,Function驱动无需定义,具体如下。
[drivers/usb/dwc3/gadget.c]
/* 端点0的描述符在dwc3 gadget驱动中定义 */
static struct usb_endpoint_descriptor dwc3_gadget_ep0_desc = {
.bLength = USB_DT_ENDPOINT_SIZE, // 端点描述符长度
.bDescriptorType = USB_DT_ENDPOINT, // 描述符类型为端点描述符
.bmAttributes = USB_ENDPOINT_XFER_CONTROL, // 端点0使用控制传输
};
从上面的分析中可以看出,软件通过向USB设备控制器端点USB3_DEPnCMD寄存器写入命令来驱动USB端点工作,命令可以携带参数,参数写到USB3_DEPnCMDPAR1、USB3_DEPnCMDPAR2、USB3_DEPnCMDPAR3寄存器。命令的类型由USB3_DEPnCMD寄存器的bit[0:3]位决定,bit[10]控制命令是否执行,软件设置为1则端点开始执行命令,执行完毕,端点自动清零,软件可以根据此位判断端点是否执行完命令。USB3_DEPnCMD寄存器的低11bit意义如下图所示。在执行命令之前,需要提前把命令参数写到参数寄存器中,若不需要参数,则写入0即可。
下面是参数寄存器USB3_DEPnCMDPAR0、USB3_DEPnCMDPAR1、USB3_DEPnCMDPAR2的具体意义。
位域 | 说明 |
---|---|
31:30 | 配置行为。0-初始化端点状态,用于第一次配置端点1-恢复端点状态,用于休眠唤醒后恢复端点状态,需要恢复的状态保存到DEPCMDPAR2寄存器中2-修改端点状态,用于修改已经配置好的端点配置,例如修改DEPEVTEN使能位、interrupt number、MaxPacketSize。 |
29:26 | Reserved |
25:22 | 设置突发传输数值Burst Size,即一个微帧内传输多少包数据,只有USB3.0及以上支持此特性。0:Burst length = 11:Burst length = 2…15:Burst length = 16 |
21:17 | 分配给此端点的FIFO编号。对于控制端点,输出端点和输入端点的值应该相等,即ep0_in和ep0_out的FIFO编号相等。对于输出端点此值为0。尽管在DRD模式中TxFIFOs数量超过16个,但在设备模式中必须使用较低的16个。 |
16:14 | Reserved |
13:3 | 输入输出端点支持的最大包长。USB3.0支持的最大包长为1024字节。 |
2:1 | 端点类型2’b00: Control2’b01: Isochronous2’b10: Bulk2’b11: Interrupt |
位域 | 说明 |
---|---|
31 | FIFO-based |
30 | Reserved |
29:25 | 设置USB端点编号。bit[29:26]: Endpoint numberbit[25]: Endpoint direction,0-OUT,1-IN物理端点0必须分配给控制器输出端点Stream的能力,指出此端点具有流传输功能(MaxStreams != 0) |
24 | 物理端点1必须分配给控制器输入端点 |
23:16 | 设置bInterval的值,端点描述符中设置了该值(写入寄存器的值为真实bInterval-1)。即轮训数据传送端点的时间间隔,对于批量传送和控制传送的端点忽略,对于同步传送的端点,必须为1,对于中断传送的端点,范围为1-255。dwc3控制器支持的有效值范围为0-13 |
15 | 如果bulk端点使用了 External Buffer Control (EBC)模式 |
14 | 如果bit15设置了该位才能设置,否则不要设置此位。1:控制器不会更新TRB的HWO位域0:控制器会更新TRB的HWO位域 |
13:8 | 使能设备端点的特殊事件DEPEVTEN,写入0关闭。Bit 13: Stream Event Enable (StreamEvtEn)Bit 12: ReservedBit 11: ReservedBit 10: XferNotReady Enable (XferNRdyEn)Bit 9: XferInProgress Enable (XferInProgEn)Bit 8: XferComplete Enable (XferCmplEn) |
7:5 | Reserved |
4:0 | 输入和输出端点的中断编号。指示此端点产生的与端点相关的中断的中断/事件缓冲区号。如果没有使能multiple Interrupter配置,则此位域必须设置为0 |
位域 | 说明 |
---|---|
31:0 | 当USB3_DEPnCMDPAR0寄存器的bit[31:30]为1时,该寄存器中保存的是需要恢复端点状态的值,否则为Reserved |
USB Function驱动通过调用usb_ep_enable打开除端点0以外的端点。端点0打开逻辑由dwc3驱动自身处理,不需要USB Function驱动直接参与。
[include/linux/usb/gadget.h]
int usb_ep_enable(struct usb_ep *ep);
usb_ep_enable函数的工作流程如下图所示,和usb_gadget_udc_start函数一样,最终通过调用__dwc3_gadget_ep_enable实现。__dwc3_gadget_ep_enable函数的执行流程见2.2.1.节,下面主要说不同的地方:
USB Function驱动通过调用usb_ep_disable
关闭除端点0以外的端点。端点0关闭逻辑由dwc3驱动自身处理,不需要USB Function驱动直接参与。
[include/linux/usb/gadget.h]
int usb_ep_disable(struct usb_ep *ep);
usb_ep_disable函数的工作流程如下图所示,和usb_gadget_udc_stop函数一样,最终通过调用__dwc3_gadget_ep_disable实现。__dwc3_gadget_ep_disable函数的执行流程见2.2.1.节,最后设置端点状态关闭。
USB Function驱动可以通过gadget_find_ep_by_name和usb_gadget_ep_match_desc函数来匹配要使用的端点。前者实现较为简单,通过端点的名称进行匹配,使用的不多。后者通过端点描述符和USB3.0端点伴侣描述符进行匹配,使用较多,返回值为1时表示匹配成功,返回值为0表示匹配失败,其匹配的流程如下:
[include/linux/usb/gadget.h]
// name为要匹配端点的名称
struct usb_ep *gadget_find_ep_by_name(
struct usb_gadget *g, const char *name);
// ep为要匹配端点的数据结构,desc匹配端点的端点描述符
// ep_comp匹配端点的USB3.0端点伴侣描述符
int usb_gadget_ep_match_desc(struct usb_gadget *gadget,
struct usb_ep *ep, struct usb_endpoint_descriptor *desc,
struct usb_ss_ep_comp_descriptor *ep_comp);
USB Function驱动通过调用usb_ep_alloc_request和usb_ep_free_request分配和释放USB I/O请求。
[include/linux/usb/gadget.h]
struct usb_request *usb_ep_alloc_request(struct usb_ep *ep, gfp_t gfp_flags);
void usb_ep_free_request(struct usb_ep *ep, struct usb_request *req);
dwc3所有端点都是通过dwc3_gadget_ep_alloc_request分配USB请求,实质上分配的是dwc3_request数据结构,内部包含了通用的USB请求数据结构usb_request。
dwc3所有端点都是通过dwc3_gadget_ep_free_request释放USB请求。
USB Function驱动通过调用usb_ep_queue和usb_ep_dequeue将USB I/O请求提交到发送队列和从发送队列取消。
[include/linux/usb/gadget.h]
int usb_ep_queue(struct usb_ep *ep,
struct usb_request *req, gfp_t gfp_flags);
int usb_ep_dequeue(struct usb_ep *ep, struct usb_request *req);
向端点0和非端点0提交USB请求,usb_ep_queue内部调用的函数不同。端点0调用dwc3_gadget_ep0_queue函数,非端点0调用dwc3_gadget_ep_queue函数。
如下图所示,向端点0提交USB请求时,会调用dwc3 gadget驱动的dwc3_gadget_ep0_queue函数,其主要的执行流程为:
usb_ep_queue向非端点0提交USB请求的过程如下图所示,最终通过__dwc3_gadget_ep_queue函数提交。下面分析一下向非端点0提交USB请求的主要工作内容:
上面说过了,TRB和request最终通过dwc3_prepare_one_trb实现,该函数的执行过程如下图所示,主要的工作内容为:
端点0和非端点0取消USB请求的过程是一样的,最终都是通过调用dwc3 gadget驱动的dwc3_gadget_ep_dequeue实现,其主要的工作内容如下:
dwc3驱动在发送USB请求的时候需要调用usb_gadget_map_request映射缓冲区,获取缓冲区的DMA地址。USB请求发送完或dequeue USB请求的时候,需要调用usb_gadget_unmap_request将缓冲区进行反向映射。
[include/linux/usb/gadget.h]
int usb_gadget_map_request(struct usb_gadget *gadget,
struct usb_request *req, int is_in);
void usb_gadget_unmap_request(struct usb_gadget *gadget,
struct usb_request *req, int is_in);
usb_gadget_map_request的执行流程如下图所示,dma_map_sg适用于支持SG的DMA,dma_map_single适用于不支持SG的DMA,底层映射的机制是相同的,这里只说明dma_map_single。
usb_gadget_unmap_request的执行流程如下图所示,dma_unmap_sg适用于支持SG的DMA,dma_unmap_single适用于不支持SG的DMA,底层映射的机制是相同的,这里只说明dma_unmap_single。
swiotlb_dma_ops是arm64平台上使用软件实现的smmu/iommu,定义如下。
[arch/arm64/mm/dma-mapping.c]
void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
struct iommu_ops *iommu, bool coherent);
static struct dma_map_ops swiotlb_dma_ops = {
.alloc = __dma_alloc,
.free = __dma_free,
.mmap = __swiotlb_mmap,
.get_sgtable = __swiotlb_get_sgtable,
.map_page = __swiotlb_map_page,
.unmap_page = __swiotlb_unmap_page,
.map_sg = __swiotlb_map_sg_attrs,
.unmap_sg = __swiotlb_unmap_sg_attrs,
.sync_single_for_cpu = __swiotlb_sync_single_for_cpu,
.sync_single_for_device = __swiotlb_sync_single_for_device,
.sync_sg_for_cpu = __swiotlb_sync_sg_for_cpu,
.sync_sg_for_device = __swiotlb_sync_sg_for_device,
.dma_supported = swiotlb_dma_supported,
.mapping_error = swiotlb_dma_mapping_error,
};
当USB请求传输完成后,可通过usb_gadget_giveback_request函数回调USB请求的回调函数。USB请求的回调函数一般由Function驱动设置。
[include/linux/usb/gadget.h]
void usb_gadget_giveback_request(struct usb_ep *ep, struct usb_request *req)
{
if (likely(req->status == 0))
usb_led_activity(USB_LED_EVENT_GADGET);
trace_usb_gadget_giveback_request(ep, req, 0);
req->complete(ep, req);
}
USB协议定义了一组联络代码,表明传输的状态。联络代码会在数据信息包或联络信息包中传输。联络代码有ACK、NAK、STALL、NYET和ERR。具体如下:
UDC驱动提供了设置端点状态的函数,如下所示。usb_ep_set_halt可以将端点特性设置为Halt,此时端点将处于STALL状态,不发送数据也不接收数据,除非主机发送CLEAR_FEATURE请求。usb_ep_clear_halt函数用于清除端点的Halt特性,此时端点将从STALL状态中恢复。usb_ep_set_wedge将端点特性设置为Halt并且会忽略CLEAR_FEATURE请求,只有Function驱动才可以清除Halt特性。
[include/linux/usb/gadget.h]
int usb_ep_set_halt(struct usb_ep *ep);
int usb_ep_clear_halt(struct usb_ep *ep);
int usb_ep_set_wedge(struct usb_ep *ep);
端点0设置和清除halt特性的执行流程如下图所示。usb_ep_set_halt和usb_ep_clear_halt函数最终都会调到dwc3 gadget驱动的dwc3_gadget_ep0_set_halt函数,当第二个参数value=1时表示usb_ep_set_halt,value=0时表示usb_ep_clear_halt。虽然上层区t分了se halt和clear halt,但对于端点0,底层都是进行stall and restart操作,流程如下:
非端点0设置和清除halt特性的执行流程如下图所示。usb_ep_set_halt和usb_ep_clear_halt函数最终都会调到dwc3 gadget驱动的dwc3_gadget_ep_set_halt函数,当第二个参数value=1时表示usb_ep_set_halt,value=0时表示usb_ep_clear_halt。蓝色表示usb_ep_set_halt函数执行路径,橙黄色表示usb_ep_clear_halt函数执行路径。绿色两者都会执行。需要注意的是isoc端点无法设置和清除halt特性。 usb_ep_set_halt执行流程如下:
usb_ep_clear_halt执行流程如下:
usb_ep_set_wedge函数将端点的状态设置为stall,并且会忽略主机发送的CLEAR_FEATURE请求,也就是说主机无法清除带有DWC3_EP_WEDGE标志的端点的stall状态,只有设备驱动自己可以清除。
软件可以通过usb_gadget_connect和usb_gadget_disconnect函数控制设备连接主机和设备和主机断开。usb_gadget_connect调用后,设备会将D+上拉,主机识别D+上拉后开始枚举设备。
[include/linux/usb/gadget.h]
int usb_gadget_connect(struct usb_gadget *gadget);
int usb_gadget_disconnect(struct usb_gadget *gadget);
usb_gadget_connect和usb_gadget_disconnect的执行流程如下图所示。将DWC3_DCTL寄存器第31位设置为1时,USB设备控制器开始运行,会将D+上拉,主机识别D+上拉后开始枚举设备。将DWC3_DCTL寄存器第31位设置为0时,USB设备停止运行,会和主机断开连接,在将第31位设置为0之前,需要将所有传输的USB请求清空,将31位设置为0之后,需要等待设备和主机断开连接的操作完成,通过读取DWC3_DSTS寄存器的第22位判断,为0时表示已断开连接。
Function驱动和UDC驱动绑定的时候,实质上是通过usb_udc_connect_control函数控制设备和主机连接和断开。主机通过vbus向设备供电,若设备不需要主机供电,则udc->vbus=true。若需要,则通过usb_udc_vbus_handler函数更新vbus的状态,像U盘、移动硬盘这类设备,必须要主机供电,供电以后才能开始工作和被主机枚举。
[include/linux/usb/gadget.h]
static void usb_udc_connect_control(struct usb_udc *udc)
{
if (udc->vbus)
usb_gadget_connect(udc->gadget);
else
usb_gadget_disconnect(udc->gadget);
}
/* updates the udc core vbus status, and try to
* connect or disconnect gadget
*/
void usb_udc_vbus_handler(struct usb_gadget *gadget, bool status)
{
struct usb_udc *udc = gadget->udc;
if (udc) {
udc->vbus = status;
usb_udc_connect_control(udc);
}
}
UDC驱动向/sys目录导出了一些属性文件,供使用者在用户空间查询和操作。下面以rk3399为例,进行介绍:
查看当前的USB控制器是否支持OTG
/sys/devices/platform/usb0/fe800000.dwc3/udc/fe800000.dwc3/is_otg
查看当前的USB控制器是否处于设备模式
/sys/devices/platform/usb0/fe800000.dwc3/udc/fe800000.dwc3/is_a_peripheral
查看当前的USB控制器是否是自供电
/sys/devices/platform/usb0/fe800000.dwc3/udc/fe800000.dwc3/is_selfpowered
查看当前的USB控制器速度
/sys/devices/platform/usb0/fe800000.dwc3/udc/fe800000.dwc3/current_speed
查看当前的USB控制器支持的最大速度
/sys/devices/platform/usb0/fe800000.dwc3/udc/fe800000.dwc3/maximum_speed
USB控制器的速度定义如下:
"UNKNOWN","low-speed","full-speed","high-speed","wireless","super-speed","super-speed-plus"。
控制USB设备控制器连接主机,connect-连接,disconnect-断开连接
/sys/devices/platform/usb0/fe800000.dwc3/udc/fe800000.dwc3/soft_connect
查看当前USB设备控制器的状态
/sys/devices/platform/usb0/fe800000.dwc3/udc/fe800000.dwc3/state
USB设备控制器的状态定义如下:
"not attached","attached","powered","reconnecting","unauthenticated","default","addressed","configured","suspended"。
上面总结了常用的UDC驱动接口,这些接口大多数可被Function驱动直接调用,少部分经过封装被Function驱动调用。通过分析这些接口的调用流程,对认识USB控制器内部的工作流程有很大的帮助。其实最重要的是弄清楚USB设备控制器接收数据和发送数据的流程,发送数据通过usb_ep_queue发送,上面已经介绍过了,但接收数据的流程牵扯到中断处理和中断处理线程,后面专门开一章节介绍USB设备控制器的中断处理过程和数据接收过程。