Linux: USB Gadget 驱动简介

2023-11-13

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 背景

本文所有分析基于 Linux 4.14 内核代码。

3. USB Gadget 驱动

3.1 什么是 USB Gadget 驱动?

USB 设备驱动,按照设备端关联的 USB 控制器 是工作在 主模式 还是 从模式,分为 USB 设备主机侧驱动 (主模式),或者 USB 设备从机侧驱动 (从模式)。同时,工作在 主模式USB 控制器,称为 USB 主机控制器 (UHC: USB Host Controller),工作在 从模式USB 控制器,称为 USB 设备控制器 (UDC: USB Device Controller)。有的 USB 控制器,只能工作在 主模式从模式 中的某一种;而有的则既可以工作在 主模式,也可以工作在 从模式,模式通过 OTG 切换。当然,在同一时刻,USB 控制器 要么工作在 主模式,要么工作在 从模式
本文的重点是 USB 设备从机侧驱动 (从模式),Linux 下将 USB 设备从机侧驱动 ,称为 USB Gadget 驱动USB Gadget 驱动 是通过 USB 来模拟其它类型的设备,如 USB Gadget UAC 驱动 用来模拟声卡外设;USB Gadget Serial 驱动 用来模拟串口外设,等等等等。这里所谓模拟,是指通过 USB 来模拟这些设备的行为,而这些对于连接对端的 USB 主机 是透明的。对于 USB Gadget 驱动 ,类似于譬如像 U 盘设备的固件,但它们并不完全等同,因为毕竟只是通过 USB 模拟设备行为。

3.2 USB Gadget 驱动框架

正如本小节中框图所示,USB Gadget 驱动,包括 USB 设备控制器(UDC) 驱动Gadget 功能(function)驱动 两大部分。其中 USB 设备控制器(UDC) 驱动 负责 USB 设备控制器(UDC) 和 主机侧 USB 控制器(UHC) 之间的数据传输;而 Gadget 功能驱动(function) 负责实现功能协议(如 UDC 等)。USB 设备控制器(UDC) 驱动Gadget 功能驱动(function) 彼此之间也会进行数据交互。
在进一步对 USB Gadget 驱动 做更细节的描述前,我们通过下图,先让大家对 USB Gadget 驱动框架 做一个初步认识(图片来自于网络,具体出处已不可考):
在这里插入图片描述实际上本文重点只涉及上图中右侧红框中的部分,之所有列出左边部分,是想让大家对整个 USB 驱动框架有一个整体认识。左边的部分,不仅限于 Linux,可以是任何支持 USB 主机侧 系统,如 Windows, Mac OS 等其它系统。上图中我们列出的是 Linux 系统,Linux 系统本身既包含 USB 设备主机侧驱动,又包含 USB 设备从机侧驱动(即 USB Gadget 驱动),所以 Linux 自身就形成一个完整的 USB 驱动框架闭环。
接下来内容,我们将按上图中右半部分所示,逐个来讲述框架中的每个部分。

3.3 USB 设备控制器(UDC) 驱动

3.3.1 USB 设备控制器(UDC) 驱动 概述

所有的 USB Gadget 驱动,最终都是通过 USB 设备控制器(UDC) ,和 主机侧的 USB 控制器(UHC) 进行交互;而且 UDC 是独占的,一旦它被某个 USB Gadget 驱动 使用,直到该 USB Gadget 驱动 被卸载之前,其它的 USB Gadget 驱动 就不能使用它。
先来了解下 UDC 的硬件实现梗概 和 驱动代码组织。我们以常见的 双角色(既支持 主模式,又支持 从模式)USB 控制器 为例,来了解 UDC 的硬件实现。绝大多数芯片厂商,不会自己实现 USB 控制器的所有部分,而是购买 USB 控制器 IP 库,然后再以某个 IP 库为基础来实现 UDC 硬件。如 全志 H3 用 明导国际(Mentor Graphics)MUSB 实现 UDC ,它的 硬件实现 和 驱动代码组织 梗概如下:

      硬件           |         驱动
   -----------------|---------------------------------------------------
     UDC 的 厂商实现 | drivers/usb/musb/sunxi.c (UDC 厂商驱动部分)
           |        |           |
           V        |           V
       MUSB IP 库   | drivers/usb/musb/musb* (UDC MUSB IP 库驱动部分)
                    |           |
                    |           V
                    | drivers/usb/gadget/udc/core.c (UDC core 公共代码)

上面举例的是 全志 H3 对 MUSB 的实现,其它各个厂商对各类 USB IP 的实现类似。如 DesignWareDWC3 也是经常被芯片厂商用来实现双角色 USB 控制器的 USB IP 库。DWC3 双角色 USB IP 的驱动实现于代码文件 drivers/usb/dwc3/dwc3* 中,而 UDC 驱动的其它部分类似于 全志 H3 的实现。对于单单支持 USB 设备控制器(UDC) 角色(从模式) 的驱动,代码实现于目录 drivers/usb/gadget/udc 下。

3.3.2 USB 设备控制器(UDC) 驱动示例

接下来,我们以 全志 H3 以 MUSB IP 库为基础,实现的 USB 设备控制器 驱动为例,来进行 UDC 驱动 的说明。先看下 MUSB 的 DTS 配置:

usb_otg: usb@01c19000 {
	compatible = "allwinner,sun8i-h3-musb";
	...
	phys = <&usbphy 0>; // 关联的 USB PHY
	phy-names = "usb";
	extcon = <&usbphy 0>;
	dr_mode = "otg"; // 设备模式配置为 OTG:即可以动态的切换 UHC 和 UDC 角色
	status = "okay";
};

