Linux: USB Gadget 驱动简介

这篇具有很好参考价值的文章主要介绍了Linux: 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_add_gadget_udc,# USB,linux,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 驱动 的卸载过程:文章来源地址https://www.toymoban.com/news/detail-727420.html

// 通过 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()

到了这里,关于Linux: USB Gadget 驱动简介的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Rockchip linux USB 驱动开发

    Linux USB 协议栈是一个分层的架构,如下图 5-1 所示,左边是 USB Device 驱动,右边是 USB Host 驱动,最底层是 Rockchip 系列芯片不同 USB 控制器和 PHY 的驱动。                                               Linux USB 驱动架构 Rockchip 系列芯片,主要使用两种 USB 2.0 PHY IP:Innosi

    2024年01月25日
    浏览(48)
  • Linux驱动开发(十四)---USB驱动开发学习(键盘+鼠标)

    《Linux驱动开发(一)—环境搭建与hello world》 《Linux驱动开发(二)—驱动与设备的分离设计》 《Linux驱动开发(三)—设备树》 《Linux驱动开发(四)—树莓派内核编译》 《Linux驱动开发(五)—树莓派设备树配合驱动开发》 《Linux驱动开发(六)—树莓派配合硬件进行字

    2024年02月08日
    浏览(53)
  • 嵌入式Linux开发-USB驱动

    哥们马上就要被裁了,总得整理一下技术方面的积累,准备开始下一轮的面试和找工作之旅了。。。。 通用串行总线(USB)是主机和外围设备之间的一种连接。 从拓扑上来看,是一颗由几个点对点的连接构建而成的树。这些连接是连接设备和集线器(hub)的四线电缆(底线、电源线

    2024年02月20日
    浏览(73)
  • Linux:主机USB设备驱动简析

    限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。 本文基于 linux-4.14.132 内核代码进行分析。 其中: 一个 USB 主机控制器(HCD) 上最多可连接 127 个设备(HUB或Func),每个设备在枚举完成后会分配1个地址,地址区间为1~127,特殊地址0用于在设

    2023年04月21日
    浏览(49)
  • Linux上的USB驱动器基准测试

    如果你想在U盘或其他外置USB硬盘上执行基准测试,程序与在普通内部存储设备上执行基准测试非常相似。我们需要做的就是测量USB硬盘的读写速度。在本教程中,你将看到如何在 Linux 系统上通过命令行和图形用户界面对USB硬盘进行基准测试。 在本教程中,你将学习 如何在主

    2024年02月08日
    浏览(43)
  • 【Linux驱动】Linux--USB免驱摄像头驱动分析(基于5.4内核)

    【Linux应用】Linux–V4L2摄像头应用编程 【Linux】Linux–V4L2视频驱动框架 【Linux驱动】Linux–虚拟摄像头vivid驱动分析(基于5.4内核) 本文基于Linux 5.4内核,虚拟摄像头驱动文件在 driversmediausbuvc 目录下,本文深入分析了5.4内核下UVC驱动的实现及调用过程。读完本文可以对UVC驱

    2024年02月10日
    浏览(48)
  • Linux内核中的UVC(USB Video Class)驱动

    V4L2(Video for Linux 2)是Linux内核中的视频设备驱动框架,而UVC(USB Video Class)是一种使用USB接口的摄像头设备通信协议。在Linux内核中,V4L2和UVC之间存在以下关系: 1. V4L2支持多种视频设备:V4L2是一个通用的视频设备驱动框架,它旨在支持各种类型的视频设备,包括USB摄像头

    2024年02月08日
    浏览(43)
  • ubuntu22.04 linux内核5.19版本安装usb无线网卡驱动(linux小白)

    居住环境原因,台式机(Ubuntu22.04系统)没办法连网线,需要无线网卡。 1.经上网查询,买了绿联AC1300 双频无线网卡,支持内核5.15版本以下。 2.在绿联官网下载驱动,如下图,安装过程发现缺少 linux/net/ipx文件。  3.经 ubuntu linux内核版本>5.15安装usb无线网卡驱动_linux 内核5

    2024年02月15日
    浏览(50)
  • 台式机/工控机通过网线共享笔记本电脑无线网络&linux系统下 usb网卡的驱动安装

    一、台式机/工控机通过网线共享笔记本电脑无线网络 1、 将台式机通过网线和笔记本连接。 2、 将笔记本的“本地连接”和“无线网络连接”的ipv4均设置为自动获取。 4.修改台式机的IP地址为如下(对应笔记本信息) IP地址为192.168.XXX.12 子网掩码为255.255.255.0 默认网关为192

    2024年02月14日
    浏览(100)
  • usb摄像头驱动-core层usb设备的注册

    在ubuntu中接入罗技c920摄像头打印的信息如下: 在内核中,/driver/usb/core/driver.c 文件扮演了 USB 核心驱动程序管理的重要角色。该文件包含了 USB 核心驱动程序的实现,负责管理和调度 USB 设备的注册、匹配、连接和断开等操作。 具体而言,driver.c 文件的功能和作用包括: USB

    2024年02月06日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包