前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >USB总线-Linux内核USB3.0设备控制器复合设备之USB gadget configfs分析(七)

USB总线-Linux内核USB3.0设备控制器复合设备之USB gadget configfs分析(七)

作者头像
233333
发布2023-01-01 10:59:21
9.5K0
发布2023-01-01 10:59:21
举报

1.简介

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的绑定在内核中完成,若需要修改,则需要修改内核,不灵活也不方便。Linux3.11版本引入了基于configfs的usb gadget configfs。usb gadget configfs重新实现了复合设备层,使用者可以在用户空间配置和组合内核的function,灵活的构成USB复合设备,极大了提高了工作效率。

2.使用方法

下面的脚本通过usb gadget configfs配置uac2.0。在使用之前,内核需要开启UAC2.0相关配置。uac2.0的参数在/sys/kernel/config/usb_gadget/g1/configs/c.1/uac2.0文件中,可配置通道、格式、采样率等,下面的脚本使用默认配置。

代码语言:javascript
复制
#!/bin/bash
function start_uac2()
{
	# usb_gadget依赖于libcomposite模块
	modprobe libcomposite
	# 挂载config文件系统
	mount -t configfs none /sys/kernel/config

	# 创建g1目录,实例化一个新的gadget模板
	
	echo "mkdir /sys/kernel/config/usb_gadget/g1"
	mkdir -m 0770 /sys/kernel/config/usb_gadget/g1

	# 设置产品的VendorID、ProductID及USB规范版本号
	echo "Setting Vendor Product ID's and bcdDevice"
	echo 0x2207 > /sys/kernel/config/usb_gadget/g1/idVendor
	echo 0x0019 > /sys/kernel/config/usb_gadget/g1/idProduct
	# 设备版本号
	echo 0x0200 > /sys/kernel/config/usb_gadget/g1/bcdDevice
	# USB 1.1: 0x0110
	# USB 2.0: 0x0200, USB 2.1: 0x0210, USB 2.5: 0x0250
	# USB 3.0: 0x0300, USB 3.1: 0x0310, USB 3.2: 0x0320
	# echo 0x0210 > /sys/kernel/config/usb_gadget/g1/bcdUSB
	
	# 实例化英语ID,开发商、产品和序列号字符串写入内核
	echo "Setting English strings"
	mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/strings/0x409
	echo "0123456789ABCDEF" > /sys/kernel/config/usb_gadget/g1/strings/0x409/serialnumber
	echo "rockchip"  > /sys/kernel/config/usb_gadget/g1/strings/0x409/manufacturer
	echo "USB Audio Device"  > /sys/kernel/config/usb_gadget/g1/strings/0x409/product

	# Creating Config
	echo "Creating Config"
	mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/configs/c.1
	mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409
	echo "uac2" > /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409/configuration
	echo 500 > /sys/kernel/config/usb_gadget/g1/configs/c.1/MaxPower

	 
	# bind functions
	# uac2 need to put before uvc, otherwise uvc + uac2 enumerate failed in win10
	echo "Creating UAC2 gadget functionality : uac2.0"
	mkdir /sys/kernel/config/usb_gadget/g1/functions/uac2.0
	ln -s /sys/kernel/config/usb_gadget/g1/functions/uac2.0 /sys/kernel/config/usb_gadget/g1/configs/c.1

	# Binding USB Device Controller
	echo "Binding USB Device Controller"
	echo fe800000.dwc3 > /sys/kernel/config/usb_gadget/g1/UDC
}

function stop_uac2()
{
	# Disabling the gadget
	echo "Disabling the USB gadget"
	echo "" > /sys/kernel/config/usb_gadget/g1/UDC

	# Remove functions from configurations
	rm /sys/kernel/config/usb_gadget/g1/configs/c.1/uac2.0
	# Remove strings directories in configurations
	rmdir /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409
	# remove the configurations
	rmdir /sys/kernel/config/usb_gadget/g1/configs/c.1

	# Remove functions (function modules are not unloaded, though)
	rmdir /sys/kernel/config/usb_gadget/g1/functions/uac2.0

	# Remove strings directories in the gadget
	rmdir /sys/kernel/config/usb_gadget/g1/strings/0x409

	# finally remove the gadget
	rmdir /sys/kernel/config/usb_gadget/g1
}

usage()
{
    echo "Usage: ./usb-gadget-uac2.sh start|stop"
    echo " options:"
    echo "    start           start uac2.0 gadget"
    echo "    stop            stop uac2.0 gadget"
}

case $1 in
start)
	start_uac2
	;;
stop)
	stop_uac2
	;;
*)
	usage
	exit 1
	;;
esac
exit 0

执行完上述脚本后,/sys/kernel/config/usb_gadget/g1/目录下的文件组成如下所示。b开头的文件用来配置设备描述符。functions目录下的文件用于配置function驱动的参数。

代码语言:javascript
复制
├── bcdDevice
├── bcdUSB
├── bDeviceClass
├── bDeviceProtocol
├── bDeviceSubClass
├── bMaxPacketSize0
├── configs
│   └── c.1
│       ├── bmAttributes
│       ├── MaxPower
│       ├── strings
│       │   └── 0x409
│       │       └── configuration
│       └── uac2.0 -> ../../../../usb_gadget/g1/functions/uac2.0
├── functions
│   └── uac2.0
│       ├── c_chmask      # 录音通道掩码,默认0x3
│       ├── c_srate       # 录音采样率,默认64000
│       ├── c_ssize       # 录音一帧数据所占bit位,默认16bit
│       ├── p_chmask      # 播放通道掩码,默认0x3
│       ├── p_srate       # 播放采样率,默认48000
│       ├── p_ssize       # 播放一帧数据所占bit位,默认16bit
│       └── req_number    # 分配usb请求数量,默认2个
├── idProduct
├── idVendor
├── os_desc
│   ├── b_vendor_code
│   ├── qw_sign
│   └── use
├── strings
│   └── 0x409
│       ├── manufacturer
│       ├── product
│       └── serialnumber
└── UDC    # 用于设置绑定USB控制器的名称

