Android USB之复合设备(gadget)详解

Android USB之复合设备(gadget)详解

一 .USB gadget driver

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

USB设备控制器(UDC)驱动的框图如下图所示:

Linux USB Gadget分三层架构,层次关系从上到下:

一层:USB Gadget功能层

BSP/Driver开发者通常是要实现这一层,从而实现一个具体的设备驱动,负责驱动硬件工作,和具体的USB设备控制器硬件相关,dwc3的gadget driver驱动在drivers/usb/dwc3/gadget.c文件中实现。如Anddroid在此层实现了adb,mtp,mass_storage等。浏览参考关注此层代码时,会发现“composite”是此层的关键字,此层中关键的数据结构是:struct usb_composite_driver。这一层的驱动文件一般为:driver/usb/gadget/android.c(android实现的)或driver/usb/gadget/serial.c(传统Linux实现的USB转串口)。

二层:USB设备层

这一层是Linux内核开发维护者实现的,与我们没太大关系,不用我们操心,我们只关心其的一些接口就行。浏览参考关注此层时,会发现“gadget”是此层的关键字,此层的关键数据结构是:usb_gadget_driver,usb_composite_dev。这层主要的一个驱动文件为:driver/usb/gadget/composite.c

三层:USB设备控制器驱动层

这一层主要是与CPU、CPU USB控制器有关,与硬件紧密相关,这一层也比较头痛,主要它和USB控制器牵扯在一起,涉及有寄存器、时钟、DMA等等。但是这一层往往是由芯片厂商去实现。我们一般仅需在板级文件中处理好所需要的USB接口即可。这层的关键字就是“UDC”,主要驱动文件命名含“udc”关键字,一般与CPU或芯片厂商有关,如driver/usb/gadget/xxx_udc.c。在drivers/usb/gadget/udc/core.c文件中实现,该层是一个兼容层,将USB Function驱动和具体的USB gadget驱动隔离开,抽象了统一的接口和数据结构,向USB Function驱动提供了统一且稳定的接口,同时完成USB Function驱动和USB gadget驱动的匹配。

可以用一句简单的话去概括三层的关系:USB Gadget功能层调用USB设备层的接口,USB设备层调用USB设备控制器驱动层的接口,然后USB设备控制器驱动层回调USB设备层,USB设备层回调USB Gadget功能层。

内核2.6.32.2版本软件框架:

在设备树中,设置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与其他端点有本质的区别,因此其操作函数都是特有的。其他端点主要用于传输数据,操作函数共用。

在设备树里面,将dwc3 USB控制器配置成peripheral模式,系统启动的时候会将USB控制器设置为设备模式,并初始化gadget相关资源,若配置成了otg模式,则只会初始化gadget相关资源,不会将dwc3控制器切换为设备模式,此时dwc3控制器处于otg模式,需要切换为设备模式(只有处于otg模式才可以切换为主机或设备)。

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);

};

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

};

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字节。蓝色区域软件设置,绿色区域软件设置,硬件更新。详细信息参考下表:

二.USB gadget configfs

configfs是基于ram的文件系统,与sysfs的功能有所不同。sysfs是基于文件系统的kernel对象视图,虽然某些属性允许用户读写,但对象是在kernel中创建、注册、销毁,由kernel控制其生命周期。而configfs是一个基于文件系统的内核对象管理器(或称为config_items),config_items在用户空间通过mkdir显式的创建,使用rmdir销毁,在mkdir之后会出现对应的属性,可以在用户空间对这些属性进行读写,与sysfs不同的是,这些对象的生命周期完全由用户空间控制,kernel只需要响应用户空间的操作即可。configfs和sysfs两者可以共存,但不能相互取代。早期的USB只支持单一的gadget设备,使用场景较为简单,随后加入了composite framework,用来支持多个function的gadget设备,多个function的绑定在内核中完成,若需要修改,则需要修改内核,不灵活也不方便。Linux3.11版本引入了基于configfs的usb gadget configfs。usb gadget configfs重新实现了复合设备层,使用者可以在用户空间配置和组合内核的function,灵活的构成USB复合设备,极大了提高了工作效率。

1.初始化

usb gadget configfs模块的初始化函数为gadget_cfs_init。该函数调用后,会向configfs注册一个子系统,子系统使用configfs_subsystem结构体描述。子系统中又可分为组,使用config_group描述,组内又有成员,使用config_item描述。usb gadget configfs就是configfs子系统中的一个成员,成员的名称为"usb_gadget",成员的类型使用config_item_type描述,成员类型中包含了初始化函数gadgets_ops。因此usb gadget configfs子系统最终通过调用gadgets_make进行初始化。当加载libcomposite.ko模块后,会在/sys/kernel/config/目录下生成一个usb_gadget目录。