usbphy: phy@01c19400 {
	compatible = "allwinner,sun8i-h3-usb-phy";
	...
	// PG12 用于 OTG 角色检测
	usb0_id_det-gpios = <&pio 6 12 GPIO_ACTIVE_HIGH>; /* PG12 */
 	usb0_vbus-supply = <&reg_usb0_vbus>;
	status = "okay";
};

DTS 配置的 usb_otg 指代 H3 实现的 双角色 USB 控制器usbphy 指代 USB 控制器使用的 USB PHY 设备。先看 USB 控制器的驱动:

/* drivers/usb/musb/sunxi.c */
// MUSB 全志厂商驱动入口
static int sunxi_musb_probe(struct platform_device *pdev)
{
	struct musb_hdrc_platform_data pdata;
	struct platform_device_info pinfo;
	struct sunxi_glue  *glue;
	...

	glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL);

	switch (usb_get_dr_mode(&pdev->dev)) { // 获取 DTS 配置的模式
	...
#ifdef CONFIG_USB_MUSB_DUAL_ROLE
	case USB_DR_MODE_OTG: // DTS 配置为 "otg" 模式,即双角色模式
		pdata.mode = MUSB_PORT_MODE_DUAL_ROLE;
		glue->phy_mode = PHY_MODE_USB_OTG;
		break;
#endif
	...
	}
	// 平台特定的接口: 初始化,寄存器读写,DMA, 模式设置, VBUS控制, ...
	pdata.platform_ops = &sunxi_musb_ops;
	if (!of_device_is_compatible(np, "allwinner,sun8i-h3-musb"))
		// 厂商硬件实现的端点(EndPoint)配置数据: 端点数目、方向(IN,OUT,IN&OUT), FIFO 缓冲深度 等
  		pdata.config = &sunxi_musb_hdrc_config;
  	else
  		...
	...

	memset(&pinfo, 0, sizeof(pinfo));
	pinfo.name  = "musb-hdrc"; // MUSB IP 驱动匹配关键字
	...

	// 加载 MUSB IP 驱动: 厂商驱动 -> MUSB IP 驱动
	glue->musb_pdev = platform_device_register_full(&pinfo);

	...
}

/* drivers/usb/musb/musb_core.c */
// MUSB IP 驱动入口,共实现厂商调用
static int musb_probe(struct platform_device *pdev)
{
	...
	
	return musb_init_controller(dev, irq, base);
}

// 初始化 MUSB。这里只重点关注 从设模式 相关的初始化。
static int
musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl)
{
	struct musb  *musb;

	musb = allocate_instance(dev, plat->config, ctrl);
	...

	musb->ops = plat->platform_ops; // 设置 MUSB 的厂商接口 (&sunxi_musb_ops)
 	musb->port_mode = plat->mode;
 	...

	status = musb_platform_init(musb); // 厂商特定初始化: sunxi_musb_init()
		musb->ops->init(musb) = sunxi_musb_init()
			...
			musb->isr = sunxi_musb_interrupt; // 厂商中断入口
			...
	...

	/* setup musb parts of the core (especially endpoints) */
	// MUSB IP 核心初始化,特别是 USB 端点(EndPoint)数据初始化
	status = musb_core_init(plat->config->multipoint
		? MUSB_CONTROLLER_MHDRC
  		: MUSB_CONTROLLER_HDRC, musb);
	
	...

	// 注册厂商初始化设定的中断处理接口 sunxi_musb_interrupt()
	// sunxi_musb_interrupt() -> musb_interrupt()
	// 厂商中断处理接口最终会调用 MUSB IP 的中断接口 musb_interrupt()
	if (request_irq(nIrq, musb->isr, IRQF_SHARED, dev_name(dev), musb)) {
		...
	}

	...
	
	// 按配置的模式, 初始化 MUSB IP 。
	// 再一次的,我们只关注 从设模式的相关初始化。
	switch (musb->port_mode) {
	...
	case MUSB_PORT_MODE_DUAL_ROLE: // 双角色模式, 我们只关注 从设模式 相关部分
		...
		// MUSB 从设模式 初始化.
		// 其中最重要的是会注册 UDC 对象到系统,其将和 USB Gadget Function 驱动组合到一起,
		// 形成一个完整的 USB Gadget 驱动.
		status = musb_gadget_setup(musb);
		...
		status = musb_platform_set_mode(musb, MUSB_OTG);
		break;
	}
	
	...
}

// MUSB IP 核心初始化,特别是 USB 端点(EndPoint)数据初始化
static int musb_core_init(u16 musb_type, struct musb *musb)
{
	...
	
	/* configure ep0 */
	musb_configure_ep0(musb);

	/* discover endpoint configuration */
	musb->nr_endpoints = 1;
	musb->epmask = 1;

	// 端点数目、数据搜集 + 端点初始化(FIFO 配置等)
	if (musb->dyn_fifo)
		status = ep_config_from_table(musb);
	else
		status = ep_config_from_hw(musb);
	
	...
}

/* drivers/usb/musb/musb_gadget.c */
// MUSB 从设模式 初始化.
int musb_gadget_setup(struct musb *musb)
{
	...
	
	musb->g.ops = &musb_gadget_operations; // MUSB 从设模式 操作接口
	musb->g.max_speed = USB_SPEED_HIGH;
	musb->g.speed = USB_SPEED_UNKNOWN;

	musb->g.name = musb_driver_name; // "musb-hdrc"

	// MUSB 端点(EndPoint) 数据初始化,【包括】控制端点(EP0)
	musb_g_init_endpoints(musb);

	// 注册 UDC 设备对象 到系统
	status = usb_add_gadget_udc(musb->controller, &musb->g);

	...
}

