usb gadget configfs 原理

为啥要介绍usb gadget configfs

在之前的2篇文章来介绍并验证usb gadget configfs,为啥呢?因为现在主流配置usb gadget都是采用configfs通过用户空间进行配置,而不是像之前使用hardcode方式专门有个内核模块来配置,来看一下Linux usb gadget的发展历程:

  • David Brownell 在2003年之前引入gadget framework,只支持单一的gadget设备;
  • composite framework 在2008年的时候加入进来,用来支持多个function的gadget设备;
  • FunctionFS在2010年加入,允许用户空间创建function,并与内核寄存的function组成一个支持多个function的gadget设备;

但是上述还存在一个问题,就是还在自定义的内核模块中,将各个功能各个用例绑定在一起不够便利。因此,在Linux 3.11版本引入usb gadget configfs,支持一系列API,用户层可以通过该API定义任意功能和配置,从用户空间定义特定于应用程序的usb复合设备。
Androidadb驱动实现原理这篇文章中,我们使用传统的hardcode方式来创建一个android adb的usb复合设备。在usb gadget configfs 验证文章中,我们使用usb gadget configfs方式创建一个mass storage的usb复合设备。无论是使用哪种方式,都是为了创建一个usb复合设备。本文将详细介绍通过usb gadget configfs创建usb复合设备的原理。


为啥是configfs

configfs是基于内存的文件系统,它提供的功能与sysfs相反。sysfs是基于文件系统的kernel对象视图,configfs是基于文件系统的kernel对象管理器或config_items

  • sysfs,对象是在kernel中创建的,并在sysfs中注册,对象的属性就在sysfs中出现,允许拥护空间读取。某些属性允许通过用户空间写。最重要的一点是,所有的对象都在kernel中创建或销毁,由kernel控制其生命周期;
  • configfsconfig_group是一组共享相同属性和操作的config_items的集合。config_items是在用户空间通过mkdir显式的创建,使用rmdir销毁,在mkdir之后会出现对应的属性,允许在用户空间进行读写。与sysfs不同的是,这些对象的生命周期完全由用户空间驱动,kernel驱动必须响应这些。

在kernel内核中,Documentation/filesystems/configfs/configfs.txt详细的介绍了configfs原理,samples/configfs/configfs_sample.c介绍了如何使用configfs。


配置步骤

  • 创建gadget,所支持的Functions, Configurations and Strings;
  • 设置Strings,将Functions和Configurations链接起来;
  • 使能gadget;
ActionFileSystem Commands
Create Gadget/Functions/Configurations..Make Directory (mkdir)
Destroy Gadget/Functions/Configurations..Remove Directory (rmdir)
Set Value of AttributesWrite (echo)
Get Value of AttributesRead (cat)
Group FunctionsSymlink (ln -s)
Ungroup FunctionsRemove Symlink (rm)

以下内容将详细介绍使用usb gadget configfs的步骤。

使能相关的宏

想要使用usb gadget configfs,必须使能CONFIG_FSCONFIG_LIBCOMPOSITE的宏,为啥呢?前者提供configfs的API,后者提供USB Composite Framework的功能,当然也包括创建usb gadget configfs的功能。

drivers/usb/gadget/Makefile
1
2
3
obj-$(CONFIG_USB_LIBCOMPOSITE)  += libcomposite.o
libcomposite-y := usbstring.o config.o epautoconf.o
libcomposite-y += composite.o functions.o configfs.o u_f.o

drivers/usb/gadget/configfs.c这个文件就是USB Composite Framework基于configfs提供的API,用于创建usb gadget configfs。


挂载configfs

使用mount -t configfs none /config/命令进行挂载之后,在config/目录下就会自动生成一个usb_gadget/目录,站在configfs的角度,也就是创建一个名为usb_gadgetconfig_items的属性。这个是由于在configfs.c中调用了configfs_register_subsystem()函数注册了一个名为usb_gadget的configfs子系统。

