1.概述

USB设备控制器(UDC)驱动的框图如下图所示,由三部分组成。第一部分是UDC驱动核心层,在drivers/usb/gadget/udc/core.c文件中实现,该层是一个兼容层,将USB Function驱动和具体的USB gadget驱动隔离开,抽象了统一的接口和数据结构,向USB Function驱动提供了统一且稳定的接口,同时完成USB Function驱动和USB gadget驱动的匹配。第二部分是gadget driver层,负责驱动硬件工作,和具体的USB设备控制器硬件相关,dwc3的gadget driver驱动在drivers/usb/dwc3/gadget.c文件中实现。第三部分是USB设备控制器硬件。

UDC驱动框图
USB gadget驱动描述了USB设备控制器的硬件操作方法,不同的USB控制器实现不同。有的USB控制器只能作为设备控制器,如ompa、pxa2等USB设备控制器,其驱动在drivers/usb/gadget/udc文件夹中。有的USB控制器即可做主机控制器,也可做设备控制器,具有OTG功能,可以在两种模式中切换,如dwc3 USB控制器,其驱动在drivers/usb/dwc3文件中。RK3399的USB3.0控制器采用dwc3 USB控制器,具有OTG功能。

2.控制器模式

USB控制器切换为设备模式后使用UDC驱动,因此先从USB设备控制器的初始化过程开始分析,并对关键的数据结构做出说明。

2.1.初始化

在设备树中,设置dr_mode = "otg"属性,则dwc3控制器初始化的时候会将控制器设置为USB_DR_MODE_OTG模式,同时调用dwc3_host_initdwc3_gadget_init函数初始化主机模式和设备模式所需的资源,控制器后续可以动态切换为主机模式和设备模式。dwc3 USB3.0控制器的初始化过程如下图所示,重点分析初始化设备模式的过程,主要的工作如下:
(1)将控制器设置为USB_DR_MODE_OTG模式。
(2)初始化主机模式所需资源,具体过程在分析主机驱动的时候分析。
(3)初始化设备模式所需资源。
(a)获取中断号和分配端点0传输所需的内存,端点0在设备枚举的时候使用,需要响应主机端的请求,因此需要提前分配好内存。
(b)设置dwc3设备控制器的操作函数集合为dwc3_gadget_ops,只涉及硬件的控制,不涉及I/O操作。
(c)初始化硬件端点。先初始化输出端点,后初始化输入端点。端点0的最大包长为512字节,其他端点的最大包长为1024字节。端点0的操作函数为dwc3_gadget_ep0_ops,其他端点的操作函数为dwc3_gadget_ep0_ops,端点的操作函数主要描述I/O操作。非端点0都会挂到gadget.ep_list链表。端点0支持控制传输,其他端点支持等时、批量、中断传输。
(d)添加udc驱动。首先分配usb_udc数据结构,接着将其挂到udc_list链表,最后设置udc驱动状态为USB_STATE_NOTATTACHED

dwc3_USB3.0控制器初始化过程

初始化完成后的数据结构如下图所示。最重要的还是跟端点相关的内容。端点0用于控制器传输,如设备枚举,响应主机发送的setup等请求,资源需要提前分配好。端点0与其他端点有本质的区别,因此其操作函数都是特有的。其他端点主要用于传输数据,操作函数共用。

UDC驱动数据结构

2.2.模式切换

在设备树里面,将dwc3 USB控制器配置成peripheral模式,系统启动的时候会将USB控制器设置为设备模式,并初始化gadget相关资源,若配置成了otg模式,则只会初始化gadget相关资源,不会将dwc3控制器切换为设备模式,此时dwc3控制器处于otg模式,需要切换为设备模式(只有处于otg模式才可以切换为主机或设备)。
RK3399的USB3.0控制器可以切换为主机或设备模式。有两种切换方式,一种是使用fusb302芯片,底层的硬件感知到接入的设备,一般通过usb_id和vbus进行判断,然后通过中断的方式通知系统,最后系统根据接入设备的类型,将USB控制器切换为主机模式或设备模式。另一种是手动切换,向/sys/devices/platform/usb0/dwc3_mode文件中写入值进行切换,写入0或otg则切换为otg模式,写入1或host则切换为host模式,写入2或peripheral则切换为device模式。

// 切换为otg模式
echo 0 > /sys/devices/platform/usb0/dwc3_mode
echo otg > /sys/devices/platform/usb0/dwc3_mode
// 切换为host模式
echo 1 > /sys/devices/platform/usb0/dwc3_mode
echo host > /sys/devices/platform/usb0/dwc3_mode
// 切换为device模式
echo 2 > /sys/devices/platform/usb0/dwc3_mode
echo peripheral > /sys/devices/platform/usb0/dwc3_mode