// 所有 MUSB 端点(EndPoint) 数据初始化,【包括】控制端点(EP0)
static inline void musb_g_init_endpoints(struct musb *musb)
{
	...
	
	/* initialize endpoint list just once */
	INIT_LIST_HEAD(&(musb->g.ep_list));
	
	for (epnum = 0, hw_ep = musb->endpoints;
		   epnum < musb->nr_endpoints;
		   epnum++, hw_ep++) {
		if (hw_ep->is_shared_fifo /* || !epnum */) { // 输入输出共享 FIFO,端点同时支持【输入&输出(IN & OUT)】,典型的如 EP0 端点
			init_peripheral_ep(musb, &hw_ep->ep_in, epnum, 0);
			count++;
		} else { // 只支持一个方向(IN 或 OUT)的端点
			if (hw_ep->max_packet_sz_tx) { // 只支持【输出】的端点: OUT
	 			init_peripheral_ep(musb, &hw_ep->ep_in,
	    					epnum, 1);
	 			count++;
			}
			if (hw_ep->max_packet_sz_rx) { // 只支持【输入】的端点: IN
	 			init_peripheral_ep(musb, &hw_ep->ep_out,
	    					epnum, 0);
	 			count++;
			}
		}
	}
}

// 初始化一个端点
static void
init_peripheral_ep(struct musb *musb, struct musb_ep *ep, u8 epnum, int is_in)
{
	...

	ep->current_epnum = epnum; // 端点编号
	...
	ep->is_in = is_in; // 标记端点方向:是输入(IN)还是出输出(OUT)
	
	INIT_LIST_HEAD(&ep->req_list); // 初始端点上数据请求包(usb_request)列表
	...
	// 配置端点接口和能力
	if (!epnum) { // 控制端点 EP0
		usb_ep_set_maxpacket_limit(&ep->end_point, 64);
		ep->end_point.caps.type_control = true;
		ep->end_point.ops = &musb_g_ep0_ops; // 控制端点 EP0 操作接口
		musb->g.ep0 = &ep->end_point;
	}  else {
		if (is_in)
			usb_ep_set_maxpacket_limit(&ep->end_point, hw_ep->max_packet_sz_tx);
		else
			usb_ep_set_maxpacket_limit(&ep->end_point, hw_ep->max_packet_sz_rx);
		ep->end_point.caps.type_iso = true;
		ep->end_point.caps.type_bulk = true;
		ep->end_point.caps.type_int = true;
		ep->end_point.ops = &musb_ep_ops; // 普通端点 操作接口
		list_add_tail(&ep->end_point.ep_list, &musb->g.ep_list);
	}

	// 修正端点方向设置
	if (!epnum || hw_ep->is_shared_fifo) {
		ep->end_point.caps.dir_in = true;
		ep->end_point.caps.dir_out = true;
	} else if (is_in)
		ep->end_point.caps.dir_in = true;
	else
		ep->end_point.caps.dir_out = true;
}

最后来看 UDC 设备对象的注册过程:

/* drivers/usb/gadget/udc/core.c */
int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget)
{
	return usb_add_gadget_udc_release(parent, gadget, NULL);
}

int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget,
  void (*release)(struct device *dev))
{
	struct usb_udc  *udc;

	...
	udc = kzalloc(sizeof(*udc), GFP_KERNEL); // 分配一个 UDC 设备对象

	...
	list_add_tail(&udc->list, &udc_list); // 添加 UDC 设备对象到全局列表

	...
	usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED);
 	udc->vbus = true;

	/* pick up one of pending gadget drivers */
	// 可能发生的、将 UDC 绑定到某个 USB Gadget Function 驱动。
	// 这不是通常的情形,后续我们看后一种绑定时机的细节,它们逻辑上是一样的。
	ret = check_pending_gadget_drivers(udc);
	...

	return 0;
}

对于 UDC 驱动 的讨论,就先到这里,等到后面涉及到 UDCUSB Gadget Function 绑定的时候,再讨论剩余的相关细节。

3.4 USB Gadget Function 驱动

单独把 USB Gadget Function 驱动 拿出来讲,没有太多意义。关于 USB Gadget Function 驱动 的细节,我们将在 3.5 小节中一起讲述。

3.5 USB Gadget 驱动

本小节将讲述如何将 UDC 驱动USB Gadget Function 驱动 组合到一起,形成一个完整的 USB Gadget 驱动
顺便提一下,USB Gadget 驱动代码组织在目录 drivers/usb/gadget 目录下:

drivers/usb/gadget/*.c, *.h: USB Gadget 驱动核心公共代码
drivers/usb/gadget/function/*.c, *.h: USB Gadget 驱动各功能(serial,UDC,...)代码
drivers/usb/gadget/legacy/*.c, *.h: USB Gadget 驱动

解下来,我们以 Gadget UAC 驱动 为例,来讲述 USB Gadget 驱动 的 记载、卸载,以及工作过程。其它功能的 USB Gadget 驱动 ,除了协议特定部分,其它部分的流程是类似的,读者可参考本文自行分析。

3.5.1 USB Gadget 驱动的加载

3.5.1.1 启动 UDC: 上拉 D+

// 我们假设将 Gadget UAC 驱动编译程模块形式,即 g_audio.ko 。
// 当以 insmod g_audio.ko 指令加载模块时,将进入驱动入口 usb_composite_probe()

/* drivers/usb/gadget/legacy/audio.c */
static struct usb_composite_driver audio_driver = {
	.name  = "g_audio",
	.dev  = &device_desc,
	.strings = audio_strings,
	.max_speed = USB_SPEED_HIGH,
	.bind  = audio_bind,
	.unbind  = audio_unbind,
};