[drivers/usb/gadget/configfs.c]

static struct configfs_group_operations gadgets_ops = {

.make_group = &gadgets_make,

.drop_item = &gadgets_drop,

};

static struct config_item_type gadgets_type = {

.ct_group_ops = &gadgets_ops,

.ct_owner = THIS_MODULE,

};

static struct configfs_subsystem gadget_subsys = {

.su_group = {

.cg_item = {

.ci_namebuf = "usb_gadget",

.ci_type = &gadgets_type,

},

},

.su_mutex = __MUTEX_INITIALIZER(gadget_subsys.su_mutex),

};

static int __init gadget_cfs_init(void)

{

int ret;

config_group_init(&gadget_subsys.su_group);

ret = configfs_register_subsystem(&gadget_subsys);

#ifdef CONFIG_USB_CONFIGFS_UEVENT

android_class = class_create(THIS_MODULE, "android_usb");

if (IS_ERR(android_class))

return PTR_ERR(android_class);

#endif

return ret;

}

module_init(gadget_cfs_init);

gadgets_make函数的主要工作内容是设置复合设备数据结构usb_composite_dev和复合设备驱动数据结构usb_composite_driver。

工作流程如下:

分配gadget_info结构体,gi->group.default_groups是一个二级指针,第一级指向了gi->default_groups,gi->default_groups是一个指针数组,保存了functions_group、configs_group、strings_group、os_desc_group的地址。这样就可以通过``gi->group找到所有的config_group。

初始化functions_group、configs_group、strings_group、os_desc_group,其config_item_type分别指向functions_type、config_desc_type、gadget_strings_strings_type、os_desc_type,gadget_strings_strings_type使用USB_CONFIG_STRINGS_LANG宏定义。

初始化复合设备驱动数据结构usb_composite_driver,使用usb gadget configfs时,使用者可以直接在用户空间绑定function驱动,不需要bind和unbind函数,因此configfs_do_nothing和configfs_do_nothing实现为空。复合设备驱动的 usb_gadget_driver指向configfs_driver_template。usb_gadget_driver是function驱动和UDC驱动沟通的桥梁,非常重要。

初始化复合设备数据结构usb_composite_dev,设置USB设备描述符。

设置gi->group的config_item_type指向gadget_root_type,usb gadget configfs初始化的时候首先调用gadget_root_type。

最后向configfs系统返回gi->group。当在/sys/kernel/config目录下创建usb_gadget时,configfs系统会调用gi->group和gi->group.default_groups保存的config_item_type。也即调用gadgets_make函数设置的gadget_root_type、functions_type、config_desc_type、gadget_strings_strings_type、os_desc_type。

[drivers/usb/gadget/configfs.c]

static struct config_group *gadgets_make(

struct config_group *group, const char *name)

{

......

/* 分配struct gadget_info结构体 */

gi = kzalloc(sizeof(*gi), GFP_KERNEL);

......

/* 设置group,gadgets_make最终返回的是gi->group */

gi->group.default_groups = gi->default_groups;

gi->group.default_groups[0] = &gi->functions_group;

gi->group.default_groups[1] = &gi->configs_group;

gi->group.default_groups[2] = &gi->strings_group;

gi->group.default_groups[3] = &gi->os_desc_group;

/* 设置functions_group,可配置function驱动的参数 */

config_group_init_type_name(&gi->functions_group, "functions",

&functions_type);

/* 设置configs_group,可配置USB设备参数 */

config_group_init_type_name(&gi->configs_group, "configs",

&config_desc_type);

/* 设置strings_group,可配置字符串参数 */

config_group_init_type_name(&gi->strings_group, "strings",

&gadget_strings_strings_type);

/* 设置os_desc_group,可配置操作系统描述符 */

config_group_init_type_name(&gi->os_desc_group, "os_desc",

&os_desc_type);

/* 初始化复合设备驱动-usb_composite_driver */

gi->composite.bind = configfs_do_nothing; // 实现为空

gi->composite.unbind = configfs_do_nothing; // 实现为空

gi->composite.suspend = NULL;

gi->composite.resume = NULL;

gi->composite.max_speed = USB_SPEED_SUPER; // 支持USB3.0

/* 初始化复合设备-usb_composite_dev */

composite_init_dev(&gi->cdev);

/* 设置复合设备描述符 */

gi->cdev.desc.bLength = USB_DT_DEVICE_SIZE;

gi->cdev.desc.bDescriptorType = USB_DT_DEVICE;

gi->cdev.desc.bcdDevice = cpu_to_le16(get_default_bcdDevice());

/* 设置configfs的usb_gadget_driver */

gi->composite.gadget_driver = configfs_driver_template;

gi->composite.gadget_driver.function = kstrdup(name, GFP_KERNEL);

gi->composite.name = gi->composite.gadget_driver.function;

/* 设置config_group */

config_group_init_type_name(&gi->group, name,

&gadget_root_type);

/* 向configfs系统返回&gi->group */

return &gi->group;

}

