Gadget Driver (Configfs)
Gadget Device 支撑了核心 Gadget Api 的实现,而 Function Layer 又需要使用这些 Api。怎么样将两者适配起来?Gadget Driver 就是用来完成这项工作的。
目前存在两种风格的 Gadget Driver,其中包括:
-
Legacy。这是早期风格的 Gadget Driver,只能通过静态编译的方式指定使用哪些 Function。
-
Configfs。这是目前流行的 Gadget Driver,可以通过 configfs 文件系统,不用重新编译内核,动态的配置需要使用的 Function。
我们首先介绍 configfs 风格的 Gadget Driver。
Configfs 使用
首先从使用上体验一下 configfs 的便捷。例如创建一个 ACM Function:
// 1、挂载 configfs 文件系统。
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget
// 2、创建 g1 目录,实例化一个新的 gadget 模板 (composite device)。
mkdir g1
cd g1
// 3.1、定义 USB 产品的 VID 和 PID。
echo "0x1d6b" > idVendor
echo "0x0104" > idProduct
// 3.2、实例化英语语言 ID。(0x409 是 ID 美国英语,不是任意的,可以在 USBIF 网站上下载文档查询。)
mkdir strings/0x409
ls strings/0x409/
// 3.3、将开发商、产品和序列号字符串写入内核。
echo "0123456789" > strings/0x409/serialnumber
echo "AAAA Inc." > strings/0x409/manufacturer
echo "Bar Gadget" > strings/0x409/product
// 4、创建 `Function` 功能实例,需要注意的是,一个功能如果有多个实例的话,扩展名必须用数字编号。
mkdir functions/acm.GS0
// 5.1、创建一个 USB `Configuration` 配置实例:
mkdir configs/c.1
ls configs/c.1
// 5.2、定义配置描述符使用的字符串
mkdir configs/c.1/strings/0x409
ls configs/c.1/strings/0x409/
echo "ACM" > configs/c.1/strings/0x409/configuration
// 6、捆绑功能 `Function` 实例到 `Configuration` 配置 c.1
ln -s functions/acm.GS0 configs/c.1
// 7.1、查找本机可获得的 UDC 实例 (即 gadget device)
# ls /sys/class/udc/
10200000.usb
// 7.2、将 gadget 驱动注册到 UDC 上,插上 USB 线到电脑上,电脑就会枚举 USB 设备。
echo "10200000.usb" > UDC
Configfs 层次结构
configfs 并不是 gadget 专用的,它是一个通用文件系统,方便用户通过文件系统创建文件夹、文件的方式来创建内核对象。
configfs 是很好理解的, struct config_group
相当于一个文件夹, struct
config_item_type
是这个文件夹的属性集。其中
config_item_type->ct_group_ops->make_group()/drop_item()
定义了创建/销毁下一层子文件夹的方法, config_item_type->ct_attrs
定义了子文件和相关操作函数。
我们通过解析 drivers\usb\gadget\configfs.c
文件来深入理解
configfs
的使用方法:
-
首先创建首层文件夹
/sys/kernel/config/usb_gadget
:static struct configfs_group_operations gadgets_ops = { .make_group = &gadgets_make, .drop_item = &gadgets_drop, }; static const 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); return ret; } module_init(gadget_cfs_init);
-
创建
/sys/kernel/config/usb_gadget/g1
,相当于创建一个全新的composite device
。会调用顶层struct config_group
的config_item_type->ct_group_ops->make_group()
函数,即gadgets_make()
:static struct config_group *gadgets_make( struct config_group *group, const char *name) { struct gadget_info *gi; gi = kzalloc(sizeof(*gi), GFP_KERNEL); if (!gi) return ERR_PTR(-ENOMEM); /* (1) 创建顶层文件夹 `/sys/kernel/config/usb_gadget/g1` 对应的 `struct config_group` 结构 `/sys/kernel/config/usb_gadget/g1` 下对应不少子文件,在 gadget_root_type.ct_attrs 中定义,即 `gadget_root_attrs`: 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, &gadget_dev_desc_attr_max_speed, NULL, }; */ config_group_init_type_name(&gi->group, name, &gadget_root_type); /* (2) 创建子文件夹 `/sys/kernel/config/usb_gadget/g1/functions` `functions_type` 中定义了进一步创建子文件夹的操作函数 */ config_group_init_type_name(&gi->functions_group, "functions", &functions_type); configfs_add_default_group(&gi->functions_group, &gi->group); /* (3) 创建子文件夹 `/sys/kernel/config/usb_gadget/g1/configs` `config_desc_type` 中定义了进一步创建子文件夹的操作函数 */ config_group_init_type_name(&gi->configs_group, "configs", &config_desc_type); configfs_add_default_group(&gi->configs_group, &gi->group); /* (4) 创建子文件夹 `/sys/kernel/config/usb_gadget/g1/strings` `gadget_strings_strings_type` 中定义了进一步创建子文件夹的操作函数 */ config_group_init_type_name(&gi->strings_group, "strings", &gadget_strings_strings_type); configfs_add_default_group(&gi->strings_group, &gi->group); /* (5) 创建子文件夹 `/sys/kernel/config/usb_gadget/g1/os_desc` `os_desc_type` 中定义了进一步创建哪些子文件 */ config_group_init_type_name(&gi->os_desc_group, "os_desc", &os_desc_type); configfs_add_default_group(&gi->os_desc_group, &gi->group); /* (6) `configfs.c` 的目的很明确就是创建一个 `composite device` 由用户添加和配置这个 `device` 当中的多个 `interface` 即 `function` */ 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_PLUS; spin_lock_init(&gi->spinlock); mutex_init(&gi->lock); INIT_LIST_HEAD(&gi->string_list); INIT_LIST_HEAD(&gi->available_func); 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()); gi->composite.gadget_driver = configfs_driver_template; gi->composite.gadget_driver.function = kstrdup(name, GFP_KERNEL); gi->composite.name = gi->composite.gadget_driver.function; if (!gi->composite.gadget_driver.function) goto err; return &gi->group; err: kfree(gi); return ERR_PTR(-ENOMEM); }
-
创建
/sys/kernel/config/usb_gadget/g1/functions/acm.GS0
。会调用functions_type
中定义的 function_make() 函数:static struct config_group *function_make( struct config_group *group, const char *name) { struct gadget_info *gi; struct usb_function_instance *fi; char buf[MAX_NAME_LEN]; char *func_name; char *instance_name; int ret; ret = snprintf(buf, MAX_NAME_LEN, "%s", name); if (ret ≥ MAX_NAME_LEN) return ERR_PTR(-ENAMETOOLONG); /* (1) 把 `acm.GS0` 分割成两部分: func_name = `acm` instance_name = `GS0` */ func_name = buf; instance_name = strchr(func_name, '.'); if (!instance_name) { pr_err("Unable to locate . in FUNC.INSTANCE\n"); return ERR_PTR(-EINVAL); } *instance_name = '\0'; instance_name++; /* (2) 根据 func_name 在全局链表中查找对应 function usb_get_function_instance() → try_get_usb_function_instance() → fd->alloc_inst() → acm_alloc_instance(): 并调用 usb_function_driver->alloc_inst() 分配一个 function 实例 */ fi = usb_get_function_instance(func_name); if (IS_ERR(fi)) return ERR_CAST(fi); /* (3) 初始化 function 实例 */ ret = config_item_set_name(&fi->group.cg_item, "%s", name); if (ret) { usb_put_function_instance(fi); return ERR_PTR(ret); } if (fi->set_inst_name) { ret = fi->set_inst_name(fi, instance_name); if (ret) { usb_put_function_instance(fi); return ERR_PTR(ret); } } gi = container_of(group, struct gadget_info, functions_group); mutex_lock(&gi->lock); /* (4) 将 function 实例挂载到 composite device 的 function 链表当中去 */ list_add_tail(&fi->cfs_list, &gi->available_func); mutex_unlock(&gi->lock); return &fi->group; }
在
ln -s functions/acm.GS0 configs/c.1
时给 function 实例安装实际的函数:config_usb_cfg_link() → usb_get_function() → fi->fd->alloc_func() → acm_alloc_func(): static struct usb_function *acm_alloc_func(struct usb_function_instance *fi) { struct f_serial_opts *opts; struct f_acm *acm; /* (2.1) 对应分配一个 func 实例 */ acm = kzalloc(sizeof(*acm), GFP_KERNEL); if (!acm) return ERR_PTR(-ENOMEM); spin_lock_init(&acm->lock); /* (2.2) 初始化 func 实例的成员函数 */ acm->port.connect = acm_connect; acm->port.disconnect = acm_disconnect; acm->port.send_break = acm_send_break; acm->port.func.name = "acm"; acm->port.func.strings = acm_strings; /* descriptors are per-instance copies */ acm->port.func.bind = acm_bind; acm->port.func.set_alt = acm_set_alt; acm->port.func.setup = acm_setup; acm->port.func.disable = acm_disable; opts = container_of(fi, struct f_serial_opts, func_inst); acm->port_num = opts->port_num; acm->port.func.unbind = acm_unbind; acm->port.func.free_func = acm_free_func; acm->port.func.resume = acm_resume; acm->port.func.suspend = acm_suspend; return &acm->port.func; }
Gadget Driver
Configfs 风格的 gadget driver 的定义:
drivers\usb\gadget\configfs.c:
static const struct usb_gadget_driver configfs_driver_template = {
.bind = configfs_composite_bind,
.unbind = configfs_composite_unbind,
.setup = configfs_composite_setup,
.reset = configfs_composite_disconnect,
.disconnect = configfs_composite_disconnect,
.suspend = configfs_composite_suspend,
.resume = configfs_composite_resume,
.max_speed = USB_SPEED_SUPER_PLUS,
.driver = {
.owner = THIS_MODULE,
.name = "configfs-gadget",
},
.match_existing_only = 1,
};
在调用 echo "/sys/class/udc/10200000.usb" >
/sys/kernel/config/usb_gadget/g1/UDC
时,将上述 gadget
driver
进行注册,和 UDC 已经注册好的 gadget device
进行动态适配。
gadget_dev_desc_UDC_store() → usb_gadget_probe_driver(&gi->composite.gadget_driver) → udc_bind_to_driver()
本质上是 使用 configfs 创建好的 composite device
和 gadget
device
进行绑定:
gadget_dev_desc_UDC_store() → usb_gadget_probe_driver() → udc_bind_to_driver() → configfs_composite_bind() → usb_add_function() → function->bind() → acm_bind():
static int
acm_bind(struct usb_configuration *c, struct usb_function *f)
{
/* (1) 这样 function 实例和 gadget device 进行了绑定 */
struct usb_composite_dev *cdev = c->cdev;
struct f_acm *acm = func_to_acm(f);
/* allocate instance-specific endpoints */
/* (2) function 实例可以从 gadget device 中分配得到 endpoint */
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc);
if (!ep)
goto fail;
acm->port.in = ep;
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc);
if (!ep)
goto fail;
acm->port.out = ep;
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc);
if (!ep)
goto fail;
acm->notify = ep;
}
但是 bind() 以后 function 实例只是分配了 endpoint 资源还没有被启动,因为 Device 是被动状态,只有连上 Host,被 Host
Set Configuration
操作以后。某一组 Configuration
被配置,相应的 Function 实例
才会被启用:
dwc2_hsotg_complete_setup() → dwc2_hsotg_process_control() → hsotg->driver->setup() → configfs_composite_setup() → composite_setup() → set_config() → f->set_alt() → acm_set_alt():
static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
{
struct f_acm *acm = func_to_acm(f);
struct usb_composite_dev *cdev = f->config->cdev;
/* we know alt == 0, so this is an activation or a reset */
/* (1) 使能 endpoint,并且提交 `struct usb_request` 请求 */
if (intf == acm->ctrl_id) {
if (acm->notify->enabled) {
dev_vdbg(&cdev->gadget->dev,
"reset acm control interface %d\n", intf);
usb_ep_disable(acm->notify);
}
if (!acm->notify->desc)
if (config_ep_by_speed(cdev->gadget, f, acm->notify))
return -EINVAL;
usb_ep_enable(acm->notify);
} else if (intf == acm->data_id) {
if (acm->notify->enabled) {
dev_dbg(&cdev->gadget->dev,
"reset acm ttyGS%d\n", acm->port_num);
gserial_disconnect(&acm->port);
}
if (!acm->port.in->desc || !acm->port.out->desc) {
dev_dbg(&cdev->gadget->dev,
"activate acm ttyGS%d\n", acm->port_num);
if (config_ep_by_speed(cdev->gadget, f,
acm->port.in) ||
config_ep_by_speed(cdev->gadget, f,
acm->port.out)) {
acm->port.in->desc = NULL;
acm->port.out->desc = NULL;
return -EINVAL;
}
}
gserial_connect(&acm->port, acm->port_num);
} else
return -EINVAL;
return 0;
}