Linux USB Gadget | 框架 / 复合设备实践 / Configfs 配置
注:本文为“Linux USB Gadget ”相关文章合辑。
图片清晰度受引文原图所限。
略作重排,未整理去重。
如有内容异常,请看原文
Linux USB Gadget 框架概述
2018-04-11
haoxing990
本文记录我在公司 Gadget 相关的驱动开发开发过程中的感悟。
总结下来,共开发了四个与 Gadget 相关的驱动:字符驱动、g_multi
、g_ether
和 g_zero
。
要理解 Gadget,必须先了解其框架,明确 composite
、gadget
和 udc
之间的关系,以及 USB 描述符的作用。
- 一个 USB 设备有一个设备描述符。
- 有一个或多个配置描述符。
- 每个配置描述符包含一个或多个接口(在 Gadget 端,接口正式命名为
usb_func
)。 - 每个接口包含零个或多个端点。
编写 Gadget 的关键在于理解 udc
、gadget
和 composite
之间的联系和架构层次。在实际应用中,Gadget 并不需要我们编写,需要我们自己编写的是 Composite 层,以及对 UDC 层的修改。以下是详细介绍。
1. Composite 层
Composite
的英文意思是“复合”,估计是因为编写 USB Gadget 层设备驱动时将功能整合在一起,通过统一的函数 usb_composite_register
注册。功能各异,杂七杂八,所以称为复合层。
在该层,需要注意的相关结构体和函数如下:
struct usb_composite_dev { // 作为 Composite 复合设备,所有 Composite 设备都必须实现该设备。struct usb_gadget *gadget; // 设备和 Gadget 交互,Gadget 和 UDC 交互。struct usb_request *req; // 每个设备自带一个 USB 请求,所有数据交互都通过该请求发送。struct usb_configuration *config; // 一个设备有一个或多个配置。/* private: *//* internals */unsigned int suspended:1;struct usb_device_descriptor desc; // 设备描述符,唯一struct list_head configs; // 配置struct list_head gstrings; // 字符描述struct usb_composite_driver *driver; // 设备绑定的驱动u8 next_string_id;char *def_manufacturer; // 默认制造商/* the gadget driver won't enable the data pullup* while the deactivation count is nonzero.*/unsigned deactivations;/* the composite driver won't complete the control transfer's* data/status stages till delayed_status is zero.*/int delayed_status;/* protects deactivations and delayed_status counts*/spinlock_t lock;
};
struct usb_composite_driver { // 所有 Composite 驱动必须填充该结构体。const char *name;const struct usb_device_descriptor *dev; // 必须实现struct usb_gadget_strings **strings;enum usb_device_speed max_speed;unsigned needs_serial:1;int (*bind)(struct usb_composite_dev *cdev); // 必须实现int (*unbind)(struct usb_composite_dev *); // 必须实现void (*disconnect)(struct usb_composite_dev *);/* global suspend hooks */void (*suspend)(struct usb_composite_dev *);void (*resume)(struct usb_composite_dev *);struct usb_gadget_driver gadget_driver; // 该驱动由 Composite 提供,所有与 Composite 相关的驱动都会默认分配该驱动。该驱动是
};
struct usb_gadget_driver { // 该驱动是 USB core 和 Composite 之间交互必不可少的一环,两者之间的联系主要靠它来维持,内核已经提供好了,不需要我们去实现。char *function;enum usb_device_speed max_speed;int (*bind)(struct usb_gadget *gadget,struct usb_gadget_driver *driver);void (*unbind)(struct usb_gadget *);int (*setup)(struct usb_gadget *, // 枚举过程中必不可少的函数。不需要驱动去实现。const struct usb_ctrlrequest *);void (*disconnect)(struct usb_gadget *);void (*suspend)(struct usb_gadget *);void (*resume)(struct usb_gadget *);/* FIXME support safe rmmod */struct device_driver driver;
};
static const struct usb_gadget_driver composite_driver_template = { // 所有的 Composite 设备都会在注册 Gadget 驱动时采用该实例填充。.bind = composite_bind,.unbind = composite_unbind,.setup = composite_setup,.disconnect = composite_disconnect,.suspend = composite_suspend,.resume = composite_resume,.driver = {.owner = THIS_MODULE,},
};
2. Composite 驱动的注册过程
以 zero.c
为例:
static int __init init(void)
{return usb_composite_register(&zero_driver);
}
-
usb_composite_register(&zero_driver);
driver->gadget_driver = composite_driver_template;
// 此过程并未涉及对 Composite 设备的注册操作,而是将 Composite 驱动中注册的相关信息填充到 Gadget 中,利用 Gadget 去和 UDC 打交道。return usb_gadget_probe_driver(gadget_driver);
// 该函数首先判定bind
、setup
等函数是否实现。不需要我们去实现。list_for_each_entry(udc, &udc_list, list)
// 查找注册在内核中的 UDC 实例,找到后进行下一步操作,未找到则退出,驱动注册失败。ret = udc_bind_to_driver(udc, driver);
// 将 UDC 和 Gadget 驱动绑定在一起。
-
udc_bind_to_driver(udc, driver);
ret = driver->bind(udc->gadget, driver);
// 最关键的函数是composite_bind
,将在后面介绍。ret = usb_gadget_udc_start(udc->gadget, driver);
// 此处可以理解为一切就绪,UDC 相关设置已经写入寄存器。ret = usb_gadget_connect(udc->gadget);
// 插入 Host USB 口,检查 D+ 电平的变化,即枚举过程。
-
composite_bind
// 最重要的函数,是理解 Gadget 设计的关键
static int composite_bind(struct usb_gadget *gadget, // 该函数主要是实现配置描述符接口等操作。struct usb_gadget_driver *gdriver)
{struct usb_composite_dev *cdev;struct usb_composite_driver *composite = to_cdriver(gdriver);int status = -ENOMEM;cdev = kzalloc(sizeof *cdev, GFP_KERNEL);if (!cdev)return status;spin_lock_init(&cdev->lock);cdev->gadget = gadget;set_gadget_data(gadget, cdev);INIT_LIST_HEAD(&cdev->configs);INIT_LIST_HEAD(&cdev->gstrings);status = composite_dev_prepare(composite, cdev);if (status)goto fail;/* composite gadget needs to assign strings for whole device (like* serial number), register function drivers, potentially update* power state and consumption, etc*//* 此处才是开始调用驱动的 bind 函数 */status = composite->bind(cdev);if (status < 0)goto fail;update_unchanged_dev_desc(&cdev->desc, composite->dev);/* has userspace failed to provide a serial number? */if (composite->needs_serial && !cdev->desc.iSerialNumber)WARNING(cdev, "userspace failed to provide iSerialNumber\n");INFO(cdev, "%s ready\n", composite->name);return 0;fail:__composite_unbind(gadget, false);return status;
}
2.1 composite_bind
的关键步骤
struct usb_composite_driver *composite = to_cdriver(gdriver);
// 将 Gadget 转换为 Composite 的关键函数。cdev = kzalloc(sizeof *cdev, GFP_KERNEL);
// 实现cdev
设备。set_gadget_data(gadget, cdev);
// 填充私有数据,以便内核可以通过 Gadget 获取cdev
。INIT_LIST_HEAD(&cdev->configs);
// 初始化配置描述链表。status = composite_dev_prepare(composite, cdev);
// 实现 USB 请求,针对端点 0(控制端点)。status = composite->bind(cdev);
// 调用驱动的bind
函数。
2.2 composite_dev_prepare
函数
int composite_dev_prepare(struct usb_composite_driver *composite, // 该函数主要实现 USB 请求,针对端点 0,即控制端点。struct usb_composite_dev *cdev)
{struct usb_gadget *gadget = cdev->gadget;int ret = -ENOMEM;/* preallocate control response and buffer */cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); // 申请控制端点 USB 请求if (!cdev->req)return -ENOMEM;cdev->req->buf = kmalloc(USB_COMP_EP0_BUFSIZ, GFP_KERNEL); // 请求发送内容将保存在此if (!cdev->req->buf)goto fail;ret = device_create_file(&gadget->dev, &dev_attr_suspended);// 创建设备文件,位于 /sys/class/dev 目录下if (ret)goto fail_dev;cdev->req->complete = composite_setup_complete; // 回调函数。gadget->ep0->driver_data = cdev; // 通过端点即可获取设备。cdev->driver = composite; // 将 Composite 设备和驱动绑定在一起。/** As per USB compliance update, a device that is actively drawing* more than 100mA from USB must report itself as bus-powered in* the GetStatus(DEVICE) call.*/if (CONFIG_USB_GADGET_VBUS_DRAW <= USB_SELF_POWER_VBUS_MAX_DRAW)usb_gadget_set_selfpowered(gadget);/* interface and string IDs start at zero via kzalloc.* we force endpoints to start unassigned; few controller* drivers will zero ep->driver_data.*/usb_ep_autoconfig_reset(gadget);return 0;
fail_devkfree(cdev->req->buf);fail:usb_ep_free_request(gadget->ep0, cdev->req);cdev->req = NULL;return ret;
}
2.3 zero_bind
函数
static int __init zero_bind(struct usb_composite_dev *cdev) // 实现 Composite 设备的关键,涉及 strings、配置描述符、接口(function)、端点的实例化。
{struct f_ss_opts *ss_opts;struct f_lb_opts *lb_opts;int status;/* Allocate string descriptor numbers ... note that string* contents can be overridden by the composite_dev glue.*/status = usb_string_ids_tab(cdev, strings_dev);if (status < 0)return status;device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id;// 制造商device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id;// 产品 IDdevice_desc.iSerialNumber = strings_dev[USB_GADGET_SERIAL_IDX].id;// 设备序列号功能索引setup_timer(&autoresume_timer, zero_autoresume, (unsigned long) cdev);func_inst_ss = usb_get_function_instance("SourceSink"); // 获取 function,此处的实现十分巧妙,通过 usb_function_register 实现。if (IS_ERR(func_inst_ss))return PTR_ERR(func_inst_ss);ss_opts = container_of(func_inst_ss, struct f_ss_opts, func_inst);ss_opts->pattern = gzero_options.pattern;ss_opts->isoc_interval = gzero_options.isoc_interval;ss_opts->isoc_maxpacket = gzero_options.isoc_maxpacket;ss_opts->isoc_mult = gzero_options.isoc_mult;ss_opts->isoc_maxburst = gzero_options.isoc_maxburst;ss_opts->bulk_buflen = gzero_options.bulk_buflen; // 每次传送的 buf 大小func_ss = usb_get_function(func_inst_ss); // 获取 source_link 实例,同样通过 usb_function_register 注册获取。if (IS_ERR(func_ss)) {status = PTR_ERR(func_ss);goto err_put_func_inst_ss;}func_inst_lb = usb_get_function_instance("Loopback");if (IS_ERR(func_inst_lb)) {status = PTR_ERR(func_inst_lb);goto err_put_func_ss;}lb_opts = container_of(func_inst_lb, struct f_lb_opts, func_inst);lb_opts->bulk_buflen = gzero_options.bulk_buflen; // 在 usb_function_registe 注册时已经分配。lb_opts->qlen = gzero_options.qlen;func_lb = usb_get_function(func_inst_lb);if (IS_ERR(func_lb)) {status = PTR_ERR(func_lb);goto err_put_func_inst_lb;}sourcesink_driver.iConfiguration = strings_dev[USB_GZERO_SS_DESC].id;// 设置配置描述符索引loopback_driver.iConfiguration = strings_dev[USB_GZERO_LB_DESC].id;/* support autoresume for remote wakeup testing */sourcesink_driver.bmAttributes &= ~USB_CONFIG_ATT_WAKEUP;loopback_driver.bmAttributes &= ~USB_CONFIG_ATT_WAKEUP;sourcesink_driver.descriptors = NULL;loopback_driver.descriptors = NULL;if (autoresume) {sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;autoresume_step_ms = autoresume * 1000;}/* support OTG systems */if (gadget_is_otg(cdev->gadget)) {sourcesink_driver.descriptors = otg_desc;sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;loopback_driver.descriptors = otg_desc;loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;}/* Register primary, then secondary configuration. Note that* SH3 only allows one config...*/if (loopdefault) { // 若只支持 loopback 即回环模式。usb_add_config_only(cdev, &loopback_driver); // 则 loopback 配置先注册usb_add_config_only(cdev, &sourcesink_driver); // 后注册} else {usb_add_config_only(cdev, &sourcesink_driver); // 同上usb_add_config_only(cdev, &loopback_driver);}status = usb_add_function(&sourcesink_driver, func_ss); // 将功能即接口绑定到配置描述符if (status)goto err_conf_flb;usb_ep_autoconfig_reset(cdev->gadget); // 重新设置 epstatus = usb_add_function(&loopback_driver, func_lb);if (status)goto err_conf_flb;usb_ep_autoconfig_reset(cdev->gadget);usb_composite_overwrite_options(cdev, &coverwrite); // 支持传参。修改 vendor、product 等。INFO(cdev, "%s, version: " DRIVER_VERSION "\n", longname);return 0;err_conf_flb:usb_put_function(func_lb);func_lb = NULL;
err_put_func_inst_lb:usb_put_function_instance(func_inst_lb);func_inst_lb = NULL;
err_put_func_ss:usb_put_function(func_ss);func_ss = NULL;
err_put_func_inst_ss:usb_put_function_instance(func_inst_ss);func_inst_ss = NULL;return status;
}
2.4 usb_add_function
函数
int usb_add_function(struct usb_configuration *config, // 配置中实例化接口。struct usb_function *function)
{int value = -EINVAL;DBG(config->cdev, "adding '%s'/%p to config '%s'/%p\n",function->name, function,config->label, config);if (!function->set_alt || !function->disable) // 接口是否设置了 set_alt 函数,该函数调用表示当前接口可用,其他接口不可用。goto done;function->config = config;list_add_tail(&function->list, &config->functions);/* REVISIT *require* function->bind? */if (function->bind) {value = function->bind(config, function); // 对于一个配置多个接口的 cdev 设备,再次对 function 进行 bind 操作。if (value < 0) {list_del(&function->list);function->config = NULL;}} elsevalue = 0;/* We allow configurations that don't work at both speeds.* If we run into a lowspeed Linux system, treat it the same* as full speed ... it's the function drivers that will need* to avoid bulk and ISO transfers.*/if (!config->fullspeed && function->fs_descriptors)config->fullspeed = true;if (!config->highspeed && function->hs_descriptors)config->highspeed = true;if (!config->superspeed && function->ss_descriptors)config->superspeed = true;done:if (value)DBG(config->cdev, "adding '%s'/%p --> %d\n",function->name, function, value);return value;
}
2.5 loopback_bind
函数
static int loopback_bind(struct usb_configuration *c, struct usb_function *f) // 将配置和功能绑定在一起
{struct usb_composite_dev *cdev = c->cdev;struct f_loopback *loop = func_to_loop(f);int id;int ret;/* allocate interface ID(s) */id = usb_interface_id(c, f); // 一般从 0 开始配置。分配接口 ID 号if (id < 0)return id;loopback_intf.bInterfaceNumber = id;id = usb_string_id(cdev); // 获取字符描述符索引if (id < 0)return id;strings_loopback[0].id = id;loopback_intf.iInterface = id;/* allocate endpoints */loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc); // 分配批量输入端点if (!loop->in_ep) {autoconf_fail:ERROR(cdev, "%s: can't autoconfigure on %s\n",f->name, cdev->gadget->name);return -ENODEV;}loop->in_ep->driver_data = cdev; /* claim */loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc); // 分配批量输出端点if (!loop->out_ep)goto autoconf_fail;loop->out_ep->driver_data = cdev; /* claim *//* support high speed hardware */hs_loop_source_desc.bEndpointAddress =fs_loop_source_desc.bEndpointAddress;hs_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress;/* support super speed hardware */ss_loop_source_desc.bEndpointAddress =fs_loop_source_desc.bEndpointAddress;ss_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress;ret = usb_assign_descriptors(f, fs_loopback_descs, hs_loopback_descs, // 此处主要设置 USB 速度。ss_loopback_descs);if (ret)return ret;DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",(gadget_is_superspeed(c->cdev->gadget) ? "super" :(gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")),f->name, loop->in_ep->name, loop->out_ep->name);return 0;
}
3. Gadget 框架驱动注册过程总结
- 填充
usb_composite_driver
驱动实例,调用usb_composite_probe
进行注册。 - 将
usb_composite_driver
中的gadget_driver
填充,调用usb_gadget_probe_driver(gadget_driver)
,使得 UDC 能够和 Composite 设备联系起来。 - 调用
udc_bind_to_driver
,将 UDC 和 Composite 绑定在一起。 - 调用
driver->bind(udc->gadget, driver)
,实际上是调用composite_bind
函数,该函数由内核实现。该函数主要创建cdev
设备,将真正的驱动和cdev
绑定在一起。 - 再次调用编写的驱动的
bind
函数,此时主要是将cdev
设备的配置和function
进行填充。设备必须有配置,配置必须有接口。 - 针对多
function
的驱动,必须再次绑定bind
函数,此次主要是设置接口 ID,实例化端点等。 - 前 6 步操作完成后,表示 Composite 设备驱动已经注册成功。成功后,UDC 进入请求连接状态,等待中断响应。
- 中断响应即响应主设备发起的枚举操作,完成枚举过程,枚举响应主要调用
function->setup
函数。枚举过程将在另一篇文章中介绍。
Linux USB Gadget — 软件结构
窗外云天 于 2012-06-25 18:34:41 发布
一、UDC 层
UDC 层是与硬件相关的底层,负责与具体的 USB 设备控制器交互。以 S3C2410 的 UDC 实现为例,相关文件为 s3c2410_udc.c
和 s3c2410_udc.h
。S3C2410 的 USB 设备控制器作为 Linux 设备模型中的 platform 设备进行注册。以下是该层的关键数据结构和函数。
1. 数据结构
struct s3c2410_udc {spinlock_t lock;struct s3c2410_ep ep[S3C2410_ENDPOINTS];int address;struct usb_gadget gadget;struct usb_gadget_driver *driver;struct s3c2410_request fifo_req;u8 fifo_buf[EP_FIFO_SIZE];u16 devstatus;u32 port_status;int ep0state;unsigned got_irq : 1;unsigned req_std : 1;unsigned req_config : 1;unsigned req_pending : 1;u8 vbus;struct dentry *regs_info;
};
在 s3c2410_udc.c
中,声明了一个全局变量 memory
,用于表示 S3C2410 的 USB 设备控制器,包含设备的各种信息。
static struct s3c2410_udc memory = {.gadget = {.ops = &s3c2410_ops,.ep0 = &memory.ep[0].ep,.name = gadget_name,.dev = {.init_name = "gadget",},},/* control endpoint */.ep[0] = {.num = 0,.ep = {.name = ep0name,.ops = &s3c2410_ep_ops,.maxpacket = EP0_FIFO_SIZE,},.dev = &memory,},/* first group of endpoints */.ep[1] = {.num = 1,.ep = {.name = "ep1-bulk",.ops = &s3c2410_ep_ops,.maxpacket = EP_FIFO_SIZE,},.dev = &memory,.fifo_size = EP_FIFO_SIZE,.bEndpointAddress = 1,.bmAttributes = USB_ENDPOINT_XFER_BULK,},.ep[2] = {.num = 2,.ep = {.name = "ep2-bulk",.ops = &s3c2410_ep_ops,.maxpacket = EP_FIFO_SIZE,},.dev = &memory,.fifo_size = EP_FIFO_SIZE,.bEndpointAddress = 2,.bmAttributes = USB_ENDPOINT_XFER_BULK,},.ep[3] = {.num = 3,.ep = {.name = "ep3-bulk",.ops = &s3c2410_ep_ops,.maxpacket = EP_FIFO_SIZE,},.dev = &memory,.fifo_size = EP_FIFO_SIZE,.bEndpointAddress = 3,.bmAttributes = USB_ENDPOINT_XFER_BULK,},.ep[4] = {.num = 4,.ep = {.name = "ep4-bulk",.ops = &s3c2410_ep_ops,.maxpacket = EP_FIFO_SIZE,},.dev = &memory,.fifo_size = EP_FIFO_SIZE,.bEndpointAddress = 4,.bmAttributes = USB_ENDPOINT_XFER_BULK,}
};
2. 函数
UDC 层需要注册一个 platform 驱动,其结构体定义如下:
static struct platform_driver udc_driver_2410 = {.driver = {.name = "s3c2410-usbgadget",.owner = THIS_MODULE,},.probe = s3c2410_udc_probe,.remove = s3c2410_udc_remove,.suspend = s3c2410_udc_suspend,.resume = s3c2410_udc_resume,
};
其中,s3c2410_udc_probe
是最关键的一个函数。当 platform 总线为驱动程序找到合适的设备后,会调用此函数。该函数的主要职责是初始化设备的时钟,申请 I/O 资源以及 IRQ 资源,并初始化 platform 设备结构体 struct s3c2410_udc memory
。
UDC 层与 USB 设备层通过 struct usb_gadget
和 struct usb_gadget_driver
这两个结构体进行交互。struct usb_gadget
在 UDC 层中初始化,而 struct usb_gadget_driver
则在 USB 设备层中初始化,并通过 usb_gadget_register_driver(struct usb_gadget_driver *driver)
函数从 USB 设备层传入 UDC 层。该函数是 UDC 层与 USB 设备层交互的核心,它将 usb_gadget
与 usb_gadget_driver
联系在一起。
UDC 层的主要任务是为 usb_gadget
提供服务函数,并实现 USB 设备的中断处理程序。以下是 UDC 层提供的关键内容:
(1)usb_gadget
操作函数集合
static const struct usb_gadget_ops s3c2410_ops = {.get_frame = s3c2410_udc_get_frame,.wakeup = s3c2410_udc_wakeup,.set_selfpowered = s3c2410_udc_set_selfpowered,.pullup = s3c2410_udc_pullup,.vbus_session = s3c2410_udc_vbus_session,.vbus_draw = s3c2410_vbus_draw,
};
(2)端点操作函数集合
static const struct usb_ep_ops s3c2410_ep_ops = {.enable = s3c2410_udc_ep_enable,.disable = s3c2410_udc_ep_disable,.alloc_request = s3c2410_udc_alloc_request,.free_request = s3c2410_udc_free_request,.queue = s3c2410_udc_queue,.dequeue = s3c2410_udc_dequeue,.set_halt = s3c2410_udc_set_halt,
};
(3)USB 中断处理程序
static irqreturn_t s3c2410_udc_irq(int dummy, void *_dev)
(4)其他相关辅助函数,如调试相关函数。
二、USB 设备层
USB 设备层虽然名字与设备相关,但属于硬件无关层。其主要功能是隔离 Gadget 功能驱动与硬件相关层,使功能驱动直接与 USB 设备层交互,无需关心硬件细节。此外,USB 设备层还提供了 USB 设备的基本数据结构,供不同的 Gadget 功能驱动共同调用。如果没有这一层,每个功能驱动都需要实现自己的 USB 设备,导致代码复用率降低。
USB 设备层的相关代码为 composite.c
和 composite.h
。它向下与 UDC 层交互,向上与 Gadget 功能驱动层交互。与 UDC 层的交互主要通过调用 usb_gadget_register_driver(struct usb_gadget_driver *driver)
函数实现,该函数由 UDC 层提供。以下是 usb_gadget_driver
的结构体定义:
struct usb_gadget_driver {char *function;enum usb_device_speed speed;int (*bind)(struct usb_gadget *);void (*unbind)(struct usb_gadget *);int (*setup)(struct usb_gadget *, const struct usb_ctrlrequest *);void (*disconnect)(struct usb_gadget *);void (*suspend)(struct usb_gadget *);void (*resume)(struct usb_gadget *);/* FIXME support safe rmmod */struct device_driver driver;
};
在 composite.c
中声明了一个 usb_gadget_driver
类型的变量 composite_driver
,并将其作为参数传递给 usb_gadget_register_driver
函数:
static struct usb_gadget_driver composite_driver = {.speed = USB_SPEED_HIGH,.bind = composite_bind,.unbind = __exit_p(composite_unbind),.setup = composite_setup,.disconnect = composite_disconnect,.suspend = composite_suspend,.resume = composite_resume,.driver = {.owner = THIS_MODULE,},
};
以下是 USB 设备层与 Gadget 功能驱动层交互的关键内容:
(1)数据结构
struct usb_composite_dev {struct usb_gadget *gadget;struct usb_request *req;unsigned bufsiz;struct usb_configuration *config;/* private: *//* internals */struct usb_device_descriptor desc;struct list_head configs;struct usb_composite_driver *driver;u8 next_string_id;/* the gadget driver won't enable the data pullup* while the deactivation count is nonzero.*/unsigned deactivations;/* protects at least deactivation count */spinlock_t lock;
};
该结构体代表一个 USB 设备,包含设备描述符和配置信息,以及指向 usb_gadget
和 usb_composite_driver
的指针,从而将 UDC 层与功能驱动层联系在一起。该结构体在 composite_bind
函数中分配并初始化。
struct usb_composite_driver {const char *name;const struct usb_device_descriptor *dev;struct usb_gadget_strings **strings;int (*bind)(struct usb_composite_dev *);int (*unbind)(struct usb_composite_dev *);void (*suspend)(struct usb_composite_dev *);void (*resume)(struct usb_composite_dev *);
};
该结构体代表一个 USB 设备驱动,是功能驱动层与 USB 设备层交互的主要数据结构,由功能驱动层声明并初始化。
(2)函数
int __init usb_composite_register(struct usb_composite_driver *driver)
{if (!driver || !driver->dev || !driver->bind || composite)return -EINVAL;if (!driver->name)driver->name = "composite";composite_driver.function = (char *)driver->name;composite_driver.driver.name = driver->name;composite = driver;return usb_gadget_register_driver(&composite_driver);
}
该函数由 Gadget 功能驱动层调用,用于将功能驱动层与 USB 设备层联系在一起。它初始化了 composite_driver
,并调用 usb_gadget_register_driver
函数。composite
是一个全局指针,指向功能驱动传递过来的 driver
。因此,功能驱动层与 USB 设备层通过该函数建立联系。usb_gadget_register_driver
调用后,UDC 层与 USB 设备层也联系在一起。usb_composite_register
函数通常在功能驱动的模块初始化函数中调用,因此只要功能驱动加载,三个软件层就会通过数据结构联系在一起。
三、Gadget 功能驱动层
Gadget 功能驱动层是 USB Gadget 软件结构的最上层,主要负责实现 USB 设备的具体功能。这一层通常与 Linux 内核的其他层有密切联系。例如,模拟 U 盘的 Gadget 功能驱动会与文件系统层和块 I/O 层交互。以下以最简单的 Gadget 功能驱动 zero
为例进行说明。
模块初始化函数
static int __init init(void)
{return usb_composite_register(&zero_driver);
}
该函数非常简单,仅调用 usb_composite_register
,将功能驱动层与 USB 设备层联系在一起。zero_driver
是一个 usb_composite_driver
类型的结构体,其声明如下:
static struct usb_composite_driver zero_driver = {.name = "zero",.dev = &device_desc,.strings = dev_strings,.bind = zero_bind,.unbind = zero_unbind,.suspend = zero_suspend,.resume = zero_resume,
};
zero
功能驱动只需实现上述函数集合即可完成其功能。
总结
Linux 下的 USB Gadget 软件结构分为三层:UDC 层、USB 设备层和 Gadget 功能驱动层。这三层通过数据结构和函数相互联系,共同实现 USB 设备的功能。虽然本文仅分析了三层之间的联系,但数据传输的具体过程还需要进一步分析。以下是软件结构的示意图:
钢得锅
2022.01.23
目前最新的驱动已经将 usb_gadget_register_driver 换成 usb_gadget_probe_driver 了。
Android USB 之复合设备 (Gadget) 详解
2022-10-22
sheldon_blogs
一. 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 分为三层架构,层次关系从上到下如下:
1. USB Gadget 功能层
BSP/Driver 开发者通常需要实现这一层,从而实现一个具体的设备驱动,负责驱动硬件工作,与具体的 USB 设备控制器硬件相关。例如,DWC3 的 Gadget 驱动在 drivers/usb/dwc3/gadget.c
文件中实现。Android 在此层实现了 ADB、MTP、Mass Storage 等功能。浏览此层代码时,会发现“Composite”是此层的关键字,此层中关键的数据结构是 struct usb_composite_driver
。这一层的驱动文件一般为 driver/usb/gadget/android.c
(Android 实现的)或 driver/usb/gadget/serial.c
(传统 Linux 实现的 USB 转串口)。
2. USB 设备层
这一层由 Linux 内核开发维护者实现,与我们关系不大,我们只需关心其接口即可。浏览此层代码时,会发现“Gadget”是此层的关键字,此层的关键数据结构是 usb_gadget_driver
和 usb_composite_dev
。这一层的主要驱动文件为 driver/usb/gadget/composite.c
。
3. USB 设备控制器驱动层
这一层主要与 CPU 和 CPU 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 USB 3.0 控制器的初始化过程如下图所示,重点分析初始化设备模式的过程,主要工作如下:
- 将控制器设置为
USB_DR_MODE_OTG
模式。 - 初始化主机模式所需资源。
- 初始化设备模式所需资源:
- 获取中断号并分配端点 0 传输所需的内存。端点 0 在设备枚举时使用,需要响应主机端的请求,因此需要提前分配内存。
- 设置 DWC3 设备控制器的操作函数集合为
dwc3_gadget_ops
,仅涉及硬件控制,不涉及 I/O 操作。 - 初始化硬件端点。先初始化输出端点,后初始化输入端点。端点 0 的最大包长为 512 字节,其他端点的最大包长为 1024 字节。端点 0 的操作函数为
dwc3_gadget_ep0_ops
,其他端点的操作函数为dwc3_gadget_ep_ops
,端点的操作函数主要描述 I/O 操作。非端点 0 都会挂到gadget.ep_list
链表上。端点 0 支持控制传输,其他端点支持等时、批量、中断传输。 - 添加 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
描述端点的最大包长,由端点描述符(软件)配置,例如在 USB 3.0 中,Bulk 传输的最大包长为 512 字节,Isochronous 的最大包长为 1024 字节。maxpacket_limit
描述端点硬件能处理的最大包长,例如在 USB 3.0 中,端点 0 最大能处理 512 字节,其他端点最大能处理 1024 字节。USB 3.0 支持在一个 125 微秒内 Burst 传输多个数据包,最大值由 maxburst
设置,范围为 0-15,0 表示传输 1 包,15 表示可以传输 16 包,对于端点 0 该值只能为 0。每个端点都有不同的地址,使用 addr
描述。desc
指向端点描述符。如果使用 USB 3.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-16unsigned mult:2; // multiplier, 'mult' value for SS Isoc EPs// 端点支持的最大 burst,范围 0-15,USB 3.0 支持该选项unsigned maxburst:5;u8 address; // 端点地址,用于区分不同的端点const struct usb_endpoint_descriptor *desc; // 端点描述符const struct usb_ss_ep_comp_descriptor *comp_desc; // USB 3.0 伴侣描述符
};
[drivers/usb/dwc3/core.h]
struct dwc3_ep { // DWC3 USB 控制器设备模式端点数据结构struct usb_ep endpoint; // 通用的设备端点数据结构struct list_head pending_list; // pending 的 IO requestsstruct list_head started_list; // started 的 IO requestsspinlock_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; // USB 3.0 伴侣描述符struct dwc3 *dwc; // 指向 DWC3 控制器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_MASKu8 type;u8 resource_index;u32 allocated_requests; // 已经分配的 IO requestsu32 queued_requests; // 入队准备传输的 IO requests 数量// the interval on which the ISOC transfer is startedu32 interval;// a human readable name e.g. ep1out-bulkchar name[20];unsigned direction:1; // true for TX, false for RXunsigned 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
描述,Function 驱动会将数据封装成 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 controllersunsigned num_sgs; // number of SG entriesunsigned num_mapped_sgs; // number of SG entries mapped to DMA// The stream id, when USB 3.0 bulk streams are being usedunsigned stream_id:16;/* If true, hints that no completion irq is needed.Helpful sometimes with deep request queues that are handleddirectly 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 betreated as errors (queue stops advancing till cleanup). */unsigned short_not_ok:1;/* Function called when request completes, so this request andits buffer may be re-used. The function will always be called withinterrupts 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 byteswill usually still be in flight (often in a hardware fifo).Errors (for reads or writes) stop the queue from advancinguntil the completion callback returns, so that any transfersinvalidated 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 untilthe 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 (OUTtransfers) this may be less than the requested length. If theshort_not_ok flag is set, short reads are treated as errorseven when status otherwise indicates successful completion.Note that for writes (IN transfers) some data bytes may stillreside in a device-side FIFO when the request is reported ascomplete. */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 requestu8 epnum; // 该请求对应的端点编号struct dwc3_trb *trb; // 所属 trb 的地址dma_addr_t trb_dma; // 所属 trb 的 DMA 地址unsigned direction:1; // IN or OUT direction flagunsigned mapped:1; // true when request has been dma-mappedunsigned 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 { // TRB 结构u32 bpl; // 缓冲区低 32 位地址 DW0-3u32 bph; // 缓冲区高 32 位地址 DW4-7u32 size; // 缓冲区长度 [23:0] DW8-Bu32 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 的绑定在内核中完成。如果需要修改,则需要修改内核,不够灵活也不方便。Linux 3.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_UEVENTandroid_class = class_create(THIS_MODULE, "android_usb");if (IS_ERR(android_class))return PTR_ERR(android_class);
#endifreturn 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; // 支持 USB 3.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/ -l
total 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 bcdUSB
drwxr-xr-x 3 root root 0 2022-10-22 14:18 configs
drwxr-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_speed
drwxr-xr-x 2 root root 0 2022-10-22 14:22 os_desc
drwxr-xr-x 3 root root 0 2022-10-22 14:18 stringsconsole:/ # ls /config/usb_gadget/g1/functions/ -l
total 0
drwxr-xr-x 2 root root 0 2022-09-02 05:50 accessory.gs2
drwxr-xr-x 2 root root 0 2022-09-02 05:50 audio_source.gs3
drwxr-xr-x 2 root root 0 2022-09-02 05:50 ffs.adb
drwxr-xr-x 2 root root 0 2022-09-02 05:50 ffs.mtp
drwxr-xr-x 2 root root 0 2022-09-02 05:50 ffs.ptp
drwxr-xr-x 2 root root 0 2022-09-02 05:50 midi.gs5
drwxr-xr-x 3 root root 0 2022-09-02 05:50 rndis.gs4
drwxr-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
会调用 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 Function API
usb_get_function_instance
函数,遍历func_list
链表,根据名称进行匹配。用户空间输入的是uac2
,则匹配uac2
驱动。其定义在f_uac2.c
文件中。 - 匹配成功后回调 Function 驱动定义的
alloc_inst
函数。对于uac2
,则回调afunc_alloc_inst
函数:- 分配
f_uac2_opts
数据结构,内部包含usb_function_instance
。 - 设置
uac2
的config_item_type
为f_uac2_func_type
,f_uac2_func_type
会在/sys/kernel/config/usb_gadget/g1/functions/uac2.0
目录下导出设置uac2
音频参数的属性文件,便于用户空间设置。 - 设置音频参数的默认值,用户可以在用户空间修改。
- 分配
- 将获取到的
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 Function API,调用
uac2
驱动的afunc_alloc
函数:- 分配
f_uac2
数据结构,内部包含usb_function
。 - 填充
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 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
函数。其主要工作内容如下:- 分配端点的
usb_request
、分配缓冲区、设置usb_request
的回调函数、复位所有端点,并将 Gadget 的端点数量清零。 - Function 驱动保存对应的配置,并回调 Function 驱动的
bind
函数。对于uac2
,则回调afunc_bind
函数。 - 如果使用
os_string
,则需要分配os_string
request。
- 分配端点的
- 调用 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
驱动是生产者,用户是消费者;播放时则相反。
以下是 u_audio_iso_complete
函数的代码:
[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,dp0
1 [rockchipdp1 ]: rockchip_dp1 - rockchip,dp1rockchip,dp1
2 [rockchiphdmi1 ]: rockchip-hdmi1 - rockchip-hdmi1rockchip-hdmi1
3 [rockchiphdmi0 ]: rockchip-hdmi0 - rockchip-hdmi0rockchip-hdmi0
4 [rockchiphdmiin ]: rockchip_hdmiin - rockchip,hdmiinrockchip,hdmiin
5 [rockchipes8388 ]: rockchip-es8388 - rockchip-es8388rockchip-es8388
6 [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
会将数据静音,具体可以参考以下 Android 12 的注释:
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/hmkdir /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/dwFrameIntervalln -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/mmkdir /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/hecho 1024 > /sys/kernel/config/usb_gadget/g1/functions/uvc.gs6/streaming_maxpacket // 一个微帧传 1K 数据,可以配置最大 3Kstop 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/subclassrm 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
查看当前系统注册的 UDC 驱动:
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.usb
lrwxrwxrwx 1 root root 0 2022-09-02 07:47 fc400000.usb -> ../../devices/platform/usbdrd3_1/fc400000.usb/udc/fc400000.usb
查看与 USB 相关的系统设备:
cat /proc/devices // 查看系统设备中与 usb 相关的设备
输出示例:
180 usb
189 usb_device
查看 USB 总线信息:
ls /sys/bus/usb/devices/ // 查看当前系统中的 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 usb3
2-0:1.0 2-1.2 4-0:1.0 4-1.2 4-1:1.0 6-1:1.0 usb4
2-1 2-1.2:1.0 4-1 4-1.2:1.0 5-0:1.0 usb1 usb5
2-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”。
通过 configfs 配置的 Linux USB Gadget
闹闹爸爸
2021-08-12 14:57
概述
USB Linux Gadget 是一种具有 UDC(USB 设备控制器)的设备,可以连接到 USB 主机,以扩展其附加功能,如串口或大容量存储能力。
一个 Gadget
被它的主机视为一组配置,每个配置都包含一些接口,从 Gadget
的角度来看,这些接口被称为功能,每个功能代表一个串行连接或一个 SCSI 磁盘。
Linux 提供了许多 Gadget
可以使用的功能。
创建一个 Gadget
意味着决定将有哪些配置以及每个配置将提供哪些功能。
Configfs
(请参阅 Configfs—用户空间驱动的内核对象配置)非常适合告诉内核上述决定。本文档是关于如何实现这一点的。它还描述了如何将 configfs
集成到 Gadget
中。
要求
为了使其工作,配置文件必须可用,因此 CONFIGFS_FS
必须为 ‘y’ 或 ‘m’ 在 .config
中。在撰写本文时,USB_LIBCOMPOSITE
选择 CONFIGFS_FS
。
用法
(描述 configfs
提供的第一个功能的原始帖子可以在这里看到:Configfs for USB Gadget)
$ modprobe libcomposite
$ mount none $CONFIGFS_HOME -t configfs
其中 CONFIGFS_HOME
是 configfs
的挂载点。
1. 创建 Gadget
对于每个要创建的 Gadget
,必须创建相应的目录:
$ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
例如:
$ mkdir $CONFIGFS_HOME/usb_gadget/g1
$ cd $CONFIGFS_HOME/usb_gadget/g1
每个 Gadget
需要指定其 vendor id <VID>
和 product id <PID>
:
$ echo <VID> > idVendor
$ echo <PID> > idProduct
Gadget
还需要它的序列号、制造商和产品字符串。为了有一个地方存储它们,必须为每种语言创建一个字符串子目录,例如:
$ mkdir strings/0x409
然后可以指定字符串:
$ echo <serial number> > strings/0x409/serialnumber
$ echo <manufacturer> > strings/0x409/manufacturer
$ echo <product> > strings/0x409/product
2. 创建配置
每个 Gadget
将由许多配置组成,必须创建相应的目录:
$ mkdir configs/<name>.<number>
<name>
可以是文件系统中合法的任意字符串,而 <number>
是配置的编号,例如:
$ mkdir configs/c.1
每个配置也需要它的字符串,所以必须为每种语言创建一个子目录,例如:
$ mkdir configs/c.1/strings/0x409
然后可以指定配置字符串:
$ echo <configuration> > configs/c.1/strings/0x409/configuration
也可以为配置设置一些属性,例如:
$ echo 120 > configs/c.1/MaxPower
3. 创建功能
Gadget
将提供一些功能,对于每个功能,必须创建相应的目录:
$ mkdir functions/<name>.<instance name>
其中 <name>
对应于一个允许的功能名称,<instance name>
是文件系统中允许的任意字符串,例如:
$ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
每个函数都提供其特定的属性集,具有只读或读写访问权限。如适用,需要酌情写入。更多信息请参考 Documentation/ABI/testing/configfs-usb-gadget
。
4. 关联功能及其配置
此时,许多 Gadget
被创建出来,每个 Gadget
都有一些指定的配置和一些可用的功能。剩下的就是指定哪个功能在哪个配置中可用(同一个功能可以在多个配置中使用)。这是通过创建符号链接来实现的:
$ ln -s functions/<name>.<instance name> configs/<name>.<number>
例如:
$ ln -s functions/ncm.usb0 configs/c.1
5. 启用 Gadget
以上所有步骤的目的是组成 Gadget
的配置和功能。
示例目录结构可能看起来像这样:
../strings./strings/0x409./strings/0x409/serialnumber./strings/0x409/product./strings/0x409/manufacturer./configs./configs/c.1./configs/c.1/ncm.usb0 -> ../../../../usb_gadget/g1/functions/ncm.usb0./configs/c.1/strings./configs/c.1/strings/0x409./configs/c.1/strings/0x409/configuration./configs/c.1/bmAttributes./configs/c.1/MaxPower./functions./functions/ncm.usb0./functions/ncm.usb0/ifname./functions/ncm.usb0/qmult./functions/ncm.usb0/host_addr./functions/ncm.usb0/dev_addr./UDC./bcdUSB./bcdDevice./idProduct./idVendor./bMaxPacketSize0./bDeviceProtocol./bDeviceSubClass./bDeviceClass
这样的 Gadget
必须最终启用,以便 USB 主机能够枚举它。
为了启用 Gadget
,它必须绑定到 UDC
(USB 设备控制器):
$ echo <udc name> > UDC
其中 <udc name>
是在 /sys/class/udc/*
中的,例如:
$ echo s3c-hsotg > UDC
6. 禁用 Gadget
$ echo "" > UDC
7. 清理
从配置中删除功能:
$ rm configs/<config name>.<number>/<function>
<config name>.<number>
指定配置,<function>
是指向从配置中删除的功能的符号链接,例如:
$ rm configs/c.1/ncm.usb0
删除配置中的字符串目录:
$ rmdir configs/<config name>.<number>/strings/<lang>
例如:
$ rmdir configs/c.1/strings/0x409
并删除配置:
$ rmdir configs/<config name>.<number>
例如:
rmdir configs/c.1
删除功能(功能模块不会被卸载):
$ rmdir functions/<name>.<instance name>
例如:
$ rmdir functions/ncm.usb0
删除 Gadget
中的字符串目录:
$ rmdir strings/<lang>
例如:
$ rmdir strings/0x409
最后移除 Gadget
:
$ cd ..
$ rmdir <gadget name>
例如:
$ rmdir g1
实施设计
下面介绍 configfs
的工作原理。在 configfs
中有项目和组,它们都表示为目录。项和组之间的区别在于,组可以包含其他组。下图中只显示了一个项目。项和组都可以具有属性,这些属性表示为文件。用户可以创建和删除目录,但不能删除文件,文件可以是只读的或读写的,这取决于它们所代表的内容。
configfs
的文件系统部分操作 config_items/groups
和 configfs_attributes
,它们是通用的,对所有配置的元素具有相同的类型。但是,它们被嵌入到特定于使用的更大的结构中。下面的图片中有一个“cs”,它包含一个 config_item
和一个“sa”,它包含一个 configfs_attribute
。
文件系统视图是这样的:
././cs (directory)|+--sa (file)|...
每当用户读取/写入“sa”文件时,都会调用一个函数,该函数接受一个 struct config_item
和一个 struct configfs_attribute
。在上述函数中,使用众所周知的 container_of
技术检索“cs”和“sa”,并调用适当的 sa
函数(show
或 store
)并传递“cs”和字符缓冲区。“show” 用于显示文件的内容(将数据从 cs
复制到缓冲区),而 “store” 用于修改文件的内容(将数据从缓冲区复制到 cs
),但这取决于两个函数的实现者来决定它们的操作。
typedef struct configured_structure cs;
typedef struct specific_attribute sa;sa+----------------------------------+cs | (*show)(cs *, buffer); |
+-----------------+ | (*store)(cs *, buffer, length); |
| | | |
| +-------------+ | | +------------------+ |
| | struct |-|----|------>|struct | |
| | config_item | | | |configfs_attribute| |
| +-------------+ | | +------------------+ |
| | +----------------------------------+
| data to be set | .
| | .
+-----------------+ .
文件名由配置项/组设计器决定,而目录通常可以随意命名。一个组可以有许多自动创建的默认子组。
有关 configfs
的更多信息,请参见 Documentation/filesystems/configfs.rst
。
上面描述的概念转化为 USB Gadget
如下:
- 一个小工具有它的配置组,它有一些属性(
idVendor
,idProduct
等)和默认子组(configs
,functions
,strings
)。写入属性将导致信息存储在适当的位置。在配置、函数和字符串子组中,用户可以创建它们的子组来表示给定语言中的配置、函数和字符串组。 - 用户创建配置和函数,在配置中创建到函数的符号链接。当将
Gadget
的UDC
属性写入时使用此信息,这意味着将Gadget
绑定到UDC
。驱动程序/drivers/usb/gadget/configfs.c
中的代码遍历所有配置,并且在每个配置中遍历所有函数并绑定它们。这样整个Gadget
就被绑定了。 - 文件驱动程序
/drivers/usb/gadget/configfs.c
包含以下代码:
Gadget
的config_group
Gadget
的默认组(configs
,functions
,strings
)- 将函数与配置关联(符号链接)
- 个 USB 函数自然都有自己想要配置的视图,所以特定函数的
config_groups
定义在函数实现文件/drivers/USB/gadget/f_*.c
中。 - 函数的代码是以它所使用的方式编写的。
Usb_get_function_instance()
,它反过来调用 request_module
。因此,只要 modprobe
工作正常,特定函数的模块就会自动加载。请注意,相反的情况是不正确的:在 Gadget
被禁用和卸载后,模块仍然是加载的。
Configfs - 用户空间驱动的内核对象配置
闹闹爸爸
2021-08-24
Configfs 是什么?
Configfs 是一个基于 RAM 的文件系统,它提供了与 sysfs 相反的功能。具体来说,sysfs 是基于文件系统的内核对象视图,而 configfs 是基于文件系统的内核对象管理器,或称为 config_items。
使用 sysfs 时,内核中创建的对象(例如,当发现设备时)会被注册到 sysfs。其属性随后会出现在 sysfs 中,允许用户空间通过 readdir(3)
和 read(2)
读取属性,某些属性可能还允许通过 write(2)
修改。重要的是,对象的创建和销毁完全由内核控制,内核管理着 sysfs 表示的生命周期,而 sysfs 仅仅是这一过程的一个窗口。
相比之下,configfs 的 config_item
是通过显式的用户空间操作创建的,例如 mkdir(2)
。它通过 rmdir(2)
销毁。这些属性在 mkdir(2)
时出现,可以通过 read(2)
和 write(2)
读取或修改。与 sysfs 类似,readdir(3)
可以查询 items 和/或属性的列表,也可以通过符号链接(symlink(2)
)将项目分组在一起。与 sysfs 不同,configfs 的生命周期完全由用户空间驱动,支持这些项的内核模块必须对此做出响应。
sysfs 和 configfs 可以且应该同时存在于同一个系统上,它们并不是彼此的替代品。
使用 Configfs
Configfs 可以被编译为一个模块,或者直接编译到内核中。可以通过以下命令访问它:
mount -t configfs none /config
除非客户端模块也被加载,否则 configfs 树将是空的。这些模块将它们的 item 类型注册到 configfs 作为子系统。加载客户端子系统后,它将显示为 /config
下的一个子目录(或多个子目录)。与 sysfs 类似,configfs 树始终存在,无论是否挂载在 /config
上。
通过 mkdir(2)
创建 item。此时,item 的属性也将显示出来。readdir(3)
可以确定属性是什么,read(2)
可以查询它们的默认值,而 write(2)
可以存储新值。不要在一个属性文件中混合多个属性。
Configfs 属性有两种类型:
- 普通属性:类似于 sysfs 属性,是小型 ASCII 文本文件,最大大小为一页(
PAGE_SIZE
,在 i386 上为 4096 字节)。最好每个文件只使用一个值,同样的来自 sysfs 的警告也适用。Configfs 期望write(2)
一次存储整个缓冲区。当写入到普通的 configfs 属性时,用户空间进程应该首先读取整个文件,修改它们希望更改的部分,然后将整个缓冲区写回。 - 二进制属性:有点类似于 sysfs 的二进制属性,但在语义上有一些细微的变化。
PAGE_SIZE
限制不适用,但整个二进制 item 必须适合单个内核vmalloc
缓冲区。用户空间的write(2)
调用被缓冲,属性的write_bin_attribute
方法将在最后的close
调用时被调用,因此用户空间必须检查close(2)
的返回代码,以验证操作成功完成。为了避免恶意用户抢占内核,有一个每个二进制属性的最大缓冲值。
当需要销毁一个 item 时,使用 rmdir(2)
删除它。如果任何其他 item 与该 item 有链接(通过 symlink(2)
),则该 item 不能被销毁。链接可以通过 unlink(2)
删除。
配置 FakeNBD:示例
假设有一个网络块设备(Network Block Device,简称 NBD)驱动程序允许您访问远程块设备。称之为 FakeNBD。FakeNBD 使用 configfs 进行配置。显然,系统管理员将使用一个合适的程序来配置 FakeNBD,但该程序必须以某种方式告诉驱动程序。这就是 configfs 的作用所在。
当 FakeNBD 驱动程序被加载时,它将自己注册到 configfs 中。此时,readdir(3)
会看到如下内容:
# ls /config
fakenbd
可以使用 mkdir(2)
创建一个 FakeNBD 连接。名称是任意的,但工具可能会使用这个名称,例如 UUID 或磁盘名:
# mkdir /config/fakenbd/disk1
# ls /config/fakenbd/disk1
target device rw
target
属性包含 FakeNBD 将连接到的服务器的 IP 地址。device
属性是服务器上的设备。rw
属性决定连接是只读还是读写:
# echo 10.0.0.1 > /config/fakenbd/disk1/target
# echo /dev/sda1 > /config/fakenbd/disk1/device
# echo 1 > /config/fakenbd/disk1/rw
至此,设备已经通过 shell 命令 配置完成。
使用 Configfs 进行编程
Configfs 中的每个对象都是一个 config_item
。config_item
反映了子系统中的一个对象,并具有与该对象上的值匹配的属性。Configfs 负责处理该对象及其属性的文件系统表示,允许子系统忽略除了基本的显示/存储交互之外的所有交互。
在 config_group
中创建和销毁 item。组是共享相同属性和操作的 item 的集合。item 由 mkdir(2)
创建并由 rmdir(2)
删除,但 configfs 会处理它。该组有一组用于执行这些任务的操作。
子系统是客户端模块的顶层。在初始化期间,客户端模块用 configfs 注册子系统,子系统显示为 configfs 文件系统顶部的一个目录。子系统也是一个 config_group
,可以做 config_group
可以做的所有事情。
struct config_item
struct config_item {char *ci_name;char ci_namebuf[UOBJ_NAME_LEN];struct kref ci_kref;struct list_head ci_entry;struct config_item *ci_parent;struct config_group *ci_group;struct config_item_type *ci_type;struct dentry *ci_dentry;
};void config_item_init(struct config_item *);
void config_item_init_type_name(struct config_item *,const char *name,struct config_item_type *type);
struct config_item *config_item_get(struct config_item *);
void config_item_put(struct config_item *);
通常,struct config_item
被嵌入到容器结构中,这个结构实际上表示子系统正在做什么。该结构的 config_item
部分是对象如何与 configfs 交互的。
无论是在源文件中静态定义,还是由父文件 config_group
创建,config_item
必须调用其中一个 _init()
函数。这将初始化引用计数并设置适当的字段。
config_item
的所有用户都应该通过 config_item_get()
对其进行引用,并在通过 config_item_put()
完成时删除该引用。
config_item
本身只能在 configfs 中出现。子系统通常希望 item 显示和/或存储属性。为此,它需要一个类型。
struct config_item_type
struct configfs_item_operations {void (*release)(struct config_item *);int (*allow_link)(struct config_item *src,struct config_item *target);void (*drop_link)(struct config_item *src,struct config_item *target);
};struct config_item_type {struct module *ct_owner;struct configfs_item_operations *ct_item_ops;struct configfs_group_operations *ct_group_ops;struct configfs_attribute **ct_attrs;struct configfs_bin_attribute **ct_bin_attrs;
};
config_item_type
最基本的功能是定义可以在 config_item
上执行哪些操作。所有已动态分配的 items 都需要提供 ct_item_ops->release()
方法。当 config_item
的引用计数为零时,将调用此方法。
struct configfs_attribute
struct configfs_attribute {char *ca_name;struct module *ca_owner;umode_t ca_mode;ssize_t (*show)(struct config_item *, char *);ssize_t (*store)(struct config_item *, const char *, size_t);
};
当 config_item
希望一个属性以文件的形式出现在 item 的 configfs 目录中时,它必须定义一个 configfs_attribute
来描述它。然后将该属性添加到以 NULL
结尾的数组 config_item_type->ct_attrs
中。当 item 出现在 configfs 中时,属性文件将以 configfs_attribute->ca_name
作为文件名出现。configfs_attribute->ca_mode
指定文件权限。
如果一个属性是可读的,并且提供了一个 ->show
方法,那么当用户空间请求对该属性进行 read(2)
时,该方法将被调用。如果一个属性是可写的,并且提供了一个 ->store
方法,当用户空间请求对该属性进行 write(2)
时,该方法将被调用。
struct configfs_bin_attribute
struct configfs_bin_attribute {struct configfs_attribute cb_attr;void *cb_private;size_t cb_max_size;
};
当需要使用二进制 blob 作为 item 的 configfs 目录中的文件内容时,使用二进制属性。为此,将二进制属性添加到以 NULL
结束的数组 config_item_type->ct_bin_attrs
中。当该 item 出现在 configfs 中时,属性文件将以 configfs_bin_attribute->cb_attr.ca_name
作为文件名出现。configfs_bin_attribute->cb_attr.ca_mode
指定文件权限。cb_private
成员由驱动程序提供,而 cb_max_size
成员指定要使用的 vmalloc
缓冲区的最大数量。
如果二进制属性是可读的,并且 config_item
提供了一个 ct_item_ops->read_bin_attribute()
方法,当用户空间请求对该属性进行 read(2)
时,该方法将被调用。对于 write(2)
,情况类似。读/写被缓冲,因此只有一个读/写操作发生;属性本身不需要关心它。
struct config_group
config_item
不能独立存在。唯一的创建方式是通过 config_group
上的 mkdir(2)
。这将触发子 item 的创建:
struct config_group {struct config_item cg_item;struct list_head cg_children;struct configfs_subsystem *cg_subsys;struct list_head default_groups;struct list_head group_entry;
};void config_group_init(struct config_group *group);
void config_group_init_type_name(struct config_group *group,const char *name,struct config_item_type *type);
config_group
结构包含一个 config_item
。正确配置该 item 意味着 group 可以按照 item 本身的权限行事。但是,它还可以做更多的事情:它可以创建子 item 或 group。这是通过在 group 的 config_item_type
上指定的组操作来完成的:
struct configfs_group_operations {struct config_item *(*make_item)(struct config_group *group,const char *name);struct config_group *(*make_group)(struct config_group *group,const char *name);int (*commit_item)(struct config_item *item);void (*disconnect_notify)(struct config_group *group,struct config_item *item);void (*drop_item)(struct config_group *group,struct config_item *item);
};
组通过提供 ct_group_ops->make_item()
方法创建子 item。如果提供了该方法,则从组目录中的 mkdir(2)
调用该方法。子系统分配一个新的 config_item
(更有可能是它的容器结构),初始化它,并将它返回给 configfs。然后 Configfs 将填充文件系统树以反映新 item。
如果子系统希望子节点本身是一个组,则该子系统提供 ct_group_ops->make_group()
。在组上使用 group_init()
函数,其他所有操作都是相同的。
最后,当用户空间对 item 或组调用 rmdir(2)
时,调用 ct_group_ops->drop_item()
。因为 config_group
也是一个 config_item
,所以没有必要单独使用 drop_group()
方法。子系统必须对 item 分配时初始化的引用进行 config_item_put()
。如果一个子系统没有工作要做,它可以省略 ct_group_ops->drop_item()
方法,configfs 将代表子系统调用 config_item_put()
。
注意:
drop_item()
是 void
类型函数,因此不会失败。当调用 rmdir(2)
时,configfs 将从文件系统树中删除该项(假设它没有子项使其忙碌)。子系统负责对此做出响应。如果子系统在其他线程中有对该 item 的引用,则内存是安全的。item 可能需要一段时间才能真正从子系统的使用中消失。但它已经从 configfs 中消失了。
当 drop_item()
被调用时,item 的链接已经被拆除。它在父节点上不再有引用,在 item 层次结构中也没有位置。如果客户端需要在拆解之前进行一些清理,子系统可以实现 ct_group_ops->disconnect_notify()
方法。该方法在 configfs 从文件系统视图中删除 item 之后被调用,但在 item 从其父组中删除之前。和 drop_item()
一样,disconnect_notify()
是 void
类型,不会失败。客户端子系统不应该在这里删除任何引用,因为它们仍然必须在 drop_item()
中删除。
当 config_group
仍然有子 item 时,它不能被删除。这在 configfs rmdir(2)
代码中实现。->drop_item()
将不会被调用,因为 item 还没有被删除。rmdir(2)
将失败,因为目录不是空的。
struct configfs_subsystem
子系统必须在 module_init
时注册自己。这告诉 configfs 让子系统出现在文件树中:
struct configfs_subsystem {struct config_group su_group;struct mutex su_mutex;
};int configfs_register_subsystem(struct configfs_subsystem *subsys);
void configfs_unregister_subsystem(struct configfs_subsystem *subsys);
子系统由顶层 config_group
和互斥锁组成。这个组是创建子 config_items
的地方。对于子系统,这个组通常是静态定义的。在调用 configfs_register_subsystem()
之前,子系统必须通过常用的 group_init()
函数初始化组,并且还必须初始化互斥锁。
当注册调用返回时,子系统是活动的,它将通过 configfs 可见。此时,可以调用 mkdir(2)
,并且子系统必须为此做好准备。
示例
这些基本概念的最佳示例是 samples/configfs/configfs_sample.c
中的 simple_children
子系统/组和 simple_child
item。它展示了一个显示和存储属性的普通对象,以及一个创建和销毁这些子对象的简单组。
层次导航和子系统互斥锁
Configfs 还提供了一个额外的好处。config_groups
和 config_items
被安排在一个层次结构中,因为它们出现在文件系统中。子系统永远不会涉及文件系统部分,但子系统可能对这个层次结构感兴趣。由于这个原因,层次结构通过 config_group->cg_children
和 config_item->ci_parent
结构成员进行镜像。
子系统可以导航 cg_children
列表和 ci_parent
指针,以查看由子系统创建的树。这可能与 configfs 对层次结构的管理竞争,因此 configfs 使用子系统互斥锁来保护修改。每当一个子系统想要在层次结构中导航时,它必须在子系统互斥锁的保护下进行。
当新分配的项目还没有链接到这个层次结构时,子系统将无法获取互斥锁。类似地,当正在删除的 item 尚未解除链接时,它将无法获取互斥锁。这意味着当 item 在 configfs 中时,item 的 ci_parent
指针永远不会是 NULL
,并且 item 只会在其父 item 的 cg_children
列表中持续相同的时间。这允许子系统在 ci_parent
和 cg_children
持有互斥对象时信任它们。
通过符号链接进行 item 聚合
Configfs 通过 group->item
的父/子关系提供了一个简单的组。然而,通常较大的环境需要在父/子连接之外进行聚合。这是通过 symlink(2)
实现的。
config_item
可以提供 ct_item_ops->allow_link()
和 ct_item_ops->drop_link()
方法。如果 ->allow_link()
方法存在,则可以使用 config_item
作为链接的源调用 symlink(2)
。这些链接只允许在 configfs config_items
之间。configfs 文件系统之外的任何 symlink(2)
尝试都将被拒绝。
当调用 symlink(2)
时,源 config_item
的 ->allow_link()
方法将与它本身和目标项一起调用。如果源 item 允许链接到目标 item,则返回 0。如果源 item 只希望链接到某种类型的对象(例如,在它自己的子系统中),那么它可能希望拒绝链接。
当符号链接上调用 unlink(2)
时,源 item 会通过 ->drop_link()
方法得到通知。与 ->drop_item()
方法一样,这是一个 void
函数,不会返回失败。子系统负责响应更改。
当一个 config_item
链接到任何其他 item 时,它不能被删除;当一个 item 链接到它时,它也不能被删除。在 configfs 中不允许悬挂符号链接。
自动创建子组
新的 config_group
可能希望有两种类型的子 config_items
。虽然这可以通过 ->make_item()
中的 magic name 进行编码,但希望有一个方法让用户空间更明确地看到这种差异。
Configfs 提供了一种方法,可以在父组创建时自动创建一个或多个子组,而不是在组中某些 item 的行为与其他 item 不同。因此,mkdir("parent")
的结果是 "parent"
,"parent/subgroup1"
,一直到 "parent/subgroupN"
。现在可以在 "parent/subgroup1"
中创建类型 1 的 item,在 "parent/subgroupN"
中创建类型 N 的 item。
这些自动子组或默认组不排除父组的其他子组。如果存在 ct_group_ops->make_group()
,则可以直接在父组上创建其他子组。
Configfs 子系统通过使用 configfs_add_default_group()
函数将默认组添加到父 config_group
结构中来指定它们。每个添加的组与父组同时被填充到 configfs 树中。类似地,它们与父类同时被删除。不提供额外的通知。当调用 ->drop_item()
方法通知子系统父组将离开时,它还意味着与该父组关联的每个默认组子组。
因此,无法通过 rmdir(2)
直接删除默认组。当父组上的 rmdir(2)
检查子组时,它们也不被考虑。
依赖子系统
有时其他驱动程序依赖于特定的 configfs item。例如,ocfs2 挂载依赖于心跳区域 item。如果使用 rmdir(2)
删除该心跳区域 item,则 ocfs2 挂载必须变为 BUG 或只读。这是不可取的。
Configfs 提供了两个额外的 API 调用:configfs_depend_item()
和 configfs_undepend_item()
。客户端驱动程序可以在现有的项上调用 configfs_depend_item()
来告诉 configfs 它是被依赖的。Configfs 将从 rmdir(2)
返回该 item 的 -EBUSY
。当 item 不再被依赖时,客户端驱动程序调用 configfs_undepend_item()
。
这些 API 不能在任何 configfs 回调中调用,因为它们会发生冲突。它们可以阻塞和分配。客户端驱动程序可能不应该自行调用它们。相反,它应该提供外部子系统调用的 API。
这是如何实现的?想象一下 ocfs2 挂载进程。当它挂载时,它请求一个心跳区域 item。这是通过调用心跳代码来完成的。在心跳代码中,查找区域 item。此时,心跳代码调用 configfs_depend_item()
。如果成功,心跳代码知道该区域可以安全地交给 ocfs2。如果它失败了,无论如何它都会被拆除,心跳代码可以优雅地上报一个错误。
可提交的 Item
注意:可提交的 Item 目前尚未实现。
一些 config_items
不能具有有效的初始状态。也就是说,不能为 item 的属性指定默认值以使 item 能够执行其工作。用户空间必须配置一个或多个属性,在这些属性之后,子系统可以启动这个 item 所代表的任何实体。
考虑上面的 FakeNBD 设备。如果没有目标地址(target address
)和目标设备(target device
),子系统就不知道要导入哪个块设备。这个简单的示例假设子系统只是等待,直到配置了所有适当的属性,然后再连接。这确实有效,但现在每个属性存储都必须检查属性是否已初始化。如果条件满足,每个存储的属性存储都必须触发连接。
最好有一个显式的操作,通知子系统 config_item
已经准备好了。更重要的是,显式操作允许子系统提供关于属性是否以有意义的方式初始化的反馈。Configfs 将其作为可提交的 Item 提供。
Configfs 仍然只使用正常的文件系统操作。通过 rename(2)
提交 item。item 从可以修改的目录移动到不能修改的目录。
任何提供 ct_group_ops->commit_item()
方法的组都有可提交的 Items。当这个组出现在 configfs 中时,mkdir(2)
将不会直接在这个组中工作。相反,这个组将有两个子目录:“live” 和 “pending”。“live” 目录也不支持 mkdir(2)
或 rmdir(2)
。它只允许 rename(2)
。“pending” 目录允许 mkdir(2)
和 rmdir(2)
。在 “pending” 目录中创建了一个 item。它的属性可以随意修改。用户空间通过将 item 重命名到 “live” 目录来提交 item。此时,子系统接收到 ->commit_item()
回调。如果所有必需的属性都满足要求,则该方法返回 0,并将 item 移动到 “live” 目录。
由于 rmdir(2)
不能在 “live” 目录下工作,因此必须关闭或 “uncommitted” item。同样,这是通过 rename(2)
完成的,这次是从 “live” 目录返回到 “pending” 目录。子系统由 ct_group_ops->uncommit_object()
方法通知。
via:
-
Linux usb gadget 框架概述 - haoxing990 - 博客园
https://www.cnblogs.com/haoxing990/p/8799133.html -
Linux USB Gadget-- 软件结构 - CSDN 博客
https://blog.csdn.net/yaozhenguo2006/article/details/7690707 -
USB 总线 - Linux 内核 USB3.0 设备控制器复合设备之 USB gadget configfs 分析 - CSDN 博客
https://blog.csdn.net/u011037593/article/details/123698241 -
Android USB 之复合设备 (gadget) 详解 - sheldon_blogs - 博客园
https://www.cnblogs.com/blogs-of-lxl/p/16815102.html -
通过 configfs 配置的 Linux USB gadget - 闹闹爸爸 - 博客园
https://www.cnblogs.com/wanglouxiaozi/p/15131949.html -
Configfs - 用户空间驱动的内核对象配置 - 闹闹爸爸 - 博客园
https://www.cnblogs.com/wanglouxiaozi/p/15179719.html -
Linux USB gadget configured through configfs — The Linux Kernel documentation
https://www.kernel.org/doc/html/v5.3/usb/gadget_configfs.html