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) 驱动 概述
      • 3.3.2 USB 设备控制器(UDC) 驱动示例
    • 3.4 USB Gadget Function 驱动
    • 3.5 USB Gadget 驱动
      • 3.5.1 USB Gadget 驱动的加载
        • 3.5.1.1 启动 UDC: 上拉 D+
        • 3.5.1.2 设备枚举
      • 3.5.2 USB Gadget 驱动数据交流过程
      • 3.5.3 USB Gadget 驱动的卸载

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()

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

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

相关文章

微服务系列文章之 nginx负载均衡

nginx负载均衡 负载均衡建立在现有网络结构之上&#xff0c;提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽&#xff0c;增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。 随着网站的发展&#xff0c;服务器压力越来越大&#xff0c;我们可能首先会将数…

CentOS 安装字体 微软雅黑

fc-list命令查看已经安装的字体 fc-list :langzh命令可以查看已安装的中文字体 找到windows系统里面的字体 上传到服务器 /usr/share/fonts/winFonts 下&#xff0c;winFonts目录是自己建立的&#xff0c;名称无要求 如果C:\Windows\Fonts下的字体没法直接传输将这个文件夹复…

东方通信基于 KubeSphere 的云计算落地经验

作者&#xff1a;周峰 吴昌泰 公司简介 东方通信股份有限公司&#xff08;以下简称“东方通信”&#xff09;创立于 1958 年&#xff0c;是一家集硬件设备、软件、服务为一体的整体解决方案提供商。公司于 1996 年成功改制上市&#xff0c;成为上海证交所同时发行 A 股和 B 股…

jenkins手把手教你从入门到放弃01-jenkins简介(详解)

一、简介 jenkins是一个可扩展的持续集成引擎。持续集成&#xff0c;也就是通常所说的CI&#xff08;Continues Integration&#xff09;&#xff0c;可以说是现代软件技术开发的基础。持续集成是一种软件开发实践&#xff0c; 即团队开发成员经常集成他们的工作&#xff0c;通…

Ribbon 负载均衡服务调用

文章目录 1 SpringCloud Load Balance2 总结:3 Ribbon工作流程&#xff1a;4 自定义Ribbon 负载均衡算法&#xff1a;4.1 iRule接口&#xff1a;4.2 Ribbon自带的负载均衡算法&#xff1a;4.3 负载均衡算法替代&#xff1a;4.3.1、在非启动类包及子包下创建配置类4.3.2、定义4.…

网络虚拟化相关的Linux接口介绍

Linux拥有丰富的网络虚拟化功能&#xff0c;能被虚拟机&#xff0c;容器还有云网络使用。在这篇文章中&#xff0c;我会给出所有通用网络虚拟化接口的简要介绍。没有代码分析&#xff0c;只有简短的接口介绍和在Linux上的使用操作。这系列接口都可以使用ip link命令实现。 这篇…

Spring核心和设计思想(1)

1.Spring是什么&#xff1f; 我们通常说的Spring指的是Spring FrameWork&#xff08;Spring 框架&#xff09;&#xff0c;它是一个开源框架&#xff0c;有着活跃而庞大的社区&#xff0c;这就是它长久不衰的原因。Spring支持广泛的应用场景&#xff0c;它让Java企业级的应用程…

用html+javascript打造公文一键排版系统5:二级标题排版

公文中二级标题的一般以&#xff08;X&#xff09;标注&#xff08;其中X为由"一二三四五六七八九十"中的字符组成的字符串&#xff09;&#xff0c;用楷体字加粗。 首先我们要判断一段文字是否包含二级标题&#xff0c;最简单的方法 就是判断文字中的头一个字符是否…

【Linux】Linux下的项目自动化构建工具——make和makefile

❤️前言 大家好&#xff0c;好久不见&#xff01;今天小狮子为大家带来的文章是一篇关于Linux下的项目自动化构建工具——make和makefile的博客&#xff0c;希望能帮助到大家。 正文 当我们进行涉及多文件的工程开发时&#xff0c;我们需要对很多不同类型、不同功能&#xff…