/*
 * include/linux/usb/composite.h
 *
 * #define module_usb_composite_driver(__usb_composite_driver) \
 * 	module_driver(__usb_composite_driver, usb_composite_probe, \
 *        		usb_composite_unregister)
 */
// 定义 USB Gadget 驱动 
module_usb_composite_driver(audio_driver);

在进一步讨论前,先看看 module_usb_composite_driver()usb_composite_driver 的定义:

#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
 return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
 __unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

#define module_usb_composite_driver(__usb_composite_driver) \
    module_driver(__usb_composite_driver, usb_composite_probe, \
         	usb_composite_unregister)

可以看到,module_usb_composite_driver() 定义了两个函数。具体到我们的 UAC 例子,就是定义了函数 audio_driver_init()audio_driver_exit()

static int __init audio_driver_init(void)
{
	return usb_composite_probe(&audio_driver);
}
module_init(audio_driver_init);

static void __exit audio_driver_exit(void)
{
	usb_composite_unregister(&audio_driver);
}
module_exit(audio_driver_exit);

再看下 usb_composite_driver 的定义:

struct usb_composite_driver {
	const char    *name;
	const struct usb_device_descriptor *dev;
	struct usb_gadget_strings  **strings;
	enum usb_device_speed   max_speed;
	unsigned  needs_serial:1;
	
	int   (*bind)(struct usb_composite_dev *cdev); // audio_bind()
	int   (*unbind)(struct usb_composite_dev *); // audio_unbind()

	void   (*disconnect)(struct usb_composite_dev *);

	/* global suspend hooks */
	void   (*suspend)(struct usb_composite_dev *);
	void   (*resume)(struct usb_composite_dev *);
	struct usb_gadget_driver  gadget_driver;
};

struct usb_gadget_driver {
	char   *function;
	enum usb_device_speed max_speed;
	int   (*bind)(struct usb_gadget *gadget,
			struct usb_gadget_driver *driver); // composite_bind()
	void   (*unbind)(struct usb_gadget *); // composite_unbind()
	int   (*setup)(struct usb_gadget *,
			const struct usb_ctrlrequest *); // composite_setup()
	void   (*disconnect)(struct usb_gadget *);
	void   (*suspend)(struct usb_gadget *);
	void   (*resume)(struct usb_gadget *);
	void   (*reset)(struct usb_gadget *);

	struct device_driver driver;

	// 可以显式指定 USB Gadget 驱动关联的 UDC。
	// 但绝大多数情形都会将 udc_name 设为 NULL, 这指示直接使用系统中能找到的 UDC,
	// 因为通常系统中不会有超过一个 UDC 存在。
	char   *udc_name;
	...
};

好,继续看 Gadget UAC 驱动的加载过程:

/* drivers/usb/gadget/composite.c */
int usb_composite_probe(struct usb_composite_driver *driver)
{
	struct usb_gadget_driver *gadget_driver;

	driver->gadget_driver = composite_driver_template;
 	gadget_driver = &driver->gadget_driver;
 	...

	return usb_gadget_probe_driver(gadget_driver); // 绑定 UDC
}

/* drivers/usb/gadget/udc/core.c */
int usb_gadget_probe_driver(struct usb_gadget_driver *driver)
{
	...
	mutex_lock(&udc_lock);
	if (driver->udc_name) {
		...
	} else {
		list_for_each_entry(udc, &udc_list, list) {
			/* For now we take the first one */
			if (!udc->driver)
				goto found;
		}
	}
	...

found:
	ret = udc_bind_to_driver(udc, driver); // 绑定 UDC 和 usb_gadget_driver
	mutex_unlock(&udc_lock);
	return ret;
}

static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver)
{
	...
	// 绑定 UDC 和 usb_gadget_driver
	udc->driver = driver;
	udc->dev.driver = &driver->driver;
	udc->gadget->dev.driver = &driver->driver;

	// 间接触发 UAC 驱动的 bind:
	// composite_bind() -> audio_bind()
	ret = driver->bind(udc->gadget, driver);
		...
		status = composite->bind(cdev) = audio_bind()
		...
	...

	ret = usb_gadget_udc_start(udc); /* 启动 UDC: 上电 */
	...

	usb_udc_connect_control(udc); /* D+ 上拉: 触发枚举过程 */

	...
	return 0;
}

重点看一下 audio_bind(),看它是如何将 Gadget Fuction(UAC 1.0 Function) 绑定上来的:

/* drivers/usb/gadget/legacy/audio.c */
static struct usb_function_instance *fi_uac1;
static struct usb_function *f_uac1

static int audio_bind(struct usb_composite_dev *cdev)
{
	struct f_uac1_legacy_opts *uac1_opts;

	fi_uac1 = usb_get_function_instance("uac1");
		try_get_usb_function_instance(name)
			...
			list_for_each_entry(fd, &func_list, list) {
				if (strcmp(name, fd->name)) // 按名字查找 function driver
   					continue;
   				...
   				fi = fd->alloc_inst(); // f_audio_alloc_inst()
   				if (IS_ERR(fi))
					module_put(fd->mod);
				else
					fi->fd = fd;
				break;
			}
			...
			return fi;
	...

	status = usb_add_config(cdev, &audio_config_driver, audio_do_config);
		...
		status = usb_add_config_only(cdev, config); // 添加配置 @config
		...
		status = bind(config); // function 绑定:audio_do_config(), ...
		...
	...
}