3.代码分析

3.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目录。

代码语言:javascript
复制
[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_UEVENT
	android_class = class_create(THIS_MODULE, "android_usb");
	if (IS_ERR(android_class))
		return PTR_ERR(android_class);
#endif
	return ret;
}
module_init(gadget_cfs_init);

gadgets_make函数的主要工作内容是设置复合设备数据结构usb_composite_dev和复合设备驱动数据结构usb_composite_driver。 工作流程如下:

代码语言:javascript
复制
[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_UEVENT
	android_class = class_create(THIS_MODULE, "android_usb");
	if (IS_ERR(android_class))
		return PTR_ERR(android_class);
#endif
	return ret;
}
module_init(gadget_cfs_init);

gadgets_make函数的主要工作内容是设置复合设备数据结构usb_composite_dev和复合设备驱动数据结构usb_composite_driver。 工作流程如下:

  1. 分配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`。
  2. 初始化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宏定义。
  3. 初始化复合设备驱动数据结构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驱动沟通的桥梁,非常重要。
  4. 初始化复合设备数据结构usb_composite_dev,设置USB设备描述符。
  5. 设置gi->group的config_item_type指向gadget_root_type,usb gadget configfs初始化的时候首先调用gadget_root_type。
  6. 最后向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。
代码语言:javascript
复制
[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;   // 支持USB3.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目录,包含了操作系统信息,一般不需要设置。

代码语言:javascript
复制
[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,
};

3.2.主要调用流程分析

下面结合uac2.0用户空间配置脚本和内核中的USB gadget configfs代码,分析一下当用户空间配置时,内核中做了什么。重点关注驱动相关的执行流程。

3.2.1.创建配置

当在用户空间执行mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/configs/c.1命令时,会调用到configfs的config_desc_make函数。主要的工作流程为:

  1. 分配一个config_usb_cfg1结构体,该结构体包含了usb_configuration结构体,保存了该USB设备的配置信息。
  2. 设置USB设备的配置描述符的某些选项。如bConfigurationValue(根据创建配置目录的名称设置,如c.1,则设置为1)、MaxPower、bmAttributes。
  3. 向用户空间导出该配置属性文件,便于使用者设置。
  4. 调用usb_add_config_only函数将该配置挂到usb_composite_dev的configs链表。
image
image
3.2.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数据结构,执行流程如下:

  1. 调用gadget funcation API usb_get_function_instance函数,遍历func_list链表,根据名称进行匹配。用户空间输入的是uac2,则匹配uac2驱动。其定义在f_uac2.c文件中。
  2. 匹配成功回调function驱动定义的alloc_inst函数,对于uac2,则回调afunc_alloc_inst函数。
    1. 首先分配f_uac2_opts数据结构,内部包含了usb_function_instance。
    2. 设置uac2的config_item_type为f_uac2_func_type,f_uac2_func_type会在/sys/kernel/config/usb_gadget/g1/functions/uac2.0目录下导出设置uac2音频参数的属性文件,便于用户空间设置。
    3. 设置音频参数的默认值,使用者可在用户空间修改。
  3. 将获取到的uac2的usb_function_instance挂到gadget_info的available_func链表。
image
image
3.2.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数据结构,执行流程如下:

  1. 通过gadget funcation API,调用uac2驱动的afunc_alloc函数。
    1. 分配f_uac2数据结构,内部包含了usb_function。
    2. 填充usb_function数据结构,重要的是设置的回调函数,usb_gadget_driver在适当的时候会回调这些函数。如驱动绑定的时候会回调afunc_bind函数,解除绑定的时候会回调afunc_unbind函数,设置配置的时候会回调afunc_set_alt函数等等。
  2. 将获取到的uac2的usb_function挂到config_usb_cfg的func_list链表。
image
image
3.2.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驱动。执行流程如下:

  1. 判断输入的USB控制器名称,若输出为空或者是none,则解除usb_gadget_driver和底层USB控制器的绑定。反之调用usb_udc_attach_driver函数进行匹配USB设备控制器。
  2. 遍历udc_list链表,查找fe800000.dwc3 USB设备控制器。
  3. 找到对应的USB设备控制器,则保存绑定的usb_gadget_driver,即configfs_driver_template。
  4. 回调configfs_composite_bind函数。其主要的工作内容如下:
    1. 分配端点的usb_request、分配缓冲区、设置usb_request的回调函数、复位所有端点,并将gadget的端点数量清零。
    2. function驱动保存对应的配置,并回调function驱动的bind函数,对于uac2则回调 afunc_bind 函数。
    3. 如果使用os_string,则需要分配os_string requset。
  5. 调用UDC驱动接口usb_gadget_udc_start使能USB设备控制器。
  6. 调用UDC驱动接口usb_udc_connect_control连接USB主机控制器,这样USB主机就能识别并枚举USB设备。
image
image

4.总结

本节以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驱动,其实现又不同。本节没有涉及uac2驱动的工作流程及数据的收发过程,这些将再uac2驱动章节进行详细介绍。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-12-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.简介
  • 2.使用方法
  • 3.代码分析
    • 3.1.初始化
      • 3.2.主要调用流程分析
        • 3.2.1.创建配置
        • 3.2.2.获取function实例
        • 3.2.3.获取function
        • 3.2.4.驱动绑定
    • 4.总结
    相关产品与服务
    腾讯云代码分析
    腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档