Linux: USB Gadget 驱动简介

news2024/10/7 4:24:04

文章目录

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

针对文件内容匹配,过滤,排序

grep 过滤&#xff0c;针对文本内容进行过滤&#xff0c;也就是查找 grep -i 忽略大小写&#xff0c;默认的可以不加 grep -n 显示匹配行号 grep -c 只统计匹配的行数 grep -v ,取反&#xff0c;查找的内容不显示 grep的作用就是过滤文本内容&#xff0c;是针对行来进行处理…

navicate_windows_14

1.新建文本文档2.输入如下内容 echo off set dnInfo set dn2ShellFolder set rpHKEY_CURRENT_USER\Software\Classes\CLSID :: reg delete HKEY_CURRENT_USER\Software\PremiumSoft\NavicatPremium\Registration14XCS /f %针对<strong><font color"#FF0000"…

​python接口自动化(四十二)- 项目架构设计之大结局(超详解)​

简介 这一篇主要是将前边的所有知识做一个整合&#xff0c;把各种各样的砖块---模块&#xff08;post请求&#xff0c;get请求&#xff0c;logging&#xff0c;参数关联&#xff0c;接口封装等等&#xff09;垒起来&#xff0c;搭建一个房子。并且有很多小伙伴对于接口项目测试…

spring复习:(40)全注解的spring AOP

零、需要的依赖&#xff1a; <dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.9</version></dependency><dependency><groupId>org.aspectj</groupId><arti…

TikTok正在测试“商店”直播功能!这次能成功吗?

Tik Tok作为世界上增长最快的中国社交媒体平台&#xff0c;越南、印尼、日本、印度、美国……它每登录一个国家&#xff0c;都能极快地占领当地民众的手机屏幕&#xff0c;在极短的时间内成为现象级的产品。 可以说只用了短短3年的时间&#xff0c;Tik Tok就火遍了全球&#x…

整数拆分(力扣)动态规划 JAVA

给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。 示例 2: 输入: n 10 输出: 36 解释: 10 3 3 4…

如何无代码将AI图像生成接入您的办公系统中,实现业务流程自动化

当设计接到一个需求时&#xff0c;按照常规的工作安排&#xff0c;从对接需求到最后完成效果图最短时间都要在5天左右&#xff0c;如果遇到高要求的客户或领导&#xff0c;后期还需要在电脑上进一步调整细节&#xff0c;一张成片起码要花上数小时时间去完成。 而人工智能的出现…

每天一道大厂SQL题【Day26】脉脉真题实战(二)活跃时长的均值

文章目录 每天一道大厂SQL题【Day26】脉脉真题实战(二)活跃时长的均值每日语录第26题 中级题: 活跃时长的均值1. 需求列表思路分析 答案获取加技术群讨论附表文末SQL小技巧 后记 每天一道大厂SQL题【Day26】脉脉真题实战(二)活跃时长的均值 大家好&#xff0c;我是Maynor。相信…

LRU 算法,但 get 和 put 必须 O(1),用哈希表

https://leetcode.cn/problems/lru-cache/ 题目有key、value的&#xff0c;直接就上map了 结果&#xff1a;&#x1f605; 仔细一看&#xff0c;原来要 get 和 put 必须 O(1) 只能抛弃树型数据结构了 线性的数据结构也可以吧&#xff0c;如果可以构造出一个队列&#xff0c…

[ 容器 ] Docker 基本管理

目录 一、Docker 概述1.1 Docker 是什么&#xff1f;1.2 Docker 的宗旨1.3 容器的优点1.4 Docker 与 虚拟机的区别1.5 容器在内核中支持的两种技术namespace的六大类型 二、Docker核心概念2.1 镜像2.2 容器2.3 仓库 总结 一、Docker 概述 1.1 Docker 是什么&#xff1f; 是一…

OpenCV for Python 学习第三天 :图片处理之NumPy库与OpenCV相结合