static int audio_do_config(struct usb_configuration *c)
{
	...
	f_uac1 = usb_get_function(fi_uac1); // 获取 function
		f = fi->fd->alloc_func(fi); // f_audio_alloc()
			struct f_uac1 *uac1;
			...
			uac1 = kzalloc(sizeof(*uac1), GFP_KERNEL); // 分配 function 对象
			...
			// 设置 function 接口
			uac1->g_audio.func.name = "uac1_func";
			uac1->g_audio.func.bind = f_audio_bind;
			uac1->g_audio.func.unbind = f_audio_unbind;
			uac1->g_audio.func.set_alt = f_audio_set_alt;
			uac1->g_audio.func.get_alt = f_audio_get_alt;
			uac1->g_audio.func.setup = f_audio_setup; // 枚举阶段处理各种 UAC 协议特定的 setup 包
			uac1->g_audio.func.disable = f_audio_disable;
			uac1->g_audio.func.free_func = f_audio_free;
			
			return &uac1->g_audio.func;
		...
		f->fi = fi;
 		return f;
	...
	status = usb_add_function(c, f_uac1); // 绑定 function
		...
		function->config = config;
		list_add_tail(&function->list, &config->functions);
		...
		if (function->bind) {
			// function 的初始化: 各类 USB 描述符设置 等等
			value = function->bind(config, function); // f_audio_bind(), ...
			...
		}
		...
}

补充说明下,usb_get_function_instance() 查找的 Gadget Function 列表 func_list 是怎么构建的:

/* drivers/usb/gadget/function/f_uac1.c */
DECLARE_USB_FUNCTION_INIT(uac1, f_audio_alloc_inst, f_audio_alloc);

/* drivers/usb/gadget/function/f_serial.c */
DECLARE_USB_FUNCTION_INIT(gser, gser_alloc_inst, gser_alloc);