基于R语言Meta分析与【文献计量分析、贝叶斯、机器学习等】多技术融合方法与应用

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;最早出现于“循证医学”&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面。…

XUbuntu22.04之解决蓝牙鼠标不停掉线问题(追凶过程)(一百八十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Linux网络基础 — 网络层

目录 IP协议 IP协议报头格式 网段划分 特殊的IP地址 IP地址的数量限制 私有IP地址和公网IP地址 路由 补充 网络层 在复杂的环境中确定一个合适的路径。 IP协议 ip具有将数据从 主机A 跨网络送到 主机B 的能力。 主机: 配有IP地址&#xff0c;但是不进行路由控制的设备…

【Redis】Transaction(事务)

&#x1f3af;前言 Redis事务是一个组有多个Redis命令的集合&#xff0c;这些命令可以作为一个原子操作来执行。 Redis事务通常用于以下两种情况&#xff1a; 保证操作的原子性&#xff1a;在多个命令的执行过程中&#xff0c;如果有一个命令执行失败&#xff0c;整个事务都需…

挖矿记录+解决方案:利用GitLab组件对服务器进行挖矿导致CPU占用200%

文章目录 什么是云服务器挖矿?事件记录事件分析产生影响解决方案后期预防什么是云服务器挖矿? 云服务器挖矿是指利用云服务器从事赚取比特币的活动。比特币是一种虚拟数字货币,挖矿是将一段时间内比特币系统中发生的交易进行确认,并记录在区块链上形成新区块的过程。 用于…

【字节流】写数据的三种方式以及两个问题

字节流写数据 1.字节流写数据三种方式 void write(int b)&#xff1a;将指定的字节写入此文件输出流一次写一个字节数据 package com.bytestream; import java.io.FileOutputStream; import java.io.IOException; public class FileOutputStreamDemo02 {public static void ma…

融云「北极星」数据监控平台:数据可视通晓全局,精准分析定位问题

↑ 点击预约“融云北极星”直播↑ 点击预约“实时社区”直播 近期&#xff0c;融云“北极星”数据系统完成功能迭代&#xff0c;新模块“数据监控平台”正式“履新上任”。关注【融云全球互联网通信云】了解更多 点击图片查看更多详情 此前&#xff0c;“北极星”系统主要为客…

水声功率放大器的作用是什么

水声功率放大器是一种专门用于水声设备的高功率电子设备&#xff0c;主要用于提升水下信号的传输距离和保证语音清晰度。它的作用在水下通信、水下测量、海洋科学等领域都非常重要。 其主要作用有以下几个方面&#xff1a; 增强信号传输距离 水声信号在水中传播会受到各种因素的…

有哪些好用的远程传输大文件的软件

随着网络技术的日益进步和普及&#xff0c;大文件远程传输已经成为了人们生活和工作中必不可少的一部分。然而&#xff0c;在远程传输大文件的过程中&#xff0c;经常会遇到传输速度慢、容易受到干扰等问题&#xff0c;因此需要一款高效、稳定、安全的大文件远程传输软件来解决…

离线安装docker

目录 1、下载docker 安装包 2、上传docker 到服务器目录/opt/ 3、解压docker-19.03.9.tgz 4、解压的docker文件夹全部移动至/usr/bin目录 5、将docker注册为系统服务 6、重启生效 6.1、重新加载配置文件 6.2、启动Docker服务 6.3、查看启动状态 ​6.4、 设置docker为开…

伦敦金分析时切勿过分自信

伦敦金价格会呈趋势变动&#xff0c;这是投资者进行技术分析最根本、最核心的因素。根据物理学上的动力法则&#xff0c;趋势的运行会延续&#xff0c;直到有反转的现象产生为止。事实上&#xff0c;虽然伦敦金的价格不断上下波动&#xff0c;但终究是朝一定的方向前进的&#…