上一篇博客我们了解了图像在OpenCV中的保存方式。并且我们自己上手创建了一张灰度图像和一张彩色图像。除此之外&#xff0c;我们还了解到了彩色图像通道在OpenCV中和我们日常所了解的不一样&#xff0c;是通过BGR的顺序进行编码的。咱们一定要记清楚哦~ 那么今天&#xff0c;我…

STL好难(8):map和set

目录 1.一些概念的理解 &#x1f349;关联式容器和序列式容器 &#x1f349;key模型、key/value模型 &#x1f349;树形结构关联式容器 2.set的介绍 &#x1f349;set文档 &#x1f349;set的使用 &#x1f352;set的模板参数列表 &#x1f352;set的构造 &#x1f3…

【TiDB理论知识 05】TiKV-Raft协议

目录 一 概念 二 raft共识算法对于TiKV的几个重要功能 1 Raft日志复制 1 Raft日志复制流程 2 名词解释 分层次理解TIKV 2 Raft Leader选举 集群初始状态时Leader选举流程 数据正在复制时Leader选举流程 初始化时的特殊情况 raft 参数与Tidb 参数对应关系 一 概念 le…

SpringCloud系列(十六)[分布式搜索引擎篇] - DSL 查询及相关性算分的学习 (部分)

在SpringCloud系列&#xff08;十五&#xff09;[分布式搜索引擎篇] - 结合实际应用场景学习并使用 RestClient 客户端 API这篇文章中我们已经对 RestClient 有了初步的了解, 并且已经将一些数据进行了存储, 但是这并不是我们学习 ElasticSearch 的目的, ElasticSearch 最擅长的…

Java8之Stream流

目录 简介 特点 Stream操作步骤 创建 中间操作 筛选与切片 filter(Predicate p) distinct() limit(long maxSize) skip(long n) 映射 map(Function f) flatMap(Function f) 排序 自然排序 定制排序 终止操作 匹配与查找 归约 收集 好处 不足 简介 在编写…

css基本样式的使用

1、高度和宽度 .c1{height: 300px;width: 500px; }注意事项&#xff1a; 宽度&#xff0c;支持百分比行内标签&#xff0c;默认无效块级标签&#xff0c;默认有效&#xff08;即使右侧空白&#xff0c;也不给你占用&#xff09; 块级和行内标签 css样式 标签&#xff1a; di…

echarts 地图点击常见问题

echats 散点图不支持缩放 echarts 地图点击激活label如何去除 高德loca 1.4版本热力图报错 绘制的颜色区间是 0 --1 高德地图销毁不生效 自己傻逼&#xff0c;每次没有清空数组导致叠加数据&#xff0c;约点数据越多。 为何用高德地图district.search查询不到别的省数据&…

【SpringBoot】SpringBoot的创建和运行

1.什么是SpringBoot&#xff1f; Spring 的诞⽣是为了简化 Java 程序的开发的&#xff0c;⽽ Spring Boot 的诞⽣是为了简化 Spring 程序开发 的。 Spring Boot是由Pivotal团队提供的基于Spring的框架&#xff0c;该框架使用了特定的方式来进行配置&#xff0c;从而使开发…

洗鞋小程序开发

上门洗鞋小程序为用户提供了便捷、高效的鞋子洗护服务。通过手机即可预约上门取送鞋子&#xff0c;省时省力&#xff0c;让鞋子焕然一新。下面我们来看看这个小程序的具体功能介绍。 1. 预约取送服务&#xff1a;用户可以在上门洗鞋小程序中选择合适的时间和地点&#xff0c;预…

访问学者面试申请如何应对?

作为一个学者面试申请者&#xff0c;面对这一重要机会&#xff0c;我们需要认真准备并采取适当的应对策略。下面知识人网小编将提供一些建议&#xff0c;帮助你在面试中取得良好的表现。 首先&#xff0c;在准备阶段&#xff0c;你应该研究并了解申请机构的背景和研究方向。了解…