/* usb gadget configfs定义的usb_gadget_driver */

static const struct usb_gadget_driver configfs_driver_template = {

.bind = configfs_composite_bind,

.unbind = configfs_composite_unbind,

#ifdef CONFIG_USB_CONFIGFS_UEVENT

.setup = android_setup,

.reset = android_disconnect,

.disconnect = android_disconnect,

#else

.setup = composite_setup,

.reset = composite_disconnect,

.disconnect = composite_disconnect,

#endif

.suspend = composite_suspend,

.resume = composite_resume,

.max_speed = USB_SPEED_SUPER,

.driver = {

.owner = THIS_MODULE,

.name = "configfs-gadget",

},

};

下面是usb gadget configfs定义的几种config_item_type和configfs_group_operations。当在/sys/kernel/config/usb_gadget/目录下实例化一个新的gadget实例(g1)时,首先调用gadget_root_type,在g1目录下生成bDeviceClass、bDeviceSubClass、bDeviceProtocol、bMaxPacketSize0、idVendor、idProduct、bcdDevice、bcdUSB、UDC属性文件,使用者可以在用户空间进行配置。接着调用functions_type,在g1目录下生成functions目录,绑定function驱动后,会在该目录下导出function驱动的属性文件,供使用者修改;然后调用config_desc_type,在g1目录下生成configs目录;随后调用gadget_strings_strings_type,在g1目录下生成strings目录,包含了使用字符串表示的英语ID,开发商、产品和序列号等信息。最后调用os_desc_type,在g1目录下生成os_desc目录,包含了操作系统信息,一般不需要设置。如果你的SOC上有多个gadget,可以创建多个gx目录。

[drivers/usb/gadget/configfs.c]

static struct configfs_attribute *gadget_root_attrs[] = {

&gadget_dev_desc_attr_bDeviceClass,

&gadget_dev_desc_attr_bDeviceSubClass,

&gadget_dev_desc_attr_bDeviceProtocol,

&gadget_dev_desc_attr_bMaxPacketSize0,

&gadget_dev_desc_attr_idVendor,

&gadget_dev_desc_attr_idProduct,

&gadget_dev_desc_attr_bcdDevice,

&gadget_dev_desc_attr_bcdUSB,

&gadget_dev_desc_attr_UDC,

NULL,

};

static struct config_item_type gadget_root_type = {

.ct_item_ops = &gadget_root_item_ops,

.ct_attrs = gadget_root_attrs,

.ct_owner = THIS_MODULE,

};

static struct configfs_group_operations functions_ops = {

.make_group = &function_make,

.drop_item = &function_drop,

};

static struct config_item_type functions_type = {

.ct_group_ops = &functions_ops,

.ct_owner = THIS_MODULE,

};

static struct configfs_group_operations config_desc_ops = {

.make_group = &config_desc_make,

.drop_item = &config_desc_drop,

};

static struct config_item_type config_desc_type = {

.ct_group_ops = &config_desc_ops,

.ct_owner = THIS_MODULE,

};

/* 定义gadget_strings_strings_type的宏 */

USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);

[include/linux/usb/gadget_configfs.h]

#define USB_CONFIG_STRINGS_LANG(struct_in, struct_member) \

...... \

static struct configfs_group_operations struct_in##_strings_ops = { \

.make_group = &struct_in##_strings_make, \

.drop_item = &struct_in##_strings_drop, \

}; \

\

static struct config_item_type struct_in##_strings_type = { \

.ct_group_ops = &struct_in##_strings_ops, \

.ct_owner = THIS_MODULE, \

}

static struct configfs_item_operations os_desc_ops = {

.release = os_desc_attr_release,

.allow_link = os_desc_link,

.drop_link = os_desc_unlink,

};

static struct config_item_type os_desc_type = {

.ct_item_ops = &os_desc_ops,

.ct_attrs = os_desc_attrs,

.ct_owner = THIS_MODULE,

};

