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