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 驅動框架有一個整體認識。左邊的部分,不僅限於 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 的實現類似。如 DesignWare 的 DWC3 也是經常被芯片廠商用來實現雙角色 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 驅動 的討論,就先到這裏,等到後面涉及到 UDC 和 USB 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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章