drivers/usb/gadget/configfs.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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);


创建名为g1的usb复合设备

mkdir usb_gadget/g1创建名为g1(gadget 1)的usb复合设备,执行完毕之后,在g1/目录下会创建很多属性,这些属性在configfs.c文件中创建,执行了gadgets_make()函数,主要做了以下几件事:

  • 创建了包括gadget_root_type指定的属性,每个属性都定义了与之对应的_show()_store()函数,这样在用户空间就可以对其进行对应的读写。

    drivers/usb/gadget/configfs.c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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,
    };
  • 创建usb复合设备所需的configurationfunctions的属性。

  • 初始化struct usb_composite_driver composite的相关属性。

    drivers/usb/gadget/configfs.c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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;

    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;

配置PID和VID

实际上会调用idVendor_store()函数来往struct usb_device_descriptor中的idVendoridProduct写入对应的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static inline struct gadget_info *to_gadget_info(struct config_item *item)
{
return container_of(to_config_group(item), struct gadget_info, group);
}

#define GI_DEVICE_DESC_SIMPLE_W_u16(_name) \
static ssize_t gadget_dev_desc_##_name##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
u16 val; \
int ret; \
ret = kstrtou16(page, 0, &val); \
if (ret) \
return ret; \
to_gadget_info(item)->cdev.desc._name = cpu_to_le16p(&val); \
return len; \
}

#define GI_DEVICE_DESC_SIMPLE_RW(_name, _type) \
GI_DEVICE_DESC_SIMPLE_R_##_type(_name) \
GI_DEVICE_DESC_SIMPLE_W_##_type(_name)

GI_DEVICE_DESC_SIMPLE_RW(idVendor, u16);
GI_DEVICE_DESC_SIMPLE_RW(idProduct, u16);


创建并配置strings子目录

配置strings首先必须设置language,这里设置为0x0409表示使用的是en-us语言。


创建configuration和字符串

会调用config_desc_make()来填充struct usb_configuration的字段,比如mkdir usb_gadget/g1/configs/c.1,那么会将c写入到struct usb_configurationlabel字段,1写入到bConfigurationValue的字段表示有几个configurations。之后调用usb_add_config_only()将设置的configurations与特定的usb复合设备绑定起来。


创建functions

一个USB复合设备会有多个功能,每个功能由function来表示,所谓的function就是USB设备支持的功能。当执行mkdir functions/mass_storage.0的时候,会调用function_make()函数,调用usb_get_function_instance()函数传入mass_storage字段,将会从现有的function list中找到与之匹配的function。现有的function list是由drivers/usb/gadget/function/f_xxx.c中进行添加的,这就将f_xxx.c联系起来了。


将functions和configuration关联起来

执行ln -s usb_gadget/g1/functions/mass_storage.0 usb_gadget/g1/configs/c.1命令将新添加的mass_storagefunctions添加到configuration对应的function list中,表示当前usb复合设备中新增了一个function。这时候调用的是config_usb_cfg_link()函数。至此,functionsconfiguration关联起来了。接下来要将configuration与特定的UDC设备连接起来。


绑定到UDC,使能gadget

执行echo xxx > usb_gadget/g1/UDC命令,就会调用gadget_dev_desc_UDC_store()函数,这里面调用usb composite framework中的usb_gadget_probe_driver()函数将gadget driver与USB Controller Driver绑定,这里的Gadget Driver就是与我们创建的usb复合设备对应的驱动,接下来就会走一系列的bind流程。


总结

在上述的步骤中,我们通过usb gadget configfs做了以下几件事:

  • 创建了一个usb复合设备,以及创建该设备所需的configurationfunctions,并将两者绑定起来;
  • 创建与usb复合设备相对应的gadget driver
  • gadget driver与UDC绑定起来,使能usb设备。

参考资料

Title:usb gadget configfs 原理

Author:Victor Huang

Time:2020-02-04 / 19:02

Link:http://wowothink.com/6a85234b/

License: Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)