Edit online

Gadget Driver (Configfs)

31 Jan 2024
Read time: 11 minute(s)

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