查看创建的目录信息:

console:/ $ ls config/usb_gadget/g1/ -ltotal 0-rw-r--r-- 1 root root 4096 2022-10-22 14:22 UDC-rw-r--r-- 1 root root 4096 2022-10-22 14:22 bDeviceClass-rw-r--r-- 1 root root 4096 2022-10-22 14:22 bDeviceProtocol-rw-r--r-- 1 root root 4096 2022-10-22 14:22 bDeviceSubClass-rw-r--r-- 1 root root 4096 2022-10-22 15:27 bMaxPacketSize0-rw-r--r-- 1 root root 4096 2022-10-22 14:18 bcdDevice-rw-r--r-- 1 root root 4096 2022-10-22 14:18 bcdUSBdrwxr-xr-x 3 root root 0 2022-10-22 14:18 configsdrwxr-xr-x 12 root root 0 2022-10-22 14:18 functions-rw-r--r-- 1 root root 4096 2022-10-22 14:22 idProduct-rw-r--r-- 1 root root 4096 2022-10-22 14:22 idVendor-rw-r--r-- 1 root root 4096 2022-10-22 15:27 max_speeddrwxr-xr-x 2 root root 0 2022-10-22 14:22 os_descdrwxr-xr-x 3 root root 0 2022-10-22 14:18 strings

console:/ # ls config/usb_gadget/g1/functions/ -ltotal 0drwxr-xr-x 2 root root 0 2022-09-02 05:50 accessory.gs2drwxr-xr-x 2 root root 0 2022-09-02 05:50 audio_source.gs3drwxr-xr-x 2 root root 0 2022-09-02 05:50 ffs.adbdrwxr-xr-x 2 root root 0 2022-09-02 05:50 ffs.mtpdrwxr-xr-x 2 root root 0 2022-09-02 05:50 ffs.ptpdrwxr-xr-x 2 root root 0 2022-09-02 05:50 midi.gs5drwxr-xr-x 3 root root 0 2022-09-02 05:50 rndis.gs4drwxr-xr-x 4 root root 0 2022-09-02 05:50 uvc.gs6

2.主要调用流程分析

下面结合uac2.0用户空间配置脚本和内核中的USB gadget configfs代码,分析一下当用户空间配置时,内核中做了什么。重点关注驱动相关的执行流程。

(1)创建配置

当在用户空间执行mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/configs/c.1命令时(需要先挂载上目录:mount -t configfs none /sys/kernel/config),会调用到configfs的config_desc_make函数。

主要的工作流程为:

分配一个config_usb_cfg1结构体,该结构体包含了usb_configuration结构体,保存了该USB设备的配置信息。

设置USB设备的配置描述符的某些选项。如bConfigurationValue(根据创建配置目录的名称设置,如c.1,则设置为1)、MaxPower、bmAttributes。

向用户空间导出该配置属性文件,便于使用者设置。

调用usb_add_config_only函数将该配置挂到usb_composite_dev的configs链表。

(2)创建function实例

前面提到过,function驱动有两个重要的数据结构usb_function_instance和usb_function。下面是创建usb_function_instance的过程。如下图所示,当用户空间执行mkdir /sys/kernel/config/usb_gadget/g1/functions/uac2.0时,内核会调用USB gadget configfs的function_make函数,function_make函数的主要工作是获取function驱动的usb_function_instance数据结构,执行流程如下:

调用gadget funcation API usb_get_function_instance函数,遍历func_list链表,根据名称进行匹配。用户空间输入的是uac2,则匹配uac2驱动。其定义在f_uac2.c文件中。

匹配成功回调function驱动定义的alloc_inst函数,对于uac2,则回调afunc_alloc_inst函数。2.1首先分配f_uac2_opts数据结构,内部包含了usb_function_instance。2.2设置uac2的config_item_type为f_uac2_func_type,f_uac2_func_type会在/sys/kernel/config/usb_gadget/g1/functions/uac2.0目录下导出设置uac2音频参数的属性文件,便于用户空间设置。2.3设置音频参数的默认值,使用者可在用户空间修改。

将获取到的uac2的usb_function_instance挂到gadget_info的available_func链表。

(3)获取function实例

下面是获取usb_function的过程。如下图所示,当用户空间执行ln -s /sys/kernel/config/usb_gadget/g1/functions/uac2.0 /sys/kernel/config/usb_gadget/g1/configs/c.1时,内核会调用USB gadget configfs的config_usb_cfg_link函数。config_usb_cfg_link函数的主要工作是获取function驱动的usb_function数据结构,执行流程如下:

通过gadget funcation API,调用uac2驱动的afunc_alloc函数。1.1分配f_uac2数据结构,内部包含了usb_function。1.2填充usb_function数据结构,重要的是设置的回调函数,usb_gadget_driver在适当的时候会回调这些函数。如驱动绑定的时候会回调afunc_bind函数,解除绑定的时候会回调afunc_unbind函数,设置配置的时候会回调afunc_set_alt函数等等。

将获取到的uac2的usb_function挂到config_usb_cfg的func_list链表。

(4)驱动绑定

如下图所示,当用户空间执行echo fe800000.dwc3 > /sys/kernel/config/usb_gadget/g1/UDC时(注:不同平台的USB设备控制器命名不一样,如RK3588为fc000000.usb),内核会调用USB gadget configfs的gadget_dev_desc_UDC_store函数。gadget_dev_desc_UDC_store函数的主要工作是将usb_gadget_driver和底层的USB控制器绑定,usb_gadget_driver相当于一个桥梁,桥的两端分别是function驱动和UDC驱动。

执行流程如下:

判断输入的USB控制器名称,若输出为空或者是none,则解除usb_gadget_driver和底层USB控制器的绑定。反之调用usb_udc_attach_driver函数进行匹配USB设备控制器。

遍历udc_list链表,查找fe800000.dwc3 USB设备控制器。

找到对应的USB设备控制器,则保存绑定的usb_gadget_driver,即configfs_driver_template。

回调configfs_composite_bind函数。其主要的工作内容如下:4.1 分配端点的usb_request、分配缓冲区、设置usb_request的回调函数、复位所有端点,并将gadget的端点数量清零。4.2 function驱动保存对应的配置,并回调function驱动的bind函数,对于uac2则回调 afunc_bind 函数。4.3 如果使用os_string,则需要分配os_string requset。

调用UDC驱动接口usb_gadget_udc_start使能USB设备控制器。

调用UDC驱动接口usb_udc_connect_control连接USB主机控制器,这样USB主机就能识别并枚举USB设备。

以上以uac2为例,介绍了USB gadget configfs用户空间的使用方法及内核中的工作流程。USB gadget configfs提供了一个便捷的配置方法,用户可以灵活的组织USB function驱动,以组成不同功能的USB设备复合设备,当配置完成后,USB gadget configfs并不参与USB设备复合设备的工作过程。usb_gadget_driver数据结构相当于一个桥梁,连同了function驱动和UDC驱动,USB gadget configfs提供usb_gadget_driver实现为configfs_driver_template,对于legacy驱动,其实现又不同。

另外对应的alsa声卡注册驱动层代码:gadget/function/u_audio.c

(5)工作流程

USB主机发送USB_REQ_SET_INTERFACE命令时,uac2驱动将会调用afunc_set_alt函数,若intf=2,alt=1,则开始录音,若intf=1,alt=1,则开始播放。

下图是USB音频设备工作时数据流的传输过程。录音(capture)时,USB主机控制器向USB设备控制器发送音频数据,USB设备控制器收到以后通过DMA将其写入到usb_request的缓冲区中,随后再拷贝到DMA缓冲区中,用户可使用arecord、tinycap等工具从DMA缓冲区中读取音频数据,DMA缓冲区是一个FIFO,uac2驱动往里面填充数据,用户从里面读取数据。播放(playback)时,用户通过aplay、tinyplay等工具将音频数据写道DMA缓冲区中,uac2驱动从DMA缓冲区中读取数据,然后构造成usb_request,送到USB设备控制器,USB设备控制器再将音频数据发送到USB主机控制器。可以看出录音和播放的音频数据流方向相反,用户和uac2驱动构造了一个生产者和消费者模型,录音时,uac2驱动是生产者,用户是消费者,播放时则相反。

[drivers/usb/gadget/function/u_audio.c]

static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req)