上述对模式的切换,都通过调度工作队列otg_work完成,工作队列调用dwc3_rockchip_otg_extcon_evt_work函数进行切换。

[drivers\usb\dwc3\dwc3-rockchip.c]
struct dwc3_rockchip {
    ......
	struct work_struct	otg_work;
    ......
};
static void dwc3_rockchip_otg_extcon_evt_work(struct work_struct *work)
{
	if (rockchip->force_mode ? dwc->dr_mode == USB_DR_MODE_PERIPHERAL :
	    extcon_get_cable_state_(edev, EXTCON_USB)) {
        ......
		spin_lock_irqsave(&dwc->lock, flags);
		// 设备模式
		dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
		spin_unlock_irqrestore(&dwc->lock, flags);
	} else if (rockchip->force_mode ? dwc->dr_mode == USB_DR_MODE_HOST :
		   extcon_get_cable_state_(edev, EXTCON_USB_HOST)) {
        ......
		spin_lock_irqsave(&dwc->lock, flags);
		// 主机模式
		dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
		spin_unlock_irqrestore(&dwc->lock, flags);
        ......
	} else {
        ......
	}
    ......
}

3.关键数据结构

2.1.端点

struct usb_ep是Linux内核描述USB设备控制器端点的通用数据结构。ops是端点对应的操作函数,主要用于描述I/O操作。ep_list是该端点的链表节点,通常情况下挂到usb_gadgetep_list链表上。maxpacket描述端点的最大包长,由端点描述符(软件)配置,如在USB3.0中,bulk传输最大包长为512,isoc最大包长为1024。maxpacket_limit描述端点硬件能处理的最大包长,如在USB3.0中,端点0最大能处理512字节,其他端点最大能处理1024字节。USB3.0支持在一个125微妙内burst传输多个数据包,最大值由maxburst设置,范围为0-15,0表示传输1包,15表示可以传输16包,对于端点0该值只能为0。每个端点都有不同的地址,使用addr描述。desc指向端点描述符。若使用USB3.0,则还需要设置comp_desc描述符。
struct usb_ep通常不直接使用,而是嵌入到一个大的数据结构中使用。在dwc3控制器中,嵌入到了struct dwc3_ep结构体中。pending_liststarted_list用于存放I/O请求数据结构struct usb_request,前者存放pending的I/O请求,暂时还不能处理,后者存放已经开始处理的I/O请求。trb_pool是一个trb组成的数组,由硬件自动处理,里面存放传输缓冲区的地址、长度及标志,非端点0分配256个trb,trb_pool_dma保存trb_pool的物理地址。trb_enqueuetrb_dequeuetrb_pool已使用和未使用的数组索引。allocated_requests表示已分配I/O请求的数量。

[include/linux/usb/gadget.h]
struct usb_ep {  // USB设备模式端点通用数据结构
	const char		*name;         // 名字
	const struct usb_ep_ops	*ops;  // 该端点对应的操作函数
	struct list_head	ep_list;   // 端点的链表节点
	struct usb_ep_caps	caps;      // 端点支持的传输类型
	bool			claimed;
	bool			enabled;       // 端点是否使能
	unsigned		maxpacket:16;  // 最大包长,由端点描述符配置
	unsigned		maxpacket_limit:16;  // 端点硬件能处理的最大包长
	unsigned		max_streams:16;      // 流的最大数量,范围0-16
	unsigned		mult:2; // multiplier, 'mult' value for SS Isoc EPs
	// 端点支持的最大burst,范围0-15,USB3.0支持该选项
	unsigned		maxburst:5;  
	u8			address;         // 端点地址,用于区分不同的端点
	const struct usb_endpoint_descriptor	*desc;       // 端点描述符
	const struct usb_ss_ep_comp_descriptor	*comp_desc;  // USB3.0伴侣描述符
};
[drivers/usb/dwc3/core.h]
struct dwc3_ep {  // dwc3 USB控制器设备模式端点数据结构
	struct usb_ep		endpoint;      // 通用的设备端点数据结构
	struct list_head	pending_list;  // pending的IO requests
	struct list_head	started_list;  // started的IO requests
	spinlock_t		lock;              // 自旋锁
	void __iomem		*regs;         // 该端点的寄存器基地址
	struct dwc3_trb		*trb_pool;     // 该端点的trb数组,用于DMA传输数据
	dma_addr_t		trb_pool_dma;      // 该端点的trb数组物理地址
	const struct usb_ss_ep_comp_descriptor *comp_desc;  // USB3.0伴侣描述符
	struct dwc3		*dwc;   // 指向dwc3 ctrl
	u32			saved_state;
	unsigned		flags;  // 该端点的标志,由DWC3_EP开头的宏定义
#define DWC3_EP_ENABLED		(1 << 0)
#define DWC3_EP_STALL		(1 << 1)
#define DWC3_EP_WEDGE		(1 << 2)
#define DWC3_EP_BUSY		(1 << 4)
#define DWC3_EP_PENDING_REQUEST	(1 << 5)
#define DWC3_EP_MISSED_ISOC	(1 << 6)
#define DWC3_EP0_DIR_IN		(1 << 31)
	u8			trb_enqueue;  // trb数组入队索引
	u8			trb_dequeue;  // trb数组出队索引
	u8			number;       // endpoint number (1 - 15)
	// set to bmAttributes & USB_ENDPOINT_XFERTYPE_MASK
	u8			type;
	u8			resource_index;
	u32			allocated_requests;  // 已经分配的IO requests
	u32			queued_requests;     // 入队准备传输的IO requests数量
	// the interval on which the ISOC transfer is started
	u32			interval;
	// a human readable name e.g. ep1out-bulk
	char			name[20];
	unsigned		direction:1; // true for TX, false for RX
	unsigned		stream_capable:1;
};

