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设备控制器硬件。
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_init
和dwc3_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
。
初始化完成后的数据结构如下图所示。最重要的还是跟端点相关的内容。端点0用于控制器传输,如设备枚举,响应主机发送的setup等请求,资源需要提前分配好。端点0与其他端点有本质的区别,因此其操作函数都是特有的。其他端点主要用于传输数据,操作函数共用。
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_gadget
的ep_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_list
和started_list
用于存放I/O请求数据结构struct usb_request
,前者存放pending的I/O请求,暂时还不能处理,后者存放已经开始处理的I/O请求。trb_pool
是一个trb组成的数组,由硬件自动处理,里面存放传输缓冲区的地址、长度及标志,非端点0分配256个trb,trb_pool_dma
保存trb_pool
的物理地址。trb_enqueue
和trb_dequeue
是trb_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_request
,free_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
表示有多少个scatterlist
,complete
是该usb_request
传输完成后的回调函数,不能睡眠,由dwc3控制器的下半部分(中断线程)调用,context
是complete
回调函数的参数,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)传输请求块是一种硬件格式,由端点硬件自动处理。bpl
和bph
是分别是64位缓冲区DMA地址的低32位和高32位,size
是缓冲区的长度,占23位,其余为控制位。dwc3控制器设备驱动会将dwc3_request
和dwc3_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字节。蓝色区域软件设置,绿色区域软件设置,硬件更新。详细信息参考下表。
位域 | 全称 | 说明 | 硬件如何访问 |
---|---|---|---|
BPTRL | Buffer Pointer Low | 64位缓冲区DMA地址的低32位 | R_W |
BPTRH | Buffer Pointer High | 64位缓冲区DMA地址的高32位 | R_W |
BUFSIZ | Buffer Size | 缓冲区的大小,范围0-(16 MB - 1 byte),传输完成后硬件会递减此区域 | R_W |
PCM1 | Packet Count M1 | USB2.0等时传输的输入端点,支持一个微帧传输多个数据包,该区域就是设置一个微帧传输几包数据,在准备第一个trb时需要设置,USB2.0 125微妙内最多可以传输3包数据 | R_W |
TRBSTS | TRB Status | 传输状态,由硬件设置 0-成功 1-MissedIsoc 2-SetupPending 4-TransferInProgress 4-ZLP_PENDING | R_W |
HWO | Hardware Owner of Descriptor | 软件准备TRB时设置为1,表示该trb属于控制器,在该位由硬件清0之前,软件不能修改此trb | R_W |
LST | Last TRB | 标识最后一个TRB, 通常一个TRB并不能传输完所有数据, 比如超长的配置描述符,此时需要将TRB组织成链表方式, 将CHN位置成1, 最后一个TRB需要将CHN置成0, LST置成1 | R |
CHN | Chain Buffers | 通常一个TRB并不能传输完所有数据,需要将CHN设置为1,将TRB组织成链表形式,控制器会将这些TRB看成一个事务进行传输,最后一个TRB需要将CHN置成0, LST置成1 | R |
TRBCTL | TRB 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/IMI | Interrupt on Short Packet / Interrupt on Missed ISOC | ISP-当输出端点收到一个short packet,同时CSP=1且LST=0,则控制器产生XferInProgress事件,IMI-对于ISOC端点,如果该位置为1, 且ISOC传输的时间过期,则控制器会产生一个XferInProgress的事件。(ISOC传输注重实时性, 对时间要求比较敏感。设备端ep_queue一包数据时都需要带一个预期发送数据的微帧号, 如果微帧号过期了, 即当前微帧号大于给定的微帧号, 则会发生Missed Isoc事件, 同时将该数据包丢弃) | R |
IOC | Interrupt on Complete | 当IOC=1时,一但TRB中的数据完成传输后控制器会产生一个XferInProgress?XferComplete?事件 | R |
参考资料
- Rockchip RK3399TRM V1.3 Part1
- Rockchip RK3399TRM V1.3 Part2
- Linux内核4.4.179版本源码