// 更多 drivers/usb/gadget/function/f_*.c 注册的 function
#define DECLARE_USB_FUNCTION_INIT(_name, _inst_alloc, _func_alloc) \
 DECLARE_USB_FUNCTION(_name, _inst_alloc, _func_alloc)  \
 static int __init _name ## mod_init(void)   \
 {        \
  return usb_function_register(&_name ## usb_func); \
 }        \
 static void __exit _name ## mod_exit(void)   \
 {        \
  usb_function_unregister(&_name ## usb_func);  \
 }        \
 module_init(_name ## mod_init);     \
 module_exit(_name ## mod_exit)

usb_function_register()
	...
	list_add_tail(&newf->list, &func_list);

到此,已经完成了 UDC 和 Function 的绑定 和 初始化过程。此时,UDC 已经触发了枚举过程,接下来看枚举期间发生了什么。

3.5.1.2 设备枚举

USB 的通信,总是从主机一侧开始。当 USB 设备控制器收到 主机发过来的数据包 (UHC -> UDC),UDC 就是产生一个中断,所以我们的分析,从中断接口 sunxi_musb_interrupt() 开始:

/* drivers/usb/musb/sunxi.c */
static irqreturn_t sunxi_musb_interrupt(int irq, void *__hci)
{
	// 厂商特定的中断处理
	musb->int_usb = readb(musb->mregs + SUNXI_MUSB_INTRUSB);
	if (musb->int_usb)
		writeb(musb->int_usb, musb->mregs + SUNXI_MUSB_INTRUSB);
	...
	musb->int_tx = readw(musb->mregs + SUNXI_MUSB_INTRTX); // 读各端点 TX 中断状态
	if (musb->int_tx)
		writew(musb->int_tx, musb->mregs + SUNXI_MUSB_INTRTX);
	
	musb->int_rx = readw(musb->mregs + SUNXI_MUSB_INTRRX); // 读各端点 RX 中断状态
	if (musb->int_rx)
		writew(musb->int_rx, musb->mregs + SUNXI_MUSB_INTRRX);
	
	// MUSB IP 公共中断处理
	musb_interrupt(musb);
	...

	return IRQ_HANDLED;
}

irqreturn_t musb_interrupt(struct musb *musb)
{
	...
	if (musb->int_usb)
  		retval |= musb_stage0_irq(musb, musb->int_usb, devctl);

	// EP0 中断处理
	if (musb->int_tx & 1) {
		if (is_host_active(musb))
			retval |= musb_h_ep0_irq(musb);
		else
			retval |= musb_g_ep0_irq(musb);

		/* we have just handled endpoint 0 IRQ, clear it */
		musb->int_tx &= ~BIT(0); // EP0 的事件处理了,清除 EP0 的位码
	}

	// 普通端点 TX 中断处理
	status = musb->int_tx;
	for_each_set_bit(epnum, &status, 16) {
		retval = IRQ_HANDLED;
		if (is_host_active(musb)) // 主机模式
			musb_host_tx(musb, epnum);
		else // 从设模式
			musb_g_tx(musb, epnum);
	}

	// 普通端点 TX 中断处理
	status = musb->int_rx;
	for_each_set_bit(epnum, &status, 16) {
		retval = IRQ_HANDLED;
		if (is_host_active(musb))
			musb_host_rx(musb, epnum);
		else
			musb_g_rx(musb, epnum);
	}

	return retval;
}

中段涉及的数据不光包含枚举或断开连接这些期间的 setup 包,还有其它数据的 RX, TX 传输。本小节讲述的枚举,所以只描述枚举相关的通信做简要说明:

sunxi_musb_interrupt()
	musb_interrupt()
		musb_g_ep0_irq()
			switch (musb->ep0_state) {
			...
			case MUSB_EP0_STAGE_SETUP:
				if (csr & MUSB_CSR0_RXPKTRDY) { /* 收包就绪 */
					struct usb_ctrlrequest setup;
          				...
          				musb_read_setup(musb, &setup); /* 从 FIFO 读取 setup 包的内容 */
          				...
          				handled = forward_to_driver(musb, &setup);
          					musb->gadget_driver->setup(&musb->g, ctrlrequest) = composite_setup()
          				...
				}
			...
			}

/* drivers/usb/gadget/composite.c */
int
composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
{
	...
	// 非特定于 Gadget Function 的 setup 包处理
	switch (ctrl->bRequest) {
	case USB_REQ_GET_DESCRIPTOR: // 获取 USB 设备描述符
		...
		break;
	case USB_REQ_SET_CONFIGURATION: // 设置 USB 设备配置描述符
		...
		value = set_config(cdev, ctrl, w_value);
		...
		break;
	case USB_REQ_GET_CONFIGURATION: // 获取 USB 设备配置描述符
		...
		break;
	case USB_REQ_SET_INTERFACE:
		...
		value = f->set_alt(f, w_index, w_value); // f_audio_set_alt(), ...
		...
		break;
	case USB_REQ_GET_INTERFACE:
		...
		value = f->get_alt ? f->get_alt(f, w_index) : 0; // f_audio_get_alt(), ...
		...
		break;
	...
	default: // 特定于 Gadget Function 的 setup 包处理
unknown:
	...
try_fun_setup:
		if (f && f->setup)
			// 进行 Gadget Function 特定的 setup 包处理
			value = f->setup(f, ctrl); // f_audio_setup()
		else
			...
		
		goto done; // 特定于 Gadget Function 的 setup 包处理完毕,直接跳到函数末尾
	}

	/* respond with data transfer before status phase? */
	// 非特定于 Gadget Function 的 setup 包处理 回馈
	if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) {
		req->length = value;
		req->context = cdev;
  		req->zero = value < w_length;
  		value = composite_ep0_queue(cdev, req, GFP_ATOMIC);
  		...
	}
	
done:
	/* device either stalls (value < 0) or reports success */
 	return value;
}

正确完成上述枚举过程,主机端接下来就可以和从设进行数据通信了。

3.5.2 USB Gadget 驱动数据交流过程

以 UAC 播放过程为例,来说明 USB Gadget 驱动 和 主机侧的 数据交流过程。在 Windows 开启播放器程序,它首先会发 SET_INTERFACE 给从设,以告知 USB Gadget 驱动做播放前的准备工作,以及启动对应的 USB 端点进行播放数据传输:

sunxi_musb_interrupt()
	...
	composite_setup()
		...
		f_audio_set_alt()
			...
			if (intf == uac1->as_out_intf) { // 准备处理播放数据
				uac1->as_out_alt = alt;
				if (alt)
					ret = u_audio_start_capture(&uac1->g_audio);
						...
						usb_ep_enable(ep); // 激活 USB 端点
						for (i = 0; i < params->req_number; i++) {
							// 分配 USB 数据包处理对象
							req = usb_ep_alloc_request(ep, GFP_ATOMIC);
							...
							req->complete = u_audio_iso_complete;
							...
							// 将 USB 数据包处理对象加入到 USB 端点
							if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
								...
						}
				else
					...
			}

接下来是收取播放数据包过程:

sunxi_musb_interrupt()
	...
	musb_g_giveback()
		...
		list_del(&req->list); // 含有数据的 usb_request 出 端点数据包队列
		...
		usb_gadget_giveback_request(&req->ep->end_point, &req->request)
			...
			req->complete(ep, req); // u_audio_iso_complete(), ...
				u_audio_iso_complete()
					...
					if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
						...
					}  else { // 拷贝 主机侧 发送的 播放数据 到 UAC 声卡 DMA 缓冲
						if (unlikely(pending < req->actual)) {
							memcpy(runtime->dma_area + hw_ptr, req->buf, pending);
							memcpy(runtime->dma_area, req->buf + pending, req->actual - pending);
						} else {
							memcpy(runtime->dma_area + hw_ptr, req->buf, req->actual);
						}
						
						...
						
						// usb_request 重新加入端点队列,以处理后续数据
						if (usb_ep_queue(ep, req, GFP_ATOMIC))
							...
					}

3.5.3 USB Gadget 驱动的卸载

仍然以 Gadget UAC 驱动的卸载为例,说明 USB Gadget 驱动 的卸载过程:

// 通过 rmmod g_audio 卸载模块
sys_delete_module()
	audio_driver_exit()
		usb_composite_unregister()
			usb_gadget_unregister_driver()
				usb_gadget_remove_driver()
					composite_unbind()
						__composite_unbind()
							remove_config()
								usb_remove_function()
									f_audio_unbind()
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux: USB Gadget 驱动简介 的相关文章

  • 如何从“git log”中查看 Git 中的特定版本?

    My git log显示为 enter code here git trial git log commit 4c5bc66ae50780cf8dcaf032da98422aea6e2cf7 Author king lt email pro
  • 通过 SSH 将变量传递给远程脚本

    我正在通过 SSH 从本地服务器在远程服务器上运行脚本 首先使用 SCP 复制该脚本 然后在传递一些参数时调用该脚本 如下所示 scp path to script server example org another path ssh s
  • 编写多个mysql脚本

    是否可以在复合脚本中包含其他 mysql 脚本 理想情况下 我不想为包含的脚本创建存储过程 对于较大的项目 我想分层维护几个较小的脚本 然后根据需要组合它们 但现在 我很乐意学习如何包含其他脚本 source是一个内置命令 您可以在 MyS
  • Xenomai 中的周期性线程实时失败

    我正在创建一个周期性线程 它在模拟输出上输出方波信号 我正在使用 Xenomai API 中的 Posix Skin 和 Analogy 我使用示波器测试了代码的实时性能 并查看了方波信号 频率为 1kHz 的延迟 我应该实现 250us
  • Linux 文本文件操作

    我有一个格式的文件 a href a href a href a href 我需要选择 之后但 之前的文本 并将其打印在行尾 添加后 例如 a href http www wowhead com search Su a a a a a
  • 是否有可能在linux中找到包含特定文本的文件?

    考虑这种情况 我在文件夹 Example 下有很多文件 如果我需要找到一个包含特定短语 如 Class Example 的文件 我该如何使用 Linux shell 来做到这一点 linux中有类似 定位 的函数可以做到这一点吗 Thank
  • Linux命令列出所有可用命令和别名

    是否有一个 Linux 命令可以列出该终端会话的所有可用命令和别名 就好像您输入 a 并按下 Tab 键一样 但针对的是字母表中的每个字母 或者运行 别名 但也返回命令 为什么 我想运行以下命令并查看命令是否可用 ListAllComman
  • 是否从页面缓存中的脏页面进行文件读取?

    当字节写入文件时 内核不会立即将这些字节写入磁盘 而是将这些字节存储在页缓存中的脏页中 回写缓存 问题是 如果在脏页刷新到磁盘之前发出文件读取 则将从缓存中的脏页提供字节 还是首先将脏页刷新到磁盘 然后进行磁盘读取以提供字节 将它们存储在进
  • 具有少量父设备属性的 udev 规则

    我需要复杂且通用的udev规则来确定插入任何 USB 集线器的特定端口的 USB 设备 所以 我必须结合设备树不同层的父属性 我有这个 udevadm info query all name dev ttyUSB0 attribute wa
  • 如何从类似于 eclipse 的命令行创建可运行的 jar 文件

    我知道 eclipse 会生成一个可运行的 jar 文件 其中提取并包含在该 jar 文件中的所有库 jar 文件 从命令提示符手动创建 jar 文件时如何执行类似的操作 我需要将所有 lib jar 解压到类文件夹中吗 目前我正在使用 j
  • 如何从linux命令行运行.exe可执行文件? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我在 Windows 中有一个 abc exe 可执行文件 我可以使用 DOS 命令提示来执行此应用程序 并为其提供一些运行时变量 我想从
  • 无法仅在控制台中启动 androidstudio

    你好 我的问题是下一个 我下载了Android Studio如果我去 路径 android studio bin 我执行studio sh 我收到以下错误 No JDK found Please validate either STUDIO
  • 如何调用位于其他目录的Makefile?

    我正在尝试这样做 我想打电话给 make Makefile存在于其他目录中 abc可以使用位于不同目录中的 shell 脚本的路径 我该怎么做呢 由于 shell 脚本不允许我cd进入Makefile目录并执行make 我怎样才能编写she
  • numpy 未定义符号:PyFPE_jbuf

    我正在尝试使用一百万首歌曲数据集 为此我必须安装 python 表 numpy cython hdf5 numexpr 等 昨天我设法安装了我需要的所有内容 在使用 hdf5 遇到一些麻烦之后 我下载了预编译的二进制包并将它们保存在我的 b
  • 如何使用AWK脚本检查表的所有列数据类型? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 在这里 我正在检查表中第一列的数据类型 但我想知道AWK中表的所有列数据类型 我尝试过 但只能获得一列数据类型 例如 Column 1
  • 在 LINUX 上使用 Python 连接到 OLAP 多维数据集

    我知道如何在 Windows 上使用 Python 连接到 MS OLAP 多维数据集 嗯 至少有一种方法 通常我使用 win32py 包并调用 COM 对象进行连接 import win32com client connection wi
  • Linux 中热插拔设备时检测设备是否存在

    我正在运行 SPIcode http lxr free electrons com source drivers spi spi omap2 mcspi c在熊猫板上 我想知道其中的哪个功能code http lxr free electr
  • 如何在Python中独立于语言安装(linux)获取用户桌面路径

    我找到了 如何找到用户桌面的路径 的几个问题和答案 但在我看来它们都已失效 至少我找到的那些 原因是 如果用户安装的 Linux 不是英语 他或她的桌面很可能位于除 Desktop 例如 对于瑞典语 我相信它是在 Skrivbord 谁知道
  • C 程序从连接到系统的 USB 设备读取数据

    我正在尝试从连接到系统 USB 端口的 USB 设备 例如随身碟 获取数据 在这里 我可以打开设备文件并读取一些随机原始数据 但我想获取像 minicom teraterm 这样的数据 请让我知道我可以使用哪些方法和库来成功完成此操作以及如
  • 如何在特定 systemd 服务重新启动时触发自定义脚本运行

    我想知道如何安排自定义脚本在重新启动服务时运行 我的用例是 每当重新启动 Tomcat 服务时 我都必须运行多个命令 我想知道是否有一种方法可以编写脚本并安排它在重新启动 Tomcat 服务时运行 我已将 tomcat 脚本设置为 syst

随机推荐

  • 解决有时候加载不出img标签图片

    在vue前端浏览器加载图片时 其他任何地方都能加载出 就唯独一个地方显示无法载入此图像 完全无法理解 解决方法是在在图片显示的界面把meta referrer标签改为never 或者在img标签上加上referrerpolicy no re
  • TFIDF算法Hadoop实现

    程序说明 利用MapReduce计算框架 计算一组英文文档中各个单词的TFIDF 某单词在某文档的TFIDF 该单词中该文档的TF 该单词IDF 其中 TF i j 单词i在文档j中出现的频率 Term Frequency TF i j N
  • MySQL之数据备份和恢复

    参考资料 关于备份的一些概念 http www open open com lib view open1382152331946 html 关于备份和数据恢复的简介 http wenku baidu com link url eVm3 9f
  • 初识C语言之数据类型,生命周期&作用域

    首先 C语言大致分为七种基础的数据类型 分别是char 字符数据类型 short 短整形 int 整形 long 长整形 long long 更长的整形 float 单精度浮点数 double 双精度浮点数 其中 char是描述字符的 sh
  • 金融行业的密钥及加密机制

    金融行业的密钥及加密机制 一 秘钥的标准体系 二 秘钥实现 三 常见术语 四 参考文档 一 秘钥的标准体系 目前金融行业的秘钥体系主要有两个 一是 Q CUP 006 4 2015 中国银联股份有限公司企业标准 中国银联银行卡交换系统技术规
  • PaddleOCR手写体训练摸索

    手写OCR识别 一 官方支持的数据格式 1 官方文档 1 1 PaddleOCR 支持两种数据格式 1 2 训练数据的默认存储路径 1 3 自定义数据集的准备 1 3 1 通用数据集 1 3 2 lmdb数据集 1 3 2 1 lmdb基本
  • LED为何通过电流控制?

    前段时间 散热部的同事咨询我关于手机的闪光灯输出电压值 说实话 一时间把我问住了 关于闪光灯 以往我们关注电流值 电压值很少关注 虽说手机的闪光灯驱动IC输出为BOOST电路 但是输出电压到多少 我还真未了解过 因闪光灯本身属于电流控制 所
  • 安装包制作工具 Inno Setup 6.0.2 汉化版-BY 胡萝卜周博客

    nno Setup 是一个免费的安装制作软件 小巧 简便 精美是其最大特点 支持pascal脚本 能快速制作出标准 Windows2000 风格的安装界面 足以完成一般安装任务 该软件用Delphi写成 其官方网站同时也提供源程序免费下载
  • mongdb 建立地图索引,删除,查询

    方式一 创建 db shop ensureIndex loc 2dsphere 2Dsphere索引 用于存储和查找球面上的点 db shop ensureIndex loc 2d 2D索引 用于存储和查找平面上的点 本人项目用的这种 查询
  • 阿里最新秋招面经,腾讯/美团/字节1万道Java中高级面试题

    又是一年过去了 职场的积雪还没有消融 又迎来了一次大考 疫情还没完全过去 大家强打起精神 相互问好致意 眼角却满是疲惫 企业调薪 裁员 组织架构调整等等 坏消息只多不少 最近也有很多来咨询跳槽的朋友 都是因为之前的公司出现了比较大的薪资和组
  • tomcat中间件的默认端口号_tomcat默认端口号(三个tomcat端口号)

    tomcat默认端口号 三个tomcat端口号 2020 05 08 10 43 21 共10个回答 Tomcat的默认端口号是多少 您好 提问者 Tomcat的默认端口号是 8080 weblogic的默认端口号是 7001 tomcat
  • 【机器学习笔记1】一元线性回归模型及预测

    目录 什么是线性回归模型 一元线性回归模型 问题引入 问题解析 代价函数 损失函数 代价函数的图像 为什么不是最小而是极小值 梯度下降算法 梯度下降算法公式 对于一元线性回归模型 学习率a的选择 关于梯度下降每一步的变化 补充 代码部分 案
  • SpringBoot整合邮箱验证(典中典)

    大体思路 先生成一个六位随机验证码并存起来 调用邮箱接口发送验证码 将用户输入的验证码和之前保存的验证码进行比对 目录 大体思路 第一步 开启SMTP服务 简单邮件传输协议 第二步 在项目中导入相关依赖 第三步 在配置文件里进行相关配置 第
  • CVE-2022-30190 MSDT远程代码执行漏洞复现

    目录 0x01 声明 0x02 简介 0x03 漏洞概述 0x04 影响版本 0x05 环境搭建 0x06 漏洞复现 是否存在利用点 CMD执行 生成docx文件利用 0x07 CS上线 启动CS服务端 CS客户端连接 设置监听 生成攻击e
  • JAVA 关于static中静态代码块的使用

    与一般静态方法的比较 一般情况下 如果有些代码必须在项目启动的时候就执行的时候 需要使用静态代码块 这种代码是主动执行的 需要在项目启动的时候就初始化 两者的区别就是 静态代码块是自动执行的 静态方法是被调用的时候才执行的 静态方法可以用类
  • 【多线程】ThreadLocal

    目录 简介 底层 set get 回收 简介 线程变量 以ThreadLocal为键 任意对象为值的结构 这个结构被附带在线程上 一个线程根据一个ThreadLocal对象查询到绑定在这个线程上的一个值 本地线程 线程的局部变量 只有当前线
  • 学习少儿编程成为一种必然趋势

    AI人工智能和少儿编程一直是大家热议的话题 在政策引领下 一些城市把人工智能带入中小学教材当中 格物斯坦小坦克认为从编程思维入手 让孩子养成清晰明朗的逻辑思维 在学习 做事各个方面 孩子将来都会得心应手 Scratch编程与其他代码编程 最
  • DS静态查找之折半查找

    题目描述 给出一个队列和要查找的数值 找出数值在队列中的位置 队列位置从1开始 要求使用折半查找算法 输入 第一行输入n 表示队列有n个数据 第二行输入n个数据 都是正整数 用空格隔开 第三行输入t 表示有t个要查找的数值 第四行起 输入t
  • 抓包基本命令

    一 概述 在一个A应用程序内数据有不同的格式如 Integer String等 但是通过网络将数据传输给B应用程序 那么在到达B应用程序之前 数据都将统一解析成数据包 也就是二进制串在网络中传输 在B应用程序前布置一个 网 在这个数据包到达
  • Linux: USB Gadget 驱动简介

    文章目录 1 前言 2 背景 3 USB Gadget 驱动 3 1 什么是 USB Gadget 驱动 3 2 USB Gadget 驱动框架 3 3 USB 设备控制器 UDC 驱动 3 3 1 USB 设备控制器 UDC 驱动 概述