struct usb_ep_ops描述端点的操作函数,主要和I/O操作相关。这些函数和硬件紧密相关,USB设备控制器需要实现这些函数,端点0和非端点0的函数实现也不一致。enable使能端点,disable禁止端点,alloc_request分配I/O请求数据结构usb_requestfree_request释放I/O请求,queue将I/O请求加入队列,dequeue将I/O请求移除队列,fifo_status获取fifo的状态,fifo_flush刷新fifo。

[include/linux/usb/gadget.h]
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);
};

2.2.USB I/O请求

USB的I/O请求使用struct usb_request描述,functon驱动会将数据封装成usb_request的形式,然后发给udc驱动,udc驱动再将其转换为trb,最后将trb传给USB控制器端点,端点会自动处理。该结构体是一个通用的数据结构,底层驱动一般不直接使用,而是将其嵌入到另外一个结构体中。buf存放需要传输的数据,length保存数据的长度,dma保存buf的物理地址,sg是聚合DMA传输表项的地址,num_sgs表示有多少个scatterlistcomplete是该usb_request传输完成后的回调函数,不能睡眠,由dwc3控制器的下半部分(中断线程)调用contextcomplete回调函数的参数,status表示此次传输的结果,0表示传输完成,负数表示传输失败,-ESHUTDOWN错误码表示此次传输失败的原因是设备断开连接或者驱动关闭了端点,actual表示传输的字节数。
struct dwc3_request是dwc3控制器设备驱动描述I/O请求的数据结构,内部嵌入了通用I/O请求的数据结构usb_request

[include/linux/usb/gadget.h]
struct usb_request {        // 用于描述一个I/O请求
	void			*buf;   // 发送或接收数据的缓冲区
	unsigned		length; // 缓冲区数据长度
	dma_addr_t		dma;    // buf的物理地址
	struct scatterlist	*sg; // a scatterlist for SG-capable controllers
	unsigned		num_sgs; // number of SG entries
	unsigned		num_mapped_sgs; // number of SG entries mapped to DMA
	// The stream id, when USB3.0 bulk streams are being used
	unsigned		stream_id:16;
	/* If true, hints that no completion irq is needed.
       Helpful sometimes with deep request queues that are handled
	   directly by DMA controllers. */
	unsigned		no_interrupt:1;
	/* If true, when writing data, makes the last packet be "short"
		by adding a zero length packet as needed; */
	unsigned		zero:1;
	/* When reading data, makes short packets be
	   treated as errors (queue stops advancing till cleanup). */
	unsigned		short_not_ok:1;
	/* Function called when request completes, so this request and
	   its buffer may be re-used.  The function will always be called with
       interrupts disabled, and it must not sleep.
       Reads terminate with a short packet, or when the buffer fills,
       whichever comes first.  When writes terminate, some data bytes
       will usually still be in flight (often in a hardware fifo).
       Errors (for reads or writes) stop the queue from advancing
       until the completion function returns, so that any transfers
	   invalidated by the error may first be dequeued. */
	void	(*complete)(struct usb_ep *ep, struct usb_request *req);
	void	*context;          // complete回调函数的参数
	struct list_head	list;  // For use by the gadget driver.
	/* Reports completion code, zero or a negative errno.
       Normally, faults block the transfer queue from advancing until
	   the completion callback returns.
       Code "-ESHUTDOWN" indicates completion caused by device disconnect,
       or when the driver disabled the endpoint. */
	int			status;
	/* Reports bytes transferred to/from the buffer.  For reads (OUT
       transfers) this may be less than the requested length.  If the
       short_not_ok flag is set, short reads are treated as errors
       even when status otherwise indicates successful completion.
       Note that for writes (IN transfers) some data bytes may still
       reside in a device-side FIFO when the request is reported as
	   complete. */
	unsigned		actual;
};
[drivers/usb/dwc3/core.h]
struct dwc3_request {  // 描述dwc3控制器的一次I/O传输
	struct usb_request	request;  // 通用的I/O请求
	struct list_head	list;     // 请求队列链表
	struct dwc3_ep		*dep;     // 该请求所属的端点
	u8			first_trb_index;  // index to first trb used by this request
	u8			epnum;            // 该请求对应的端点编号
	struct dwc3_trb		*trb;     // 所属trb的地址
	dma_addr_t		trb_dma;      // 所属trb的DMA地址
	unsigned		direction:1;  // IN or OUT direction flag
	unsigned		mapped:1;     // true when request has been dma-mapped
	unsigned		started:1;    // true when request has been queued to HW
};

