USB 驱动开发 --- Gadget 驱动框架梳理

编译链接

#----》 linux_5.10/drivers/usb/gadget/Makefile

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

obj-$(CONFIG_USB_GADGET)    += udc/ function/ legacy/

可知:

  • libcomposite 框架包含: composite、functions、configfs 等几个组件。
    ​ 注:类似 libphy框架,可再跟进比较。

  • gadget 框架包含: udc、function、legacy 三个主要组成。

    • legacy, 传统的 USB 设备功能配置方,主要依赖于 gadgetfs 接口;
    • function, 更加现代化和结构化的 USB 设备功能配置方式,它依赖于 configfs 接口;

注: Milkv Dus 内核配置文件cvitek_sg2000_milkv_duos_glibc_arm64_sd_defconfig中:CONFIG_USB_LIBCOMPOSITE=y,``CONFIG_USB_GADGET=y`,说明两个模块都内嵌到内核镜像中。

源码跟读

gadget 框架

Legacy,zero 驱动

对于 Legacy 编程模式,选择 zero 驱动跟读。

查看依赖关系

#----> linux_5.10/drivers/usb/gadget/legacy/Kconfig

config USB_ZERO
	tristate "Gadget Zero (DEVELOPMENT)"
	select USB_LIBCOMPOSITE											// 勾选 USB_LIBCOMPOSITE、 USB_F_SS_LB
	select USB_F_SS_LB

可知: Legacy 模式下的 Zere 驱动依赖 libcomposite 框架与 SS_LB Function 驱动。

...
g_zero-y               := zero.o
obj-$(CONFIG_USB_ZERO) += g_zero.o

查看配置状态

$ grep -wrn "CONFIG_USB_ZERO" linux_5.10/build/sg2000_milkv_duos_glibc_arm64_sd/.config
2643:# CONFIG_USB_ZERO is not set

Zero 驱动未纳入编译,修改如下:

diff --git a/build/boards/cv181x/sg2000_milkv_duos_glibc_arm64_sd/sg2000_milkv_duos_glibc_arm64_sd_defconfig b/build/boards/cv181x/sg2000_milkv_duos_glibc_arm64_sd/sg2000_milkv_duos_glibc_arm64_sd_defconfig
index d532054e6..361978f37 100644
--- a/build/boards/cv181x/sg2000_milkv_duos_glibc_arm64_sd/sg2000_milkv_duos_glibc_arm64_sd_defconfig
+++ b/build/boards/cv181x/sg2000_milkv_duos_glibc_arm64_sd/sg2000_milkv_duos_glibc_arm64_sd_defconfig
@@ -27,3 +27,4 @@ CONFIG_TARGET_PACKAGE_MTD-UTILS=y
 # CONFIG_TARGET_PACKAGE_RSYSLOG is not set
 CONFIG_TARGET_PACKAGE_BUSYBOX_SYSLOGD_SCRIPT=y
 CONFIG_TARGET_PACKAGE_NTP=y
+CONFIG_USB_ZERO=m

使用bear工具重新生成clangd依赖的 compile_commands.json ,附加处理:

sed -i /-mabi=lp64/d compile_commands.json 

由此可实现VScode + Clangd 插件下的代码跳转功能。跟读源码:

//----> linux_5.10/drivers/usb/gadget/legacy/zero.c

module_init(zero_driver_init);        // 内核模块初始化入口

module_usb_composite_driver(zero_driver);							// 宏展开内容如下:
static int __init zero_driver_init(void) {
    return usb_composite_probe(&zero_driver);

static struct usb_composite_driver zero_driver = {
	.name        = "zero",
	.dev         = &device_desc,
	.strings     = dev_strings,
	.max_speed   = USB_SPEED_SUPER,
	.bind        = zero_bind,										// Bind
	.unbind      = zero_unbind,
	.suspend     = zero_suspend,
	.resume      = zero_resume,
};

//----> linux_5.10/drivers/usb/gadget/composite.c

/**
 * usb_composite_probe() - register a composite driver
 ...
 */ 
int usb_composite_probe(struct usb_composite_driver *driver) {
	...
	driver->gadget_driver = composite_driver_template;              // 复制模板驱动(默认方法)
	gadget_driver = &driver->gadget_driver;                    

	gadget_driver->function    =  (char *) driver->name;            // 根据 usb_composite_driver 定义针对性的修改: function、driver.name、max_speed
	gadget_driver->driver.name = driver->name;
	gadget_driver->max_speed   = driver->max_speed;

	return usb_gadget_probe_driver(gadget_driver);                	// 将 usb_composite_driver 转换为 usb_gadget_driver 对象进行 probe

static const struct usb_gadget_driver composite_driver_template = {
    .bind         = composite_bind,
    .unbind       = composite_unbind,

    .setup        = composite_setup,
    .reset        = composite_disconnect,
    .disconnect   = composite_disconnect,

    .suspend      = composite_suspend,
    .resume       = composite_resume,

    .driver = {
        .owner    = THIS_MODULE,
    },
};

//----> linux_5.10/drivers/usb/gadget/udc/core.c
int usb_gadget_probe_driver(struct usb_gadget_driver *driver)
	struct         *udc = NULL;
	...
	mutex_lock(&udc_lock);                                          // 加锁保护,临界资源:udc_list、gadget_driver_pending_list
	if (driver->udc_name) { ... }                                   // 如果指定了目标 UDC 硬件,进行特殊 probe
	else {
		list_for_each_entry(udc, &udc_list, list) {            		// 遍历 udc 设备链表,如果第一个没有匹配到驱动的 udc 设备
			if (!udc->driver)
				goto found;    
	if (!driver->match_existing_only) {    ... }              		// 如果没有找到未匹配驱动的 UDC 且 未限定只适配已存在 UDC,则将当前驱动加入 gadget_driver_pending_list 链表, 等待下一次 probe
		list_add_tail(&driver->pending, &gadget_driver_pending_list);
	...
	return ret;
	found:
	ret = udc_bind_to_driver(udc, driver);                        	// 尝试绑定 UDC 设备 与 usb_gaget_driver
	...
}

//----> linux_5.10/drivers/usb/gadget/udc/core.c

static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver) {
	...
	udc->driver             = driver;								// 实际指向:composite_driver_template
	udc->dev.driver         = &driver->driver;
	udc->gadget->dev.driver = &driver->driver;    

	usb_gadget_udc_set_speed(udc, driver->max_speed);

	ret = driver->bind(udc->gadget, driver);                    	// Bind    阶段,实际指向 composite_driver_template 模板驱动中的 bind 方法:composite_bind
	...
	ret = usb_gadget_udc_start(udc);								// Stasrt  阶段,实际指向 UDC 设备 OPS 能力集中的 udc_start 方法:
		return udc->gadget->ops->udc_start(udc->gadget, udc->driver);
	...
	usb_udc_connect_control(udc);									// Connect 阶段,实际指向 UDC 设备 OPS 能力集中的 pullup 方法,控制上拉电阻连接
		if (udc->vbus)	
			usb_gadget_connect(udc->gadget);
				ret = gadget->ops->pullup(gadget, 1);
	... // OTG 相关,暂且忽略
	kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
	return 0;
err1:
    ...
    return ret;
}

//----> linux_5.10/drivers/usb/gadget/composite.c

static int composite_bind(struct usb_gadget *gadget, struct usb_gadget_driver *gdriver) {
	struct usb_composite_dev	*cdev;
    ...
    cdev = kzalloc(sizeof *cdev, GFP_KERNEL);                       // 申请 usb_composite_dev 设备
	struct usb_composite_driver	*composite = to_cdriver(gdriver);	// 实际指向 zero_driver 驱动, usb_composite_driver
	...
	spin_lock_init(&cdev->lock);
	cdev->gadget = gadget;
	set_gadget_data(gadget, cdev);
	INIT_LIST_HEAD(&cdev->configs);
	INIT_LIST_HEAD(&cdev->gstrings);

	status = composite_dev_prepare(composite, cdev) {
		struct usb_gadget *gadget = cdev->gadget;	

		cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);  // request 申请,对象 与 内存
			struct usb_request *req = ep->ops->alloc_request(ep, gfp_flags);
			...
			return req;
		cdev->req->buf = kmalloc(USB_COMP_EP0_BUFSIZ, GFP_KERNEL);  
		
		ret = device_create_file(&gadget->dev, &dev_attr_suspended);// 创建 suspended 节点
	
		cdev->req->complete = composite_setup_complete;             // request 补充,用于 complete 处理
		cdev->req->context  = cdev;
			
		gadget->ep0->driver_data = cdev;							// ep0 驱动私有数据
	
		cdev->driver = composite;                                   // 引用 zero_driver, 关联 usb_cmposite_dev 与 usb_composite_driver
	
		... // VBUS Drawn, 电流控制相关
		
		usb_ep_autoconfig_reset(gadget);
		
		return 0;
	}		
	...
	status = composite->bind(cdev);									// 实际指向 zero_driver 驱动中的 bind 方法:zero_bind, 可调整 cdev.use_os_string 等成员以影响后续流程
	...
	if (cdev->use_os_string) {                                      // 在 composite->bing 阶段被定义。
		status = composite_os_desc_req_prepare(cdev, gadget->ep0);
		...
	update_unchanged_dev_desc(&cdev->desc, composite->dev);         // 更新描述符, 从 zero_driver => cdev;
	...
	return 0;

fail:
	__composite_unbind(gadget, false);
	return status;	
}	

Legacy zero 驱动在模块入口 module_init 主动调用 usb_composite_probe(&zero_driver) 尝试与 UDC 设备绑定。
绑定由 udc_bind_to_driver 实现,过程又可分为三个步骤:

  1. Bind,gadget驱动私有的 bind 实现
    • Legacy 模式下由 composite_bind 代为实现,Function下
  2. Stasrt,配置 UDC,绑定 UDC 设备与 gadget 驱动;
  3. Connect,发起 UDC 设备连接;

回到 zero_driver 驱动,查看 bind 阶段的实现

//----> linux_5.10/drivers/usb/gadget/legacy/zero.c

static int zero_bind(struct usb_composite_dev *cdev) {
	...
	status = usb_string_ids_tab(cdev, strings_dev);                 // 更新 String 类描述符,主要涉及 ID 相关信息
	...
	device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id;    // VID、PID、SN号等基本信息更新
	device_desc.iProduct      = strings_dev[USB_GADGET_PRODUCT_IDX].id;
	device_desc.iSerialNumber = strings_dev[USB_GADGET_SERIAL_IDX].id;	
	...
	timer_setup(&autoresume_timer, zero_autoresume, 0);             // 自动唤醒定时器初始化
	
	func_inst_ss = usb_get_function_instance("SourceSink");         // Function 申请,目标:SourceSink
	
	ss_opts =  container_of(func_inst_ss, struct f_ss_opts, func_inst);	// Super-Speed 特性定义
	ss_opts->pattern       = gzero_options.pattern;
	ss_opts->isoc_interval = gzero_options.isoc_interval;	
	...
	func_ss = usb_get_function(func_inst_ss);						// Function 实例化,目标:SourceSink
	... // 次要 Function Loopback 实例化过程,忽略
	... // 唤醒相关
	if (gadget_is_otg(cdev->gadget)) {                              // OTG 相关
		...
	if (loopdefault) { ...											// 默认第一功能为 Sourcesink,其次 Loopback
	else {
		usb_add_config_only(cdev, &sourcesink_driver);
		usb_add_config_only(cdev, &loopback_driver);				// 添加 cdev 至配置表:sourcesink_driver、 loopback_driver
			... // cdev->configs 重复项检查
			config->cdev = cdev;									// config 关联 cdev
			list_add_tail(&config->list, &cdev->configs);			// cdev 关联 configs

			INIT_LIST_HEAD(&config->functions);
			config->next_interface_id = 0;							// interface 索引号
			memset(config->interface, 0, sizeof(config->interface));//   接口内容清空		
	status = usb_add_function(&sourcesink_driver, func_ss);         // 向 Sourcesink 添加 Function 实例
	... // 次要 Function Loopback 添加过程,忽略
	usb_ep_autoconfig_reset(cdev->gadget);                          // 更新端点配置	
	usb_composite_overwrite_options(cdev, &coverwrite);             // 更新 options
	
	INFO(cdev, "%s, version: " DRIVER_VERSION "\n", longname);

	return 0;	
	
err_free_otg_desc:	
	...
	return status;
}	

static struct usb_configuration sourcesink_driver = {
	.label                  = "source/sink",
	.setup                  = ss_config_setup,
	.bConfigurationValue    = 3,
	.bmAttributes           = USB_CONFIG_ATT_SELFPOWER,
	/* .iConfiguration      = DYNAMIC */
};

匹配步骤:

  1. 按名字查找已添加到 func_list 链表中的 Function;
  2. 查找到目标 function 则调用其驱动中的私有实例化函数指针(alloc_inst)创建实例;

涉及

  • functions 核心API,如: usb_get_function_instance、usb_get_function
  • composite 核心 API,如:usb_add_function;

具体 function 需要静态声明 function, 如 SourceSink 设备驱动

//----> linux_5.10/drivers/usb/gadget/functions.c

struct usb_function_instance *usb_get_function_instance(const char *name) {
	struct usb_function_driver *fd;
	struct usb_function_instance *fi;
	...
	fi = try_get_usb_function_instance(name);                       // 获取 function 实例
		mutex_lock(&func_lock);
		list_for_each_entry(fd, &func_list, list) {					// 遍历 func_list 链表
			if (strcmp(name, fd->name))								//   使用名字查找目标 Function, 当前涉及:SourceSink、Loopback
				continue;		
			...
			fi = fd->alloc_inst();									// 调用具体 function 的私有 alloc_inst 实现,如:source_sink_alloc_inst
			if (IS_ERR(fi)) ... else
				fi->fd = fd;										// 引用 function_driver 驱动
			break;
		...
	...
	return try_get_usb_function_instance(name);
}	

struct usb_function *usb_get_function(struct usb_function_instance *fi) {
	...
	f = fi->fd->alloc_func(fi);                                     // 调用具体 function 的私有 alloc_func 实现,如:source_sink_alloc_func
	...
	f->fi = fi;                                                     // 引用 function 实例
	
	return f;
}

function 驱动在 module_init 阶段先使用 usb_function_register 将自己添加至 func_list 链表。

function 私有实现:xxx.alloc_inst()、xxx.alloc_func(usb_function_instance), 以 SourceSink 为例:

//----> linux_5.10/drivers/usb/gadget/function/f_sourcesink.c

DECLARE_USB_FUNCTION(SourceSink, source_sink_alloc_inst, source_sink_alloc_func);
	.name = "SourceSink",				
	...
	.alloc_inst = source_sink_alloc_inst,
	.alloc_func = source_sink_alloc_func,			
}

static struct usb_function_instance *source_sink_alloc_inst(void) {
	struct f_ss_opts *ss_opts;

	ss_opts = kzalloc(sizeof(*ss_opts), GFP_KERNEL);                // 申请 struct f_ss_opts 对象
	...
	ss_opts->func_inst.free_func_inst = source_sink_free_instance;
	ss_opts->isoc_interval            = GZERO_ISOC_INTERVAL;	
	... // ss_opts 其他属性设定
	config_group_init_type_name(&ss_opts->func_inst.group, "", &ss_func_type);  // configfs 相关属性创建,如:bulk_buflen

	return &ss_opts->func_inst;                                     // 返回 function 实例(struct usb_function_instance),后面估计需要使用类 contains_of 方式转换到 ss_opts
}	

static struct configfs_attribute *ss_attrs[] = {
	&f_ss_opts_attr_pattern,
	&f_ss_opts_attr_isoc_interval,
	&f_ss_opts_attr_isoc_maxpacket,
	&f_ss_opts_attr_isoc_mult,
	&f_ss_opts_attr_isoc_maxburst,
	&f_ss_opts_attr_bulk_buflen,
	&f_ss_opts_attr_bulk_qlen,
	&f_ss_opts_attr_iso_qlen,
	NULL,
};

static struct usb_function *source_sink_alloc_func(struct usb_function_instance *fi) {
	struct f_sourcesink     *ss;
	...
	ss = kzalloc(sizeof(*ss), GFP_KERNEL);                          // 申请 soursink 实例
	...
	ss_opts =  container_of(fi, struct f_ss_opts, func_inst);		// 转换为 f_ss_opts 实例
	...
	ss->pattern       = ss_opts->pattern;
	ss->isoc_interval = ss_opts->isoc_interval;	
	... // 由 ss_opts 转录到 ss;
	
	ss->function.name      = "source/sink";
	ss->function.bind      = sourcesink_bind;
	ss->function.setup     = sourcesink_setup;
	ss->function.strings   = sourcesink_strings;
	ss->function.free_func = sourcesink_free_func;
	... // 其下 function  初始化

	return &ss->function;                                           // 返回 function 实例(struct usb_function),后面估计需要使用类 contains_of 方式转换到 f_ss
}	
  • ss_opts 主要与 configfs 交互,关键对象:struct config_item;
  • ss 主要与 composite 交互,与 ss_opts 仅在初始化 alloc_func 阶段完成转录;

composite 核心 API:usb_add_function;

//----> linux_5.10/drivers/usb/gadget/composite.c

int usb_add_function(struct usb_configuration *config, struct usb_function *function) {
	...
	function->config = config;
	list_add_tail(&function->list, &config->functions);             // function 记录到链表: 

	if (function->bind_deactivated) {                               // bind 过程中不回应枚举
		value = usb_function_deactivate(function);                  
		...
	if (function->bind) {                                           // function 的私有 bind 实现
		value = function->bind(config, function);                   //   调用具体 function 的私有 ;bind 实现,如:source_sink_alloc_func
		...
	if (!config->fullspeed && function->fs_descriptors)             // Full、High、Super、Super-Plus Speed 特性设定
		config->fullspeed = true;
	if (!config->highspeed && function->hs_descriptors)
		config->highspeed = true;
	...

	return value;
}	

至此,probe 的 bind 阶段告一段落。接下来是 start、connect 阶段

//----> linux_5.10/drivers/usb/gadget/udc/core.c

static inline int usb_gadget_udc_start(struct usb_udc *udc) {
	return udc->gadget->ops->udc_start(udc->gadget, udc->driver);   // 实际指向 UDC 驱动 ops 操作集的中 .udc_start 方法,对应:dwc2_hsotg_udc_start

//----> linux_5.10/drivers/usb/dwc2/gadget.c

static const struct usb_gadget_ops dwc2_hsotg_gadget_ops = {
	.udc_start      = dwc2_hsotg_udc_start,
	.pullup         = dwc2_hsotg_pullup,
	...
};

static int dwc2_hsotg_udc_start(struct usb_gadget *gadget, struct usb_gadget_driver *driver) {
	struct dwc2_hsotg *hsotg = to_hsotg(gadget);                    // dadget 设备转换为 私有对象
	... // 驱动合法性检查,含:非空、max_speed、driver.setup 方法非空
	driver->driver.bus = NULL;
	
	hsotg->driver             = driver;								// DWC2 绑定 usb_gadget_driver 
	hsotg->gadget.dev.of_node = hsotg->dev->of_node;
	hsotg->gadget.speed       = USB_SPEED_UNKNOWN;	

	if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) {                 // Duoble-Role, 即角色定义,当前做 PERIPHERAL 即 Device
		ret = dwc2_lowlevel_hw_enable(hsotg);
			int ret = __dwc2_lowlevel_hw_enable(hsotg);
				ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies);			
				ret = devm_add_action_or_reset(&pdev->dev, __dwc2_disable_regulators, hsotg);				
				if (hsotg->clk) {
					ret = clk_prepare_enable(hsotg->clk);				
				if (hsotg->uphy) {
					ret = usb_phy_init(hsotg->uphy);					
				} else if (hsotg->plat && hsotg->plat->phy_init) {
					ret = hsotg->plat->phy_init(pdev, hsotg->plat->phy_type);					
				} else {
					ret = phy_power_on(hsotg->phy);		
					if (ret == 0)
						ret = phy_init(hsotg->phy);
				}
	if (!IS_ERR_OR_NULL(hsotg->uphy))
		otg_set_peripheral(hsotg->uphy->otg, &hsotg->gadget);
		
	if (dwc2_hw_is_device(hsotg)) {
		dwc2_hsotg_init(hsotg);
		dwc2_hsotg_core_init_disconnected(hsotg, false);

	hsotg->enabled = 0;
	spin_unlock_irqrestore(&hsotg->lock, flags);

	gadget->sg_supported = using_desc_dma(hsotg);
	
	return ret;
}					
					
static void usb_udc_connect_control(struct usb_udc *udc) {
	if (udc->vbus)
		usb_gadget_connect(udc->gadget);
	else
		usb_gadget_disconnect(udc->gadget);

int usb_gadget_connect(struct usb_gadget *gadget) {		
	... 															// 如果不支持上拉电阻配置,则直接退出
	if (gadget->deactivated) {                                      // 如果设置了 deaactivated, 在 activation 之后 gadget 将会自动连接
			gadget->connected = true;
			goto out;
	ret = gadget->ops->pullup(gadget, 1);                           // 上拉电阻配置,具体由 UDC 驱动实现
out:
	trace_usb_gadget_connect(gadget, ret);							// 发起连接

	return ret;
}	

static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on) {	
	struct dwc2_hsotg *hsotg = to_hsotg(gadget);
	...
	if (is_on) {
		hsotg->enabled = 1;
		dwc2_hsotg_core_init_disconnected(hsotg, false);
		/* Enable ACG feature in device mode,if supported */
		dwc2_enable_acg(hsotg);					// active clock gating feature
		dwc2_hsotg_core_connect(hsotg);	
			dwc2_clear_bit(hsotg, DCTL, DCTL_SFTDISCON);		
	...
Function,sourcesink 驱动

对于 Function 编程模式,选择 loop 驱动跟读。
已知 Function 编程模式主要依赖 configfs 实现,所以参考内核文档``先展示使用示例:

  • linux_5.10/Documentation/usb/gadget-testing.rst
  • linux_5.10/Documentation/usb/gadget_configfs.rst

Kconfig 配置

config USB_CONFIGFS_F_LB_SS
	bool "Loopback and sourcesink function (for testing)"
	depends on USB_CONFIGFS
	select USB_F_SS_LB												# 选中 USB_F_SS_LB

对应 Makefile

#----> linux_5.10/drivers/usb/gadget/function/Makefile

...
usb_f_ss_lb-y             := f_loopback.o f_sourcesink.o
obj-$(CONFIG_USB_F_SS_LB) += usb_f_ss_lb.o

f_ss_lb Function 由 sourcesink + loopbak 两个组成,与 Legacy 模式相同,所以源码跟踪着重关注入口与使用差异

//-----> linux_5.10/drivers/usb/gadget/function/f_sourcesink.c

module_init(sslb_modinit);

static int __init sslb_modinit(void) {
	ret = usb_function_register(&SourceSinkusb_func);				// 调用 libcomposite API:usb_function_register 向 composite 注册 usb_gadget_driver;
	ret = lb_modinit();												// 一并完成 loopback 初始化
		return usb_function_register(&Loopbackusb_func);	
	...

int usb_function_register(struct usb_function_driver *newf) {
	struct usb_function_driver *fd;
	...
	mutex_lock(&func_lock);
	list_for_each_entry(fd, &func_list, list) {						// 遍历 func_list 链表,如果查找到同名驱动则退出
		if (!strcmp(fd->name, newf->name))							
			goto out;
	...
	list_add_tail(&newf->list, &func_list);							// 添加至 func_list 链表

DECLARE_USB_FUNCTION(, source_sink_alloc_inst, source_sink_alloc_func);		// 宏展开如下
static struct usb_function_driver SourceSinkusb_func = {
	.name       = "SourceSink",				
	.mod        = THIS_MODULE,					
	.alloc_inst = source_sink_alloc_inst,
	.alloc_func = source_sink_alloc_func,	
};								
MODULE_ALIAS("usbfunc:""SourceSink");

f_ss_lb Function 需要依托 configfs 实例化,参考内核文档 `` 测试步骤如下:

  1. 使用 configfs 在用户空间创建 gadget function;
  2. 使用测试用例 test-usb 测试;

使用 configfs 创建 gadget 设备示例如下:

# 加载 f_ss_lb 驱动
insmod /mnt/system/ko/usb_f_ss_lb.ko

# 变量声明
CVI_DIR=/tmp/usb
CVI_GADGET=$CVI_DIR/usb_gadget/cvitek
CVI_FUNC=$CVI_GADGET/functions
MANUFACTURER="Cvitek"
PRODUCT="USB Com Port"
SERIAL="0123456789"
VID=0x3346
PID=0x1003
CLASS=SourceSink
FUNC_NUM=0

# 环境初始化
mkdir $CVI_DIR

# configfs 挂载
mount none $CVI_DIR -t configfs

# 创建 Gadget 设备
mkdir $CVI_GADGET

# Gadget 设备初始化
echo $VID >$CVI_GADGET/idVendor
echo $PID >$CVI_GADGET/idProduct

# 信息初始化
mkdir $CVI_GADGET/strings/0x409
echo $MANUFACTURER>$CVI_GADGET/strings/0x409/manufacturer
echo $PRODUCT>     $CVI_GADGET/strings/0x409/product
echo $SERIAL>      $CVI_GADGET/strings/0x409/serialnumber

# 创建并关联 configuration 与 function 
mkdir $CVI_GADGET/configs/c.1
mkdir $CVI_GADGET/functions/$CLASS.usb$FUNC_NUM
ln -s $CVI_FUNC/$CLASS.usb$FUNC_NUM $CVI_GADGET/configs/c.1

# 配置 configuration
mkdir $CVI_GADGET/configs/c.1/strings/0x409
echo "config1">    $CVI_GADGET/configs/c.1/strings/0x409/configuration
echo 120>          $CVI_GADGET/configs/c.1/MaxPower
	
# 使能 Gadget 设备
UDC=`ls /sys/class/udc/ | awk '{print $1}'`
echo $UDC>       $CVI_GADGET/UDC

echo 4340000.usb > /tmp/usb/usb_gadget/cvitek/UDC

注:

  • 可配置信息参考内核文档,路径:Documentation/ABI/*/configfs-usb-gadget*
  • Shell 执行异常,$UDC 结果明文写入 $CVI_GADGET/UDC 节点,如:echo 4340000.usb > /tmp/usb/usb_gadget/cvitek/UDC

configfs 入口

//----> linux_5.10/drivers/usb/gadget/configfs.c

module_init(gadget_cfs_init);

static int __init gadget_cfs_init(void) {
	config_group_init(&gadget_subsys.su_group);						// 主要是链表初始化,暂且忽略
	ret = configfs_register_subsystem(&gadget_subsys);

	return ret;
}

static struct configfs_subsystem gadget_subsys = {
	.su_group = {
		.cg_item = {
			.ci_namebuf = "usb_gadget",
			.ci_type = &gadgets_type,
		}
	...
};
	
static const struct config_item_type gadgets_type = {
	.ct_group_ops   = &gadgets_ops,	
	...
};

static struct configfs_group_operations gadgets_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);
	...
	config_group_init_type_name(&gi->group, name, &gadget_root_type);

	config_group_init_type_name(&gi->functions_group, "functions", &functions_type);
	configfs_add_default_group(&gi->functions_group, &gi->group);
	
	config_group_init_type_name(&gi->configs_group, "configs", &config_desc_type);
	configfs_add_default_group(&gi->configs_group, &gi->group);

	config_group_init_type_name(&gi->strings_group, "strings", &gadget_strings_strings_type);
	configfs_add_default_group(&gi->strings_group, &gi->group);

	config_group_init_type_name(&gi->os_desc_group, "os_desc", &os_desc_type);
	configfs_add_default_group(&gi->os_desc_group, &gi->group);

	gi->composite.bind      = configfs_do_nothing;									// composite 驱动,接口方法实现
	gi->composite.unbind    = configfs_do_nothing;
	gi->composite.suspend   = NULL;
	gi->composite.resume    = NULL;
	gi->composite.max_speed = USB_SPEED_SUPER;
	...
	composite_init_dev(&gi->cdev);
	gi->cdev.desc.bLength         = USB_DT_DEVICE_SIZE;								// composite 设备,描述符初始化
	gi->cdev.desc.bDescriptorType = USB_DT_DEVICE;
	gi->cdev.desc.bcdDevice       = cpu_to_le16(get_default_bcdDevice());			//     bcdDevice 默认由 get_default_bcdDevice(), 即内核版本 LINUX_VERSION_CODE 第 0,2 Byte 决定
	
	gi->composite.gadget_driver          = configfs_driver_template;				// gadget 驱动,接口方法默认使用:configfs_driver_template
	gi->composite.gadget_driver.function = kstrdup(name, GFP_KERNEL);
	gi->composite.name                   = gi->composite.gadget_driver.function;
	...
}

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

function 创建实现,命令 mkdir $CVI_GADGET/functions/$CLASS.usb$FUNC_NUM,源码跟读:

static const struct config_item_type functions_type = {
	.ct_group_ops   = &functions_ops,
	.ct_owner       = THIS_MODULE,
};

static struct configfs_group_operations functions_ops = {
	.make_group     = &function_make,
	.drop_item      = &function_drop,
};

static struct config_group *function_make(struct config_group *group, const char *name) {
	char buf[MAX_NAME_LEN];
	char *func_name;
	char *instance_name;	
	...
	ret = snprintf(buf, MAX_NAME_LEN, "%s", name);									// 
	...
	func_name = buf;	
	instance_name = strchr(func_name, '.');	

	fi = usb_get_function_instance(func_name);						// 调用 usb_get_function_instance 创建 funcion 实例
	...
}

上述示例中,最关键一步为"使能 Gadget 设备",查找相关实现:

$ grep -wrn "UDC" linux_5.10/drivers/usb/gadget/
linux_5.10/drivers/usb/gadget/configfs.c:336:CONFIGFS_ATTR(gadget_dev_desc_, UDC);

源码跟读

//----> linux_5.10/drivers/usb/gadget/configfs.c

CONFIGFS_ATTR(gadget_dev_desc_, UDC);								// configfs 属性节点 UDC

static struct configfs_attribute *gadget_root_attrs[] = {
	&gadget_dev_desc_attr_bcdUSB,
	&gadget_dev_desc_attr_UDC,										// UDC 节点引用
	...
	
static ssize_t gadget_dev_desc_UDC_show(struct config_item *item, char *page) {	... 	// UDC Read 实现,暂且忽略

static ssize_t gadget_dev_desc_UDC_store(struct config_item *item, const char *page, size_t len) {
	struct gadget_info *gi = to_gadget_info(item);
	...

	name = kstrdup(page, GFP_KERNEL);
	... // 有效性检查及字符串处理
	mutex_lock(&gi->lock);

	if (!strlen(name)) {											// 输入空字符串 "" 则使用 unregister_gadget 将当前 Gadet 设备注销
		ret = unregister_gadget(gi);
		...
	} else {
		...
		gi->composite.gadget_driver.udc_name = name;
		ret = usb_gadget_probe_driver(&gi->composite.gadget_driver);// 使用 usb_gadget_probe_driver 尝试与 gadget 设备匹配
		...
	...
	return ret;
}	

此外调用的 usb_gadget_probe_driver 与上文跟读的 Lgacy zero 驱动相似(都是主动调用尝试与 UDC 设备绑定),所以后续的流程一致不做重复跟读。

内核还提供了在 Host 端的对应测试用例 testusb 。
在此不做展开,testusb 源码路径:linux_5.10/tools/usb/testusb.c;

对比
  • 驱动入口,直接入口都是 usb_gadget_probe_driver,前置路径不同

    • Legacy zere 驱动,使用宏 module_usb_composite_driver 定义模块入口并调用 usb_gadget_probe_driver
    • Function f_ss_lb 驱动,由 configfs 生成的 Gadget 设备中 UDC 节点 write 方法调用 usb_gadget_probe_driver
  • 驱动对象,两者都是继承的 usb_gadget_driver,封装程度不同

    • Legacy zero 驱动对象为 usb_composite_driver;
    • Function f_ss_lb 驱动对象为 usb_function_driver;
  • 接口方法,两者都基于 composite_{bind、setup, …} 连接上层

    • Legacy zero 借助 usb_gadget_driver 驱动模板(composite_driver_template) 修改后传入 usb_gadget_probe_driver 以接入 libcomposite 框架;
    • Function f_ss_lb 借助 usb_gadget_driver 驱动模板(configfs_driver_template) 修改后传入 usb_gadget_probe_driver 以接入 libcomposite 框架;

    如: composite_setup 在处理完基本功能外不会尝试执行上层的 setup 方法,包含:usb_function.setup、usb_configuration.setup;

总结
  1. Legacy zero 的方法更贴近 libcomposite 框架的(直接调用相关API),手段灵活,但开发难度相对较高;
  2. Function 集成度更高,可使用 configfs 方式高效开发、调试,但可配置内容受 function 实现的内容限定;

UDC 驱动

udc_list 链表由 USB 控制器以平台设备(of)接入系统并与驱动 probe 过程中注册到 USB composite driver 框架

#----> linux_5.10/drivers/usb/dwc2/platform.c

module_platform_driver(dwc2_platform_driver);

static struct platform_driver dwc2_platform_driver = {
	.driver = {
		.of_match_table = dwc2_of_match_table,
		...
	},
	.probe    = dwc2_driver_probe,
	...
};

static int dwc2_driver_probe(struct platform_device *dev)
	struct dwc2_hsotg *hsotg;
    ...
	hsotg = devm_kzalloc(&dev->dev, sizeof(*hsotg), GFP_KERNEL);	// 申请 hsotg 对象(也是 usb_gadget),DWC2 驱动中 dwc2_hsotg 结构体包含 usb_gadget 结构
	
	hsotg->dev = &dev->dev;	                                        // 关联 平台设备
	... // DMA 相关初始化
	hsotg->regs = devm_platform_get_and_ioremap_resource(dev, 0, &res); // IO 寄存器映射

	retval = dwc2_lowlevel_hw_init(hsotg);                          // 硬件底层初始化
	
	hsotg->irq = platform_get_irq(dev, 0);                          // 中断初始化
	retval = devm_request_irq(hsotg->dev, hsotg->irq, dwc2_handle_common_intr, IRQF_SHARED, dev_name(hsotg->dev), hsotg);
	
	hsotg->vbus_supply = devm_regulator_get_optional(hsotg->dev, "vbus");   // VBUS 初始化

	retval = dwc2_lowlevel_hw_enable(hsotg);                        // 底层硬件使能
	
	hsotg->needs_byte_swap = dwc2_check_core_endianness(hsotg);		// 数据流大小端转换配置
	
	retval = dwc2_get_dr_mode(hsotg);                               // USB 控制器角色配置
	
	... // 其他配置
	retval = dwc2_core_reset(hsotg, false);                         // 核心复位
	retval = dwc2_get_hwparams(hsotg);                              // 硬件参数更新

	dwc2_force_dr_mode(hsotg);                                      // USB 控制器角色切换
	retval = dwc2_init_params(hsotg);                               // 硬件参数更新
	... // 其他配置
#if IS_ENABLED(CONFIG_USB_ROLE_SWITCH)	
	retval = dwc2_drd_init(hsotg);	                                // 双角色功能初始化
#endif	
	if (hsotg->dr_mode != USB_DR_MODE_HOST) {                       // Gadget 初始化
		retval = dwc2_gadget_init(hsotg);  
		...
		hsotg->gadget_enabled = 1;									// 	标记 gadget_enabled = 1
	... // 其他唤醒相关配置
	... // Host 初始化
	platform_set_drvdata(dev, hsotg);
	hsotg->hibernated = 0;
	dwc2_debugfs_init(hsotg);

	if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)
		dwc2_lowlevel_hw_disable(hsotg);							// Gadget 失能
		
#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
    if (hsotg->gadget_enabled) {
        retval = usb_add_gadget_udc(hsotg->dev, &hsotg->gadget);	// 添加当前 udc 至 udc_list 链表, DWC2 驱动中 dwc2_hsotg 结构体包含 usb_gadget 结构
	
	... // proc 相关节点创建

	return 0;
}	

//----> linux_5.10/drivers/usb/dwc2/gadget.c

int dwc2_gadget_init(struct dwc2_hsotg *hsotg) {
	...
	hsotg->gadget.max_speed = USB_SPEED_HIGH;						// UDC(DWC2) 初始化配置
	hsotg->gadget.ops       = &dwc2_hsotg_gadget_ops;
	hsotg->gadget.name      = dev_name(dev);
	if (hsotg->dr_mode == USB_DR_MODE_OTG)
		hsotg->gadget.is_otg = 1;
	else if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)
		hsotg->op_state = OTG_STATE_B_PERIPHERAL;
	... // 其他配置
	ret = dwc2_hsotg_hw_cfg(hsotg);
		...
		hsotg->num_of_eps = hsotg->hw_params.num_dev_ep;			// UDC 控制器支持的端点数量
		
		hsotg->num_of_eps++;										// ep0 对象申请
		hsotg->eps_in[0]  = devm_kzalloc(hsotg->dev, sizeof(struct dwc2_hsotg_ep), GFP_KERNEL);		
		hsotg->eps_out[0] = hsotg->eps_in[0];						// IN & OUT 端点使用同一 端点对象

		cfg = hsotg->hw_params.dev_ep_dirs;
		for (i = 1, cfg >>= 2; i < hsotg->num_of_eps; i++, cfg >>= 2) {
			ep_type = cfg & 3;
			if (!(ep_type & 2)) { ...								// IN 端点创建
			if (!(ep_type & 1)) {									// OUT 端点创建

		hsotg->fifo_mem = hsotg->hw_params.total_fifo_size;			// FIFO 信息初始化
		hsotg->dedicated_fifos = hsotg->hw_params.en_multiple_tx_fifo;
		
		return 0;
	}

	hsotg->ctrl_buff = devm_kzalloc(hsotg->dev, DWC2_CTRL_BUFF_SIZE, GFP_KERNEL);	// 申请 ctrl_buff
	hsotg->ep0_buff  = devm_kzalloc(hsotg->dev, DWC2_CTRL_BUFF_SIZE, GFP_KERNEL);	// 申请 ep0_buff
	... // DMA 相关
	ret = devm_request_irq(hsotg->dev, hsotg->irq, dwc2_hsotg_irq, IRQF_SHARED, dev_name(hsotg->dev), hsotg);		// 中断申请{共享中断,},回调函数:dwc2_hsotg_irq
	
	INIT_LIST_HEAD(&hsotg->gadget.ep_list);
	hsotg->gadget.ep0 = &hsotg->eps_out[0]->ep;

	hsotg->ctrl_req = dwc2_hsotg_ep_alloc_request(&hsotg->eps_out[0]->ep, GFP_KERNEL);			
		struct dwc2_hsotg_req *req = kzalloc(sizeof(*req), flags);	// 申请 request 内存,用于 ep0
		INIT_LIST_HEAD(&req->queue);
		return &req->req;											// 由 dwc2_hsotg_req 封闭 usb_request
	}		
	
	for (epnum = 0; epnum < hsotg->num_of_eps; epnum++) {			// 遍历所有 端点,初始化 IN & OUT 端点
		if (hsotg->eps_in[epnum])
			dwc2_hsotg_initep(hsotg, hsotg->eps_in[epnum], epnum, 1);		
		if (hsotg->eps_out[epnum])
			dwc2_hsotg_initep(hsotg, hsotg->eps_out[epnum], epnum, 0);	

	dwc2_hsotg_dump(hsotg);											// DWC2 寄存器信息打印

	return 0;
}	

int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget) {
	return usb_add_gadget_udc_release(parent, gadget, NULL);
		usb_initialize_gadget(parent, gadget, release);
			dev_set_name(&gadget->dev, "gadget");
			...
			device_initialize(&gadget->dev);						// 添加 gadget 设备 Device 对象
		ret = usb_add_gadget(gadget);

int usb_add_gadget(struct usb_gadget *gadget) {
	struct usb_udc		*udc;
	...
	udc = kzalloc(sizeof(*udc), GFP_KERNEL);						// udc 设备申请
	
	device_initialize(&udc->dev);									// udc 设备实例化
	udc->dev.release = usb_udc_release;
	udc->dev.class   = udc_class;
	udc->dev.groups  = usb_udc_attr_groups;
	udc->dev.parent  = gadget->dev.parent;
	ret = dev_set_name(&udc->dev, "%s", kobject_name(&gadget->dev.parent->kobj));	
	ret = device_add(&gadget->dev);									// 添加 Gadget 设备,可触发 sysfs uevent 事件
	
	udc->gadget = gadget;											// 建立 Gadget 与 udc 关系映射
	gadget->udc = udc;

	mutex_lock(&udc_lock);
	list_add_tail(&udc->list, &udc_list);							// 添加当前 udc 至 udc_list 链表

	ret = device_add(&udc->dev);									// 添加 UDC 设备
	usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED);
	udc->vbus = true;												// 置位 UDC 设备 VBUS

	/* pick up one of pending gadget drivers */
	ret = check_pending_gadget_drivers(udc);						// 检查当前 UDC 设备是否有匹配的 gadget_driver
中断相关

DWC2 控制器驱动使用 devm_request_irq 申请了两个中断(共享)

  • dwc2_handle_common_intr,会话类型中断,probe 阶段 ;
  • dwc2_hsotg_irq, 数据收发中断,probe.gadget_init 阶段;
//----> linux_5.10/drivers/usb/dwc2/gadget.c

static irqreturn_t dwc2_hsotg_irq(int irq, void *pw) {
	...
	if (!dwc2_is_device_mode(hsotg))								// 仅当 Device 模式时处理中断
		return IRQ_NONE;

	spin_lock(&hsotg->lock);										// 加锁
irq_retry:
	gintsts = dwc2_readl(hsotg, GINTSTS);							// 读取中断寄存器 状态 与 掩码
	gintmsk = dwc2_readl(hsotg, GINTMSK);	
	gintsts &= gintmsk;

	if (gintsts & ... 												// 其他类型中断 
	if (gintsts & GINTSTS_ENUMDONE) {								// 枚举中断 
		dwc2_writel(hsotg, GINTSTS_ENUMDONE, GINTSTS);
		dwc2_hsotg_irq_enumdone(hsotg);	
	if (gintsts & (GINTSTS_OEPINT | GINTSTS_IEPINT)) {				// IN&OUT 中断 
		...
		for (ep = 0; ep < hsotg->num_of_eps && daint_out; ep++, daint_out >>= 1) {
			if (daint_out & 1) dwc2_hsotg_epint(hsotg, ep, 0);
		for (ep = 0; ep < hsotg->num_of_eps  && daint_in; ep++, daint_in >>= 1) {
			if (daint_in & 1)   dwc2_hsotg_epint(hsotg, ep, 1);
	... // 其他中断类型处理
	if (gintsts & IRQ_RETRY_MASK && --retry_count > 0)				// 如果中断类型涉及 FIFO,进行 重试
		goto irq_retry;	
	... // 其他处理
	return IRQ_HANDLED;	
}

static void dwc2_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, int dir_in) {
	...
	ints = dwc2_gadget_read_ep_interrupts(hsotg, idx, dir_in);		// 读中断状态
	dwc2_writel(hsotg, ints, epint_reg);							// 清中断 

	if (ints & DXEPINT_XFERCOMPL) {									// 收发完成中断 
		if (using_desc_dma(hsotg) && hs_ep->isochronous) {			//  ISO 异步包
			...
		} else if (dir_in) {		
			if (hs_ep->isochronous && hs_ep->interval > 1) ... 		// iso 包处理
			dwc2_hsotg_complete_in(hsotg, hs_ep);					// IN 包 complete 处理
			if (idx == 0 && !hs_ep->req)							// ep0 端点0 的包
				dwc2_hsotg_enqueue_setup(hsotg);					// IN 包 enqueue  处理
		} else if (using_dma(hsotg)) {
			...
			dwc2_hsotg_handle_outdone(hsotg, idx);					// OUT 包处理
		}
	if (ints & ...	// 其他中断类型处理
}
	
//----> linux_5.10/drivers/usb/dwc2/core_intr.c

irqreturn_t dwc2_handle_common_intr(int irq, void *dev) {
	struct dwc2_hsotg *hsotg = dev;
	...
	
	spin_lock(&hsotg->lock);
	
	if (!dwc2_is_controller_alive(hsotg)) { ...                     // 检查 dwc 控制器是否工作正常
	
	if (dwc2_is_device_mode(hsotg))                                 // Device 模式下,读取 帧数
		hsotg->frame_number = (dwc2_readl(hsotg, DSTS) & DSTS_SOFFN_MASK) >> DSTS_SOFFN_SHIFT;	
	else ...
	
	gintsts = dwc2_read_common_intr(hsotg);                         // 读取中断状态

	if (hsotg->hibernated) { ...                                    // hiberante 处理	
	
	if (gintsts & GINTSTS_SESSREQINT)                               // 会话请求型 中断 
		dwc2_handle_session_req_intr(hsotg);
	if (gintsts & GINTSTS_PRTINT) {                                 // 端口打印型 中断
		if (dwc2_is_device_mode(hsotg)) {
			dwc2_handle_usb_port_intr(hsotg);
			retval = IRQ_HANDLED;			
	... // 其他中断类型处理			
out:
	spin_unlock(&hsotg->lock);
	return retval;
}				

static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) {
	...
	dwc2_writel(hsotg, GINTSTS_SESSREQINT, GINTSTS);
	
	if (dwc2_is_device_mode(hsotg)) {	
		if (hsotg->lx_state == DWC2_L2) {
			if (hsotg->in_ppd) {
				ret = dwc2_exit_partial_power_down(hsotg, 0, true);	
			if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended)
				dwc2_gadget_exit_clock_gating(hsotg, 0);				
			dwc2_hsotg_disconnect(hsotg);
	...
}	
数据包收发
//----> linux_5.10/drivers/usb/dwc2/gadget.c

/**
 * dwc2_hsotg_enqueue_setup - start a request for EP0 packets
 ...
 * Enqueue a request on EP0 if necessary to received any SETUP packets
 * received from the host.
 *
 * 如果需要,可以向 EP0 端点 提交一个请求以便接收来自 Host 的 SETIUP 包。
 *
 */ 
static void dwc2_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg)		// 空 req 入列
	struct usb_request *req = hsotg->ctrl_req;		
	struct dwc2_hsotg_req *hs_req = our_req(req);
	...
	req->zero     = 0;
	req->length   = 8;
	req->buf      = hsotg->ctrl_buff;
	req->complete = dwc2_hsotg_complete_setup;						// complete 处理

	hsotg->eps_out[0]->dir_in   = 0;
	hsotg->eps_out[0]->send_zlp = 0;
	hsotg->ep0_state            = DWC2_EP0_SETUP;

	ret = dwc2_hsotg_ep_queue(&hsotg->eps_out[0]->ep, req, GFP_ATOMIC);	
	...
}

static void dwc2_hsotg_complete_setup(struct usb_ep *ep, struct usb_request *req) {
	struct dwc2_hsotg_ep *hs_ep = our_ep(ep);
	struct dwc2_hsotg *hsotg = hs_ep->parent;
	...
	spin_lock(&hsotg->lock);
	if (req->actual == 0)					
		dwc2_hsotg_enqueue_setup(hsotg);							// 对 空包 继续等待
	else
		dwc2_hsotg_process_control(hsotg, req->buf);				// 对 非空包 上交处理

static int dwc2_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags) {
	struct dwc2_hsotg_req *hs_req = our_req(req);
	struct dwc2_hsotg_ep *hs_ep = our_ep(ep);
	struct dwc2_hsotg *hs = hs_ep->parent;
	...
	first = list_empty(&hs_ep->queue);
	list_add_tail(&hs_req->queue, &hs_ep->queue);
	...
	if (first) {
		...
		if (hs_ep->target_frame != TARGET_FRAME_INITIAL)
			dwc2_hsotg_start_req(hs, hs_ep, hs_req, false);
	}
	return 0;
}	

/**
 * dwc2_hsotg_process_control - process a control request
 * 
 * The controller has received the SETUP phase of a control request, and
 * needs to work out what to do next (and whether to pass it on to the
 * gadget driver).
 * 
 * UDC 已经收到 SETUP 阶段的控制包请求,并判断后续动作(是否需要将其上报给 Gadget 驱动)
 */
static void dwc2_hsotg_process_control(struct dwc2_hsotg *hsotg, struct usb_ctrlrequest *ctrl) {
	struct dwc2_hsotg_ep *ep0 = hsotg->eps_out[0];
	...
	if (ctrl->wLength == 0) {										// 包类型确认
		ep0->dir_in = 1;
		hsotg->ep0_state = DWC2_EP0_STATUS_IN;
	} else if (ctrl->bRequestType & USB_DIR_IN) {
		ep0->dir_in = 1;
		hsotg->ep0_state = DWC2_EP0_DATA_IN;
	} else {
		ep0->dir_in = 0;
		hsotg->ep0_state = DWC2_EP0_DATA_OUT;
	}	

	if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
		switch (ctrl->bRequest) {
		case USB_REQ_SET_ADDRESS: ...
			hsotg->connected = 1;
			dcfg  = dwc2_readl(hsotg, DCFG);
			dcfg &= ~DCFG_DEVADDR_MASK;
			dcfg |= (le16_to_cpu(ctrl->wValue) << DCFG_DEVADDR_SHIFT) & DCFG_DEVADDR_MASK;
			dwc2_writel(hsotg, dcfg, DCFG);
	
			ret = dwc2_hsotg_send_reply(hsotg, ep0, NULL, 0);		// 发送回应包
			return;
		... // 其他case,含:USB_REQ_GET_STATUS、USB_REQ_CLEAR_FEATURE、USB_REQ_SET_FEATURE

	if (ret == 0 && hsotg->driver) {
		spin_unlock(&hsotg->lock);
		ret = hsotg->driver->setup(&hsotg->gadget, ctrl);			// 将其他类型包上报给 Gadget 驱动处理(无论 Legace、Fuction模式,最终都是 composite_setup 再到 上层 setup)
	...
}

/**
 * dwc2_hsotg_start_req - start a USB request from an endpoint's queue
 ...
 * Start the given request running by setting the endpoint registers
 * appropriately, and writing any data to the FIFOs.
 */
static void dwc2_hsotg_start_req(struct dwc2_hsotg *hsotg, struct dwc2_hsotg_ep *hs_ep, struct dwc2_hsotg_req *hs_req, bool continuing) {
	struct usb_request *ureq = &hs_req->req;
	...
	dma_reg    = dir_in ? DIEPDMA(index)  : DOEPDMA(index);
	epctrl_reg = dir_in ? DIEPCTL(index)  : DOEPCTL(index);
	epsize_reg = dir_in ? DIEPTSIZ(index) : DOEPTSIZ(index);
	...
	length = ureq->length - ureq->actual;
	... // zlp(zero length packet) 包处理
	/* store the request as the current one we're doing */
	hs_ep->req = hs_req;											// 使用当前控制器的 req
	if (using_desc_dma(hsotg)) { ...								// desc_dma 相关
	} else {
		dwc2_writel(hsotg, epsize, epsize_reg);						// 写数据
		...
	if (hs_ep->isochronous && hs_ep->interval == 1) { ...			// 同步传输,包号更新处理
	
	ctrl |= DXEPCTL_EPENA;	/* ensure ep enabled */			
	if (!(index == 0 && hsotg->ep0_state == DWC2_EP0_SETUP))
		ctrl |= DXEPCTL_CNAK;	/* clear NAK set by core */
	dwc2_writel(hsotg, ctrl, epctrl_reg);							// ctrl 相关配置

	hs_ep->size_loaded = length;
	hs_ep->last_load   = ureq->actual;  							// 更新端点 FIFO信息

	if (dir_in && !using_dma(hsotg)) {								// 非 DMA 方式则直接向 FIFO 写入数据
		hs_ep->fifo_load = 0;
		dwc2_hsotg_write_fifo(hsotg, hs_ep, hs_req);		

	dwc2_hsotg_ctrl_epint(hsotg, hs_ep->index, hs_ep->dir_in, 1);	// 使能端点中断
}

摘要:

  • UDC 作为 Gadget 初始化时,一并负责了 ep0 端点的初始化;
  • DWC2 控制器状态参考枚举 dwc2_lx_state 可知: DWC2_L2 为 Suspend 状态;
  • 使用 DECLARE_USB_FUNCTION 声明作用
    • MODULE_ALIAS(“usbfunc:”“SourceSink”),对应 modprobe 时的 request_module(“usbfunc:%s”, name);
    • usb_get_function_instance 时通过 .name 匹配目标驱动,使用 .alloc_inst 实例化;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/947625.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pd虚拟机 Parallels Desktop 20 for Mac 安装教程【支持M芯片】

文章目录 效果图一、下载软件二、安装运行⚠️注意事项&#xff1a;1、前往 系统设置–> 隐私与安全性 –> 完整磁盘访问权限&#xff0c;中允许终端&#xff1a;2、安装运行【ParallelsDesktop-20.1.2-55742.dmg】&#xff0c;运行【安装.app】3、将【Patch】文件夹拖到…

回归预测 | MATLAB实现CNN-GRU卷积门控循环单元多输入单输出回归预测

回归预测 | MATLAB实现CNN-GRU卷积门控循环单元多输入单输出回归预测 目录 回归预测 | MATLAB实现CNN-GRU卷积门控循环单元多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现CNN-GRU卷积门控循环单元多输入单输出回归预测 数据准备&#x…

JAVA创建绘图板JAVA构建主窗口鼠标拖动来绘制线条

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c; 忍不住分享一下给大家。点击跳转到网站 学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把…

CSS层叠样式表

目标 能够说出什么是CSS能够使用CSS基础选择器能够设置字体样式能够设置文本样式能够说出CSS的三种引入方式能够使用Chrome调试工具调试样式 目录 CSS简介CSS基础选择器CSS字体属性CSS文本属性CSS的引入方式综合案例Chrome调试工具 1.1 HTML的局限性 说起HTML&#xff0c;…

Win32汇编学习笔记03.RadAsm和补丁

Win32汇编学习笔记03.RadAsm和补丁-C/C基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net 扫雷游戏啊下补丁 在扫雷游戏中,点关闭弹出一个确认框,确认之后再关闭,取消就不关闭 首先第一步就是确认关闭按钮响应的位置,一般都是 WM_CLOSE 的消息 ,消息响应一般都在过…

深入Android架构(从线程到AIDL)_08 认识Android的主线程

目录 3、 认识Android的主线程(又称UI线程) 复习&#xff1a; 各进程(Process)里的主线程​编辑 UI线程的责任&#xff1a; 迅速处理UI事件 举例 3、 认识Android的主线程(又称UI线程) 复习&#xff1a; 各进程(Process)里的主线程 UI线程的责任&#xff1a; 迅速处理UI事…

4.CSS文本属性

4.1文本颜色 div { color:red; } 属性值预定义的颜色值red、green、blue、pink十六进制#FF0000,#FF6600,#29D794RGB代码rgb(255,0,0)或rgb(100%,0%,0%) 4.2对齐文本 text-align 属性用于设置元素内文本内容的水平对齐方式。 div{ text-align:center; } 属性值解释left左对齐ri…

数据挖掘——支持向量机分类器

数据挖掘——支持向量机分类器 支持向量机最小间隔面推导基于软间隔的C-SVM非线性SVM与核变换常用核函数 支持向量机 根据统计学习理论&#xff0c;学习机器的实际风险由经验风险值和置信范围值两部分组成。而基于经验风险最小化准则的学习方法只强调了训练样本的经验风险最小…

慧集通iPaaS集成平台低代码培训-基础篇

训练使用素材&#xff1a; 1.数据源&#xff1a; 单号业务日期工厂仓库物料单位数量批次0100012022-5-1210031001030001kg500202304150100012022-5-1210031001030001kg122202304150100012022-5-1210031001030001kg1250202304150100012022-5-1210031001030002kg130202304110100…

深入理解计算机系统—虚拟内存(一)

一个系统中的进程是与其他进程共享 CPU 和主存资源的。然而&#xff0c;共享主存会形成特殊的挑战。随着对 CPU 需求的增长&#xff0c;进程以某种合理的平滑方式慢了下来。但是如果太多的进程需要太多的内存&#xff0c;那么它们中的一些就根本无法运行。 为了更加有效地管理内…

Unresolved plugin: ‘org.apache.maven.plugins:maven-site-plugin:3.12.1‘

问题 使用idea 社区办加载项目提示下面问题&#xff1a; Unresolved plugin: org.apache.maven.plugins:maven-site-plugin:3.12.1 问题解决 maven插件地址&#xff1a; https://maven.apache.org/plugins/maven-dependency-plugin/plugins.html Maven 中央仓库地址&#…

如何在 Windows 10/11 上录制带有音频的屏幕 [3 种简单方法]

无论您是在上在线课程还是参加在线会议&#xff0c;您都可能需要在 Windows 10/11 上录制带有音频的屏幕。互联网上提供了多种可选方法。在这里&#xff0c;本博客收集了 3 种最简单的方法来指导您如何在 Windows 10/11 上使用音频进行屏幕录制。请继续阅读以探索&#xff01; …

spring中使用@Validated,什么是JSR 303数据校验,spring boot中怎么使用数据校验

文章目录 一、JSR 303后台数据校验1.1 什么是 JSR303&#xff1f;1.2 为什么使用 JSR 303&#xff1f; 二、Spring Boot 中使用数据校验2.1 基本注解校验2.1.1 使用步骤2.1.2 举例Valid注解全局统一异常处理 2.2 分组校验2.2.1 使用步骤2.2.2 举例Validated注解Validated和Vali…

AWS K8s 部署架构

Amazon Web Services&#xff08;AWS&#xff09;提供了一种简化的Kubernetes&#xff08;K8s&#xff09;部署架构&#xff0c;使得在云环境中管理和扩展容器化应用变得更加容易。这个架构的核心是AWS EKS&#xff08;Elastic Kubernetes Service&#xff09;&#xff0c;它是…

设计模式 结构型 适配器模式(Adapter Pattern)与 常见技术框架应用 解析

适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许将一个类的接口转换成客户端所期望的另一个接口&#xff0c;从而使原本因接口不兼容而无法一起工作的类能够协同工作。这种设计模式在软件开发中非常有用&#xff0c;尤其是在需要集成…

MCU芯片是什么意思_有哪些作用?

MCU(Microcontroller Unit)芯片&#xff0c;即微控制单元&#xff0c;是一种集成了中央处理器(CPU)、存储器(ROM、RAM)以及各种外设接口(如输入输出引脚、定时器、串口等)的集成电路芯片。它通过超大规模集成电路技术&#xff0c;将具有数据处理能力的中央处理器、随机存储器、…

如何免费解锁 IPhone 网络

您是否担心 iPhone 上的网络锁定&#xff1f;如果您的 iPhone 被锁定到特定运营商&#xff0c;解锁它可以连接到不同的运营商。好吧&#xff0c;我们为您准备了一份指南。 iPhone运营商免费解锁将是小菜一碟。在我们的解锁运营商 iphone 免费指南中。我们为您提供了一份简介&am…

Spring Security(maven项目) 3.0.2.4版本

前言&#xff1a; 通过实践而发现真理&#xff0c;又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识&#xff0c;又从理性认识而能动地指导革命实践&#xff0c;改造主观世界和客观世界。实践、认识、再实践、再认识&#xff0c;这种形式&#xff0c;循环往…

计算机的错误计算(二百)

摘要 用三个大模型计算 exp(123.456). 结果保留10位有效数字。三个大模型的输出均是错误的&#xff0c;虽然其中一个给出了正确的 Python代码。 例1. 计算 exp(123.456). 保留10位有效数字。 下面是与第一个大模型的对话。 以上为与一个大模型的对话。 下面是与另外一个大模…

Golang的缓存一致性策略

Golang的缓存一致性策略 一致性哈希算法 在Golang中&#xff0c;缓存一致性策略通常使用一致性哈希算法来实现。一致性哈希算法能够有效地解决缓存节点的动态扩容、缩容时数据重新分布的问题&#xff0c;同时能够保证数据访问的均衡性。 一致性哈希算法的核心思想是将节点的哈希…