{

......

if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {

/*

* For each IN packet, take the quotient of the current data

* rate and the endpoint's interval as the base packet size.

* If there is a residue from this division, add it to the

* residue accumulator.

*/

req->length = uac->p_pktsize;

uac->p_residue += uac->p_pktsize_residue;

/*

* Whenever there are more bytes in the accumulator than we

* need to add one more sample frame, increase this packet's

* size and decrease the accumulator.

*/

if (uac->p_residue / uac->p_interval >= uac->p_framesize) {

req->length += uac->p_framesize;

uac->p_residue -= uac->p_framesize * uac->p_interval;

}

req->actual = req->length;

}

......

if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {

// 录音时,将DMA缓冲区中的数据拷贝到usb_request缓冲区中

if (unlikely(pending < req->actual)) { // 处理DMA缓冲区回绕

memcpy(req->buf, runtime->dma_area + hw_ptr, pending);

memcpy(req->buf + pending, runtime->dma_area,

req->actual - pending);

} else {

memcpy(req->buf, runtime->dma_area + hw_ptr, req->actual);

}

} else {

// 播放时,将usb_request缓冲区中的数据拷贝到DMA缓冲区中

if (unlikely(pending < req->actual)) { // 处理DMA缓冲区回绕

memcpy(runtime->dma_area + hw_ptr, req->buf, pending);

memcpy(runtime->dma_area, req->buf + pending, req->actual - pending);

} else {

memcpy(runtime->dma_area + hw_ptr, req->buf, req->actual);

}

}

......

if ((hw_ptr % snd_pcm_lib_period_bytes(substream)) < req->actual)

snd_pcm_period_elapsed(substream); // 更新PCM设备信息,如DMA缓冲区状态

exit:

// 将usb_request重新填充到端点队列中,重复利用

if (usb_ep_queue(ep, req, GFP_ATOMIC))

dev_err(uac->card->dev, "%d Error!\n", __LINE__);

}

三.调试方法

以上创建好gadget uac2后,涉及驱动的工作流程及数据的传输的场景有哪些呢?比如,自己实现一个daemon获取Android设备上的麦克风数据写到gadget的uac声卡节点,而Android设备与PC通过USB数据线连接,这样就可以把Android设备作为一个声卡输入源(MIC)给到PC端使用。也可以通过tinyplay测试:

cat /proc/asound/cards //查看当前系统的声卡信息,其中Card 6 - UAC2Gadget 就是通过gadget驱动创建。

0 [rockchipdp0 ]: rockchip_dp0 - rockchip,dp0rockchip,dp01 [rockchipdp1 ]: rockchip_dp1 - rockchip,dp1rockchip,dp12 [rockchiphdmi1 ]: rockchip-hdmi1 - rockchip-hdmi1rockchip-hdmi13 [rockchiphdmi0 ]: rockchip-hdmi0 - rockchip-hdmi0rockchip-hdmi04 [rockchiphdmiin ]: rockchip_hdmiin - rockchip,hdmiinrockchip,hdmiin5 [rockchipes8388 ]: rockchip-es8388 - rockchip-es8388rockchip-es83886 [UAC2Gadget ]: UAC2_Gadget - UAC2_GadgetUAC2_Gadget 0

tinyplay /storage/6C97-73BB/media/16k-mono-test.wav -D 6 -d 0 //通过tinyplay向gadget uac声卡写入数据,PC端就可以录制到。

注:如果音频数据来源是通过AAudio录得,注意后台程序录音AudioPolicy会将数据静音,具体可以看以下Android12的注释:

void AudioPolicyService::updateUidStates_l()