2.3.TRB

TRB(transfer request block)传输请求块是一种硬件格式,由端点硬件自动处理。bplbph是分别是64位缓冲区DMA地址的低32位和高32位,size是缓冲区的长度,占23位,其余为控制位。dwc3控制器设备驱动会将dwc3_requestdwc3_trb进行绑定,并设置TRB中各个位,然后将TRB的DMA地址写到控制器中,最后使能传输,控制器会自动的将TRB传输到端点中,然后将TRB指定缓冲区中的数据发送出去。

[drivers/usb/dwc3/core.h]
struct dwc3_trb {
	u32		bpl;  // 缓冲区低32地址 DW0-3
	u32		bph;  // 缓冲区高32地址 DW4-7
	u32		size; // 缓冲区长度[23:0] DW8-B
	u32		ctrl; // 控制位 DWC-F
} __packed;

TRB的详细位域如下图所示,总共16字节。蓝色区域软件设置,绿色区域软件设置,硬件更新。详细信息参考下表。

trb.png

位域全称说明硬件如何访问
BPTRLBuffer Pointer Low64位缓冲区DMA地址的低32位R_W
BPTRHBuffer Pointer High64位缓冲区DMA地址的高32位R_W
BUFSIZBuffer Size缓冲区的大小,范围0-(16 MB - 1 byte),传输完成后硬件会递减此区域R_W
PCM1Packet Count M1USB2.0等时传输的输入端点,支持一个微帧传输多个数据包,该区域就是设置一个微帧传输几包数据,在准备第一个trb时需要设置,USB2.0 125微妙内最多可以传输3包数据R_W
TRBSTSTRB Status传输状态,由硬件设置
0-成功
1-MissedIsoc
2-SetupPending
4-TransferInProgress
4-ZLP_PENDING
R_W
HWOHardware Owner of Descriptor软件准备TRB时设置为1,表示该trb属于控制器,在该位由硬件清0之前,软件不能修改此trbR_W
LSTLast TRB标识最后一个TRB, 通常一个TRB并不能传输完所有数据, 比如超长的配置描述符,此时需要将TRB组织成链表方式, 将CHN位置成1, 最后一个TRB需要将CHN置成0, LST置成1R
CHNChain Buffers通常一个TRB并不能传输完所有数据,需要将CHN设置为1,将TRB组织成链表形式,控制器会将这些TRB看成一个事务进行传输,最后一个TRB需要将CHN置成0, LST置成1R
TRBCTLTRB Control指出TRB的类型
1-Control-Data-2+/Bulk/Interrupt
2-Control-Setup
3-Control-Status-2
4-Control-Status-3
5-Control-Data
6-Isochronous-First
7-Isochronous
8-Link TRB,Normal-ZLP (Bulk-IN)
R
ISP/IMIInterrupt on Short Packet / Interrupt on Missed ISOCISP-当输出端点收到一个short packet,同时CSP=1且LST=0,则控制器产生XferInProgress事件,IMI-对于ISOC端点,如果该位置为1, 且ISOC传输的时间过期,则控制器会产生一个XferInProgress的事件。(ISOC传输注重实时性, 对时间要求比较敏感。设备端ep_queue一包数据时都需要带一个预期发送数据的微帧号, 如果微帧号过期了, 即当前微帧号大于给定的微帧号, 则会发生Missed Isoc事件, 同时将该数据包丢弃)R
IOCInterrupt on Complete当IOC=1时,一但TRB中的数据完成传输后控制器会产生一个XferInProgress?XferComplete?事件R

参考资料

  1. Rockchip RK3399TRM V1.3 Part1
  2. Rockchip RK3399TRM V1.3 Part2
  3. Linux内核4.4.179版本源码
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