{

// Go over all active clients and allow capture (does not force silence) in the

// following cases:

// The client is the assistant

// AND an accessibility service is on TOP or a RTT call is active

// AND the source is VOICE_RECOGNITION or HOTWORD

// OR uses VOICE_RECOGNITION AND is on TOP

// OR uses HOTWORD

// AND there is no active privacy sensitive capture or call

// OR client has CAPTURE_AUDIO_OUTPUT privileged permission

// OR The client is an accessibility service

// AND Is on TOP

// AND the source is VOICE_RECOGNITION or HOTWORD

// OR The assistant is not on TOP

// AND there is no active privacy sensitive capture or call

// OR client has CAPTURE_AUDIO_OUTPUT privileged permission

// AND is on TOP

// AND the source is VOICE_RECOGNITION or HOTWORD

// OR the client source is virtual (remote submix, call audio TX or RX...)

// OR the client source is HOTWORD

// AND is on TOP

// OR all active clients are using HOTWORD source

// AND no call is active

// OR client has CAPTURE_AUDIO_OUTPUT privileged permission

// OR the client is the current InputMethodService

// AND a RTT call is active AND the source is VOICE_RECOGNITION

// OR Any client

// AND The assistant is not on TOP

// AND is on TOP or latest started

// AND there is no active privacy sensitive capture or call

// OR client has CAPTURE_AUDIO_OUTPUT privileged permission

......

一种解决办法是把所需的Audio source添加到virtual source 列表:

@@ -908,6 +910,7 @@ app_state_t AudioPolicyService::apmStatFromAmState(int amState) {

/* static */

bool AudioPolicyService::isVirtualSource(audio_source_t source)

{switch (source) {

case AUDIO_SOURCE_VOICE_UPLINK:

case AUDIO_SOURCE_VOICE_DOWNLINK:

@@ -915,6 +918,7 @@ bool AudioPolicyService::isVirtualSource(audio_source_t source)

case AUDIO_SOURCE_REMOTE_SUBMIX:

case AUDIO_SOURCE_FM_TUNER:

case AUDIO_SOURCE_ECHO_REFERENCE:

+ case AUDIO_SOURCE_VOICE_COMMUNICATION:

return true;

default:

break;

除了uac2,gadget支持的设备类型还有 uvc/rndis/mass_storage/hid/acm...等等,而且可以同时配置多个组合在一起使用,组合数量跟平台的usb ep数量,驱动fifo大小有关,传输速率取决于usb的硬件版本。以下示例几个手动创建gadget设备的指令操作(RK3588平台):

//- UAC

su

mount -t configfs none /sys/kernel/config

echo 1 > /sys/kernel/config/usb_gadget/g1/os_desc/use

echo "USB Audio Device" > /sys/kernel/config/usb_gadget/g1/strings/0x409/product

echo uac2 > /sys/kernel/config/usb_gadget/g1/configs/b.1/strings/0x409/configuration

echo 1 > /sys/kernel/config/usb_gadget/g1/functions/uac2.gs0/p_chmask

echo 16000 > /sys/kernel/config/usb_gadget/g1/functions/uac2.gs0/p_srate

echo 2 > /sys/kernel/config/usb_gadget/g1/functions/uac2.gs0/p_ssize

echo 0 > /sys/kernel/config/usb_gadget/g1/functions/uac2.gs0/c_chmask

rm /sys/kernel/config/usb_gadget/g1/configs/b.1/f1

rm /sys/kernel/config/usb_gadget/g1/configs/b.1/f2

ln -s /sys/kernel/config/usb_gadget/g1/functions/uac2.gs0 /sys/kernel/config/usb_gadget/g1/configs/b.1/f1

ln -s /sys/kernel/config/usb_gadget/g1/functions/ffs.adb /sys/kernel/config/usb_gadget/g1/configs/b.1/f2

echo none > /sys/kernel/config/usb_gadget/g1/UDC

echo "fc000000.usb" > /sys/kernel/config/usb_gadget/g1/UDC

//--- UVC

su

mount -t configfs none /sys/kernel/config

echo 1 > /sys/kernel/config/usb_gadget/g1/os_desc/use

echo uvc > /sys/kernel/config/usb_gadget/g1/configs/b.1/strings/0x409/configuration

mkdir /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/control/header/h

ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/control/header/h /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/control/class/fs/h

ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/control/header/h /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/control/class/ss/h

mkdir /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/mjpeg/m

mkdir /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/mjpeg/m/1080p

rm /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/header/h/m

echo 1920 > /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/mjpeg/m/1080p/wWidth

echo 1080 > /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/mjpeg/m/1080p/wHeight

echo 3315000 > /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/mjpeg/m/1080p/dwMinBitRate

echo 20000000 > /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/mjpeg/m/1080p/dwMaxBitRate

echo 8294400 > /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/mjpeg/m/1080p/dwMaxVideoFrameBufferSize

echo 333333 > /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/mjpeg/m/1080p/dwFrameInterval

ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/mjpeg/m /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/header/h/m

mkdir /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/header/h

ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/header/h /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/class/fs/h

ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/header/h /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/class/hs/h

ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/header/h /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming/class/ss/h

echo 1024 > /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming_maxpacket //一个微帧传1K数据,可以配置最大3K

stop adbd

rm /sys/kernel/config/usb_gadget/g1/configs/b.1/f1

ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6 /sys/kernel/config/usb_gadget/g1/configs/b.1/f1

rm /sys/kernel/config/usb_gadget/g1/configs/b.1/f2

ln -s /sys/kernel/config/usb_gadget/g1/functions/ffs.adb /sys/kernel/config/usb_gadget/g1/configs/b.1/f2

echo "fc000000.usb" > /sys/kernel/config/usb_gadget/g1/UDC

setprop sys.usb.state uvc

// --- RNDIS

su

mount -t configfs none /sys/kernel/config

cd /sys/kernel/config/usb_gadget/g1

rm -r functions/rndis.gs4

mkdir functions/rndis.gs4

echo 0xe0 > functions/rndis.gs4/class

echo "72:ed:3e:86:de:e6" > functions/rndis.gs4/host_addr

echo 5 > functions/rndis.gs4/qmult

echo "86:f6:b9:72:04:40" > functions/rndis.gs4/dev_addr

echo 03 > functions/rndis.gs4/protocol

echo 01 > functions/rndis.gs4/subclass

rm configs/b.1/f1

ln -s functions/rndis.gs4 configs/b.1/f1

echo "none" > /sys/kernel/config/usb_gadget/g1/UDC

echo "fc000000.usb" > /sys/kernel/config/usb_gadget/g1/UDC

// --- mass storage

su

mount -t configfs none /sys/kernel/config

mkdir /config/usb_gadget/g1/functions/mass_storage.ds0

cd /sys/kernel/config/usb_gadget/g1

echo "0x2207" > idVendor

echo "0x0000" > idProduct

echo "E0D55E6C0EBCF61198AA00CA" > strings/0x409/serialnumber

echo 'mass_storage' > configs/b.1/strings/0x409/configuration

//制作vfat格式镜像挂载使用,因为windows不识别android默认挂载的系统格式

dd bs=1M count=160 if=/dev/zero of=/sdcard/lun0.img

busybox mkfs.vfat /sdcard/lun0.img

echo /sdcard/lun0.img > functions/mass_storage.ds0/lun.0/file

//但android busybox一般没有自带mkfs.vfat,可以网上找个做好的vfat img用

echo /sdcard/userfs_vfat.img > functions/mass_storage.ds0/lun.0/file

//或者直接使用外接的U盘:

echo '/dev/block/sda1' > functions/mass_storage.ds0/lun.0/file

ls /sys/class/udc/ -l //查看当前系统注册了哪些UDC驱动,这是RK3588平台,有两个UDC驱动。

lrwxrwxrwx 1 root root 0 2022-09-02 07:47 fc000000.usb -> ../../devices/platform/usbdrd3_0/fc000000.usb/udc/fc000000.usblrwxrwxrwx 1 root root 0 2022-09-02 07:47 fc400000.usb -> ../../devices/platform/usbdrd3_1/fc400000.usb/udc/fc400000.usb

cat /proc/devices //查看系统设备跟usb相关的有这两个:180 usb189 usb_device

ls /sys/bus/usb/devices/ //有6条usb总线

1-0:1.0 2-1.1:1.0 3-0:1.0 4-1.1:1.0 4-1.3:1.0 6-1 usb32-0:1.0 2-1.2 4-0:1.0 4-1.2 4-1:1.0 6-1:1.0 usb42-1 2-1.2:1.0 4-1 4-1.2:1.0 5-0:1.0 usb1 usb52-1.1 2-1:1.0 4-1.1 4-1.3 6-0:1.0 usb2 usb6

每插入一个usb设备,/sys/bus/usb/devices/就有新的文件夹生成。其中“2-1:1.0”表明usb总线号为2,devpath为1,配置号为1,接口号为0,即2号总线的1号端口的设备,使用的是1号配置,接口号为0。cat /sys/bus/usb/devices/2-1\:1.0/bInterfaceClass <09其中09表明Hub,又如,08表明mass storage等即2-1:1.0是一个Hub,那么它下面的1号端口的设备就是的上面的“2-1.1:1.0”

参考资料链接:

https://www.kernel.org/doc/html/v5.3/usb/gadget_configfs.htmlhttps://www.codeleading.com/article/19436237396/ (usb gadget 驱动之 configfs)https://www.codeleading.com/article/31242270244/ (USB Gadget设备端驱动架构(五):Configfs系统)https://blog.csdn.net/u011037593/article/details/123698241 (USB总线-Linux内核USB3.0设备控制器复合设备之USB gadget configfs分析(七))https://blog.csdn.net/yaozhenguo2006/article/details/7690707 (Linux USB Gadget--软件结构)

相关风暴

部落冲突人物介绍 人物资料大全
365bet足彩官网

部落冲突人物介绍 人物资料大全

🌀 08-14 🌊 阅读 2849
京东白条任务怎么过?有哪些攻略?
365bet中文网站

京东白条任务怎么过?有哪些攻略?

🌀 09-02 🌊 阅读 9460
《魔兽世界》8.3影钩工蜂坐骑获得方法
365bet足彩官网

《魔兽世界》8.3影钩工蜂坐骑获得方法

🌀 07-11 🌊 阅读 5692
您在试听手机铃声:亲爱的你在哪里
s365国网公司健步走app

您在试听手机铃声:亲爱的你在哪里

🌀 08-20 🌊 阅读 9505