注:本文为“Linux USB Gadget ”相关文章合辑。
图片清晰度受引文原图所限。
略作重排,未整理去重。
如有内容异常,请看原文。
Linux: USB Gadget 驱动简介
JiMoKuangXiangQu
2025-02-27 09:46:38
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 = <®_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 Function(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 的 D+ 已经触发了枚举过程
,接下来看枚举期间发生了什么。
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);
}
// 普通端点 RX 中断处理
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()
Linux USB Gadget 驱动详解(一)
mkelehk
2019-07-28 12:39:33
由于 PC 的推广,USB(通用串行总线)是我们最熟知的通信总线规范之一,其他还有以太网、PCIE 总线和 RS232 串口等。这里我们主要讨论 USB。
USB 是一个主从通信架构,但只能一主多从。其中,USB 主机控制器有 EHCI(https://www.intel.com/content/www/us/en/products/docs/io/universal-serial-bus/ehci-specification.html) 等规范支持,而 USB 设备则包含普通 USB 设备和 HUB 两种。
USB 2.0 规范、USB class 等规范文档可以从 USB.org 获得。
另外,还有 ULPI、UTMI 接口(类比 RGMII、MII)支持 USB PHY(类比以太网 PHY)与 IP 核(OpenCores 有开源的 Verilog USB IP 核)对接。
Linux USB 驱动框架中包含很多有趣的模块,其中 drivers/usb/gadget
就是其中一个。Gadget
是 USB 设备驱动,可以按照 USB 规范和设备类进行配置,使其成为各种各样的通用 USB 设备,例如键鼠(HID class)、U 盘(mass storage class)、网卡(CDC class)、摄像头(Video class)等。
在开始 Gadget
源码分析之前,我们先通过实际操作来熟悉它!
我们分析的是 Linux 4.4.19
版本的 Gadget
源码,其他版本可能会有些出入,但原理是相通的。测试平台为通用的硬件平台 BeagleBone Black。
要使用 Linux 内核为我们准备的 Gadget
驱动示例,我们主要关注 legacy
文件夹。这个文件夹中的驱动是遗留下来的,已不建议使用,未来的 Linux 版本可能会移除。但我们这里先从传统入手!
在编译内核之前,通过 make menuconfig
修改配置。在 USB 菜单中将 Gadget
编译为模块(M),这样内核就可以编译出 Gadget
驱动。编译完成后,会在 legacy
文件夹下生成 g_xxx.ko
文件。例如,U 盘设备驱动为 g_mass_storage.ko
,而 function
文件夹则是功能接口(interface)的具体实现,生成形如 usb_f_xxx.ko
的文件,对应 U 盘驱动为 usb_f_mass_storage.ko
。
接下来,我们测试 U 盘设备驱动:
首先通过 SSH 登录到 BeagleBone Black 板(简称 BBB),然后执行以下操作,生成一个 FAT32 格式的 img
文件,并将其挂载到 sda
文件夹:
mkdir -p /var/sdcard;
cd /var/sdcard;
mkdir sda;
dd if=/dev/zero of=/var/sdcard/disk.img bs=1M count=20;
mkdosfs -F 32 disk.img
mount -t vfat -o sync /var/sdcard/disk.img /var/sdcard/sda
此时 /var/sdcard/sda
是一个 FAT32 格式的文件系统,我们可以在 Linux 系统中查看 disk.img
中包含的文件,便于测试验证!
然后在 BBB 上加载 USB 相关驱动:
modprobe musb_am335x
modprobe g_mass_storage file=/var/sdcard/disk.img
此时,U 盘设备驱动启动,并指定操作文件为 /var/sdcard/disk.img
。这样做的目的是让 PC 识别成一个“可移动磁盘”,同时在 PC 上读写磁盘时,实际上是对 BBB 板内的 /var/sdcard/disk.img
进行操作。
在 BBB 板上通过 lsmod
查看当前已加载的驱动:
lsmod
g_mass_storage 4414 0
usb_f_mass_storage 37916 2 g_mass_storage
libcomposite 44392 2 usb_f_mass_storage,g_mass_storage
musb_dsps 8235 0
musb_hdrc 71550 1 musb_dsps
udc_core 12063 2 musb_hdrc,libcomposite
usbcore 195125 2 musb_hdrc,usbhid
musb_am335x 1426 0
当 USB 线缆接入 PC 后,我们可以看到 PC 识别出“可移动磁盘”。我们可以在其上拖入文件,或者创建文件。在 BBB 板上,我们可以在已挂载的 /var/sdcard/sda
上看到刚从 PC 上拷贝过来的文件。
最后,需要说明的是,这种方式是 legacy
的,现已不推荐使用。后续内容将在《Linux USB Gadget 驱动详解(二)》中继续介绍。
Linux USB Gadget 驱动详解(二)
mkelehk
2019-07-28 15:18:23
在上篇《Linux USB Gadget 驱动详解(一)》中,我们了解到 Gadget
的测试方法,但在最后,我们留下了一个问题:如何使用新的方法进行 USB Gadget 驱动测试。
我们发现 Linux 的文档是一个宝库!
这篇文章的测试方法主要参考了以下文档:
linux-4.4.19/Documentation/ABI/testing/configfs-usb-gadget
linux-4.4.19/Documentation/usb/gadget-testing.txt
如果对 Gadget
感兴趣,除了阅读 Linux Gadget
源代码之外,还需要阅读 Linux 自带的文档。它们时常会给我们带来惊喜!
从 Linux 2.6 开始,Linux 内核加入了 configfs
文件系统,对应驱动加载命令为:
modprobe configfs
这个文件系统主要用于在用户态配置内核对象,使得用户态与内核态驱动的交互更加简单和紧密。具体可以参考内核相关文档及其源码。
我们仍然使用 BBB 板进行测试。在使用 configfs
开启 Gadget
驱动测试之前,需要加载必要的驱动程序:
modprobe musb_am335x
modprobe configfs
modprobe libcomposite
modprobe usb_f_mass_storage
其中,libcomposite
实现了通用的设备驱动 API。我们在开发自己的 Gadget
时,可以利用该 API,并参考已有示例,例如 f_mass_storage.c
、f_hid.c
等。
加载好上述驱动之后,就可以在用户态配置 U 盘驱动了:
# 挂载 configfs 文件系统
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget
mkdir gadget
cd gadget
# ls 一下,包含以下文件和目录
// UDC bMaxPacketSize0 functionsstrings
// bDeviceClass bcdDeviceidProduct
// bDeviceProtocol bcdUSB idVendor
// bDeviceSubClass configs os_desc
# 设置 USB 协议版本
echo 0x0200 > bcdUSB
# 定义产品的 VendorID 和 ProductID
echo "0x0525" > idVendor
echo "0xa4a5" > idProduct
# 实例化 "英语" ID:
mkdir strings/0x409
ls strings/0x409
// manufacturer product serialnumber
# 将开发商、产品和序列号字符串写入内核:
echo "01234567" > strings/0x409/serialnumber
echo "mkelehk" > strings/0x409/manufacturer
echo "upan" > strings/0x409/product
# 创建一个 USB 配置实例:
mkdir configs/config.1
ls configs/config.1
// MaxPower bmAttributes strings
echo 120 > configs/config.1/MaxPower
# 定义配置描述符使用的字符串
mkdir configs/config.1/strings/0x409
ls configs/config.1/strings/0x409/
// configuration
echo "mass_storage" > configs/config.1/strings/0x409/configuration
# 创建一个功能实例,需要注意的是,一个功能如果有多个实例的话,扩展名必须用数字编号:
mkdir functions/mass_storage.0
# 配置 U 盘参数
echo "/var/sdcard/disk.img" > functions/mass_storage.0/lun.0/file
echo 1 > functions/mass_storage.0/lun.0/removable
echo 0 > functions/mass_storage.0/lun.0/nofua
# 将功能实例绑定到配置 config.1
ln -s functions/mass_storage.0 configs/config.1
# 查找本机可获得的 UDC 实例
ls /sys/class/udc/
// musb-hdrc.1.auto
# 将 Gadget 驱动注册到 UDC 上,插上 USB 线到电脑上,电脑就会枚举 USB 设备。
echo "musb-hdrc.1.auto" > UDC
将上述步骤写成脚本,即可加载 U 盘驱动。其中 "/var/sdcard/disk.img"
可以按照上篇文章创建。当 echo "" > UDC
时,相当于模拟 U 盘拔掉,PC 磁盘符消失。
这种方式比 legacy
方式要先进得多。USB 设备属性的修改(如序列号、PID/VID、interface 等)、插拔模拟等都可以在用户态方便地完成,只需要在 configfs
文件系统下创建文件/文件夹,或者使用 echo
命令修改文件内容即可。
更重要的是,这种方法可以方便地实现 复合设备。USB 复合设备是指只有一个设备描述符、一个配置描述符,但同时有多个接口描述符(代表多种功能),例如一个键鼠设备,既有键盘又有鼠标。
接下来,我们创建一个复合键鼠设备。读者可以使用 configfs
尝试实现更多的复合设备进行测试。但需要注意的是,USB 的端点数量是有限的(USB 2.0 协议规定只用 4 位表示端点号,而在 FPGA 实现的 IP 核中,端点代表 FIFO),因此最大端点数是 16 对 IN/OUT 端点(包括默认端点 0)。端点资源有限,因此不可能复合任意多个设备。
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget
mkdir gadget
cd gadget
ls
# 包含以下文件和目录
// UDC bMaxPacketSize0 functionsstrings
// bDeviceClass bcdDeviceidProduct
// bDeviceProtocol bcdUSB idVendor
// bDeviceSubClass configs os_desc
# 设置 USB 协议版本 USB 2.0
echo 0x0200 > bcdUSB
# 定义产品的 VendorID 和 ProductID
echo "0x0525" > idVendor
echo "0xa4ac" > idProduct
# 实例化 "英语" ID:
mkdir strings/0x409
ls strings/0x409
// manufacturer product serialnumber
# 将开发商、产品和序列号字符串写入内核:
echo "76543210" > strings/0x409/serialnumber
echo "mkelehk" > strings/0x409/manufacturer
echo "keyboard_mouse" > strings/0x409/product
# 创建一个 USB 配置实例:
mkdir configs/config.1
ls configs/config.1
// MaxPower bmAttributes strings
echo 120 > configs/config.1/MaxPower
# 定义配置描述符使用的字符串
mkdir configs/config.1/strings/0x409
ls configs/config.1/strings/0x409/
// configuration
echo "hid" > configs/config.1/strings/0x409/configuration
# 创建功能实例,需要注意的是,一个功能如果有多个实例的话,扩展名必须用数字编号:
mkdir functions/hid.0
mkdir functions/hid.1
# 配置 HID 描述符(根据 HID 协议或者 g_hid.ko 对应的源码 hid.c 的说明)
echo 1 > functions/hid.0/subclass
echo 1 > functions/hid.0/protocol
echo 8 > functions/hid.0/report_length
echo -ne \\x5\\x1\\x9\\x2\\xa1.... > functions/hid.0/report_desc
echo 1 > functions/hid.1/subclass
echo 2 > functions/hid.1/protocol
echo 4 > functions/hid.1/report_length
echo -ne \\x5\\x1\\x9\\x2\\xa1.... > functions/hid.1/report_desc
# 将功能实例绑定到配置 config.1
ln -s functions/hid.0 configs/config.1
ln -s functions/hid.1 configs/config.1
# 查找本机可获得的 UDC 实例
ls /sys/class/udc/
// musb-hdrc.1.auto
# 将 Gadget 驱动注册到 UDC 上,插上 USB 线到电脑上,电脑就会枚举 USB 设备。
echo "musb-hdrc.1.auto" > UDC
其中,echo -ne \\\\x5\\\\x1\\\\x9\\\\x2\\\\xa1.... > functions/hid.0/report_desc
等为“HID 的报告描述符”填写,我没有填写完整。这部分需要自行查阅 HID class 的相关文档,但我在另一篇文章 《STM32F103C8T6 的 USB HID 复合设备的实现要点》 中有相关提示。
STM32F103C8T6 的 USB HID 复合设备的实现要点
mkelehk
2018-05-09 21:41:37
1. 可从官网下载 STM32_USB-FS-Device_Lib_V4.0,里面有 Custom_HID 示例可供参考。可以从 Custom_HID 修改得到。时刻记住“自己是 STM32F10X_MD 系列”以及“自己不是官方 demo 板”,即可将无用的代码去掉,例如去掉 ADC 等相关代码。
2. 需要将 SystemInit() 配置为 72MHz,否则无法工作,USB 工作在 12MHz。
3. 如果 D+ 直接上拉到 3.3V,将 demo 中 usb_pwr.c 的 PowerOn 和 PowerOff 函数改为空函数。否则 PC 无法识别,单步调试发现不知跳哪去了。假设不注释,亦可把usb_desc.c
的设备描述符bcdUSB
域改为0x0110
,即 USB 1.1,也是可行的。
4. 主要修改usb_conf.h
、usb_desc.c
以及usb_prop.c
,主要是配置描述符,增加键鼠的 HID 描述符和端点描述符,当然对应的报告描述符也需要修改与增加。
usb_prop.c
主要是在CustomHID_Reset
回调中添加新增端点的初始化,以及在CustomHID_Data_Setup
中对不同index
(对应复合设备里的不同接口)进行不同的描述符获取(HID 描述符获取函数、报告描述符获取函数等)回调函数的注册。
总体上比较容易移植!
重点:理解复合设备是“一个设备描述符中包含一个(少数设备有多个)配置描述符,而这个配置描述符又包含 N 个接口描述符(譬如,键鼠为 2 个),最后,每个接口描述符又包含 N 个端点描述符(键鼠为 2 个,一个 IN 端口,另外一个 OUT 端口,类型是中断传输方式)”。当然如果是 HID 设备还会有 HID 描述符。
顺序是:设备描述符 -> “配置描述符 1 -> 接口描述符 1_1 -> [HID 描述符 1_1] -> 端点描述符组 1_1 -> 接口描述符 1_2 -> [HID 描述符 1_2] -> 端点描述符组 1_2… -> 配置描述符 2 -> 接口描述符 2_1…"
难点:理解报告描述符,可参看 USB.org 相关的 HID class 文档。需要掌握的技巧是:从已有(现成)的复合设备(譬如键鼠)中使用 USBlyzer 工具摘取它的报告描述符,填到自己的描述符中,可实现很多,诸如写字板、touchpad(使用绝对坐标)、鼠标(相对坐标)等设备,十分强大的技巧!
最后,还可以一个接口描述符(Linux 下每个接口对应一个驱动)下含有多个报告描述符,它们之间是有 Report ID 来区分,这样的方式其实更为常见。它不同于上述复合设备。
或者参考 linux-4.4.19/Documentation/usb/gadget_hid.txt
里面有一个 /* hid descriptor for a keyboard */
的键盘 HID 报告描述符例子。
说了这么多,也还停留在对 Gadget
驱动程序的测试。接下来要对 Linux Gadget
进行源码分析了。
Linux USB Gadget 驱动详解(三)
mkelehk
2019-07-29 14:54:05
本文将对 Linux 4.4.19 版本的 USB Gadget 源码进行简单分析。鉴于前文反复测试 U 盘设备驱动,现从 linux-4.4.19/drivers/usb/gadget/legacy/mass_storage.c
开始分析。
目的是了解 U 盘设备驱动的工作原理,为什么它能让 PC 识别成“可移动磁盘”,以及它可以像市面上的 U 盘一样能读写文件。最后介绍内核 Gadget
框架提供的 API,助力实现自定义的 USB 设备驱动。
# mass_storage.c line: 242
/****************************** Some noise ******************************/
static struct usb_composite_driver msg_driver = {
.name = "g_mass_storage",
.dev = &msg_device_desc,
.max_speed = USB_SPEED_SUPER,
.needs_serial = 1,
.strings = dev_strings,
.bind = msg_bind,
.unbind = msg_unbind,
};
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_AUTHOR("Michal Nazarewicz");
MODULE_LICENSE("GPL");
static int __init msg_init(void)
{
return usb_composite_probe(&msg_driver);
}
module_init(msg_init);
static void msg_cleanup(void)
{
if (test_and_clear_bit(0, &msg_registered))
usb_composite_unregister(&msg_driver);
}
module_exit(msg_cleanup);
从代码中的 module_init(msg_init);
可知,驱动入口在 static int __init msg_init(void);
。其中,__init
修饰符表明这个函数仅在初始化期间使用,在模块被装载之后,它占用的资源就会释放掉。msg_init
函数只是一个入口,真正干活的是 usb_composite_probe
,这个函数在 composite.c
中实现:
# composite.c line: 2198
int usb_composite_probe(struct usb_composite_driver *driver)
{
struct usb_gadget_driver *gadget_driver;
if (!driver || !driver->dev || !driver->bind)
return -EINVAL;
if (!driver->name)
driver->name = "composite";
driver->gadget_driver = composite_driver_template;
gadget_driver = &driver->gadget_driver;
gadget_driver->function = (char *) driver->name;
gadget_driver->driver.name = driver->name;
gadget_driver->max_speed = driver->max_speed;
return usb_gadget_probe_driver(gadget_driver);
}
EXPORT_SYMBOL_GPL(usb_composite_probe);
我们先回到 msg_init()
函数,它用到了 struct usb_composite_driver
结构体对象的一个实例——msg_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);
int (*unbind)(struct usb_composite_dev *);
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;
};
static struct usb_composite_driver msg_driver = {
.name = "g_mass_storage",
.dev = &msg_device_desc,
.max_speed = USB_SPEED_SUPER,
.needs_serial = 1,
.strings = dev_strings,
.bind = msg_bind,
.unbind = msg_unbind,
};
这里我们定义了 U 盘驱动的“名称 name”、“设备描述符 dev”、“设备的速度”、“字符串描述符”、以及注册 bind
和 unbind
回调函数,这些参数和回调函数,最终会在 libcomposite.ko
驱动(composite.c
)中回调。
其中每一个 USB 设备均有一个设备描述符,设备描述符的字段及其意义参考 USB 2.0 规范的第九章(Linux 内核定义了 ch9.h
头文件专门来描述规范里描述的结构体和宏定义):
static struct usb_device_descriptor msg_device_desc = {
.bLength = sizeof msg_device_desc,
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = cpu_to_le16(0x0200),
.bDeviceClass = USB_CLASS_PER_INTERFACE,
/* Vendor and product id can be overridden by module parameters. */
.idVendor = cpu_to_le16(FSG_VENDOR_ID),
.idProduct = cpu_to_le16(FSG_PRODUCT_ID),
.bNumConfigurations = 1,
};
上述的 bNumConfigurations
字段值为 1,代表该 U 盘设备驱动只有一个配置(描述符)。
现在回到刚刚的 usb_composite_probe()
函数,我们先来分析这个函数内部究竟做了什么内容。
我们发现 usb_composite_probe
主要是填充好 struct usb_composite_driver
结构体里面的成员 gadget_driver
:
struct usb_gadget_driver gadget_driver;
值得注意的是,它在这里被赋值为 composite_driver_template
实例了,这个函数最后把重要的工作交给了 return usb_gadget_probe_driver(gadget_driver);
# linux-4.4.19/drivers/usb/gadget/udc/udc-core.c
int usb_gadget_probe_driver(struct usb_gadget_driver *driver)
{
struct usb_udc *udc = NULL;
int ret;
if (!driver || !driver->bind || !driver->setup)
return -EINVAL;
mutex_lock(&udc_lock);
list_for_each_entry(udc, &udc_list, list) {
/* For now we take the first one */
if (!udc->driver)
goto found;
}
pr_debug("couldn't find an available UDC\n");
mutex_unlock(&udc_lock);
return -ENODEV;
found:
ret = udc_bind_to_driver(udc, driver);
mutex_unlock(&udc_lock);
return ret;
}
EXPORT_SYMBOL_GPL(usb_gadget_probe_driver);
从上面 usb_gadget_probe_driver()
函数的代码片段可知,它在遍历 udc_list
链表,找到一个 udc
(USB 设备控制器驱动描述结构体)没有对应驱动的实例,然后通过 udc_bind_to_driver()
将 udc
驱动与上述的 composite_driver_template
模板实例绑定在一起,即能通过 udc
找回 composite_driver_template
实例,也能通过 composite_driver_template
实例找到绑定它的 udc
驱动。内核很喜欢使用 container_of()
宏找来找去,container_of()
作用是根据某对象成员的指针返回该对象的指针,有点父类找派生类的意思,作为内核面向对象编程的工具,container_of
宏用得很频繁!!既然有 udc
队列的遍历取出,必然有 udc
队列的添加,每注册一个 udc
驱动(usb_add_gadget_udc()
)就会把 udc
结构体添加到 udc
队列的尾部。
我们再来看 udc_bind_to_driver
做了什么:
# linux-4.4.19/drivers/usb/gadget/udc/udc-core.c
/* udc_lock must be held */
static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver)
{
int ret;
dev_dbg(&udc->dev, "registering UDC driver [%s]\n",
driver->function);
udc->driver = driver;
udc->dev.driver = &driver->driver;
udc->gadget->dev.driver = &driver->driver;
ret = driver->bind(udc->gadget, driver);
if (ret)
goto err1;
/* If OTG, the otg core starts the UDC when needed */
mutex_unlock(&udc_lock);
udc->is_otg = !usb_otg_register_gadget(udc->gadget, &otg_gadget_intf);
mutex_lock(&udc_lock);
if (!udc->is_otg) {
ret = usb_gadget_udc_start(udc);
if (ret) {
driver->unbind(udc->gadget);
goto err1;
}
usb_udc_connect_control(udc);
}
kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
return 0;
err1:
if (ret != -EISNAM)
dev_err(&udc->dev, "failed to start %s: %d\n",
udc->driver->function, ret);
udc->driver = NULL;
udc->dev.driver = NULL;
udc->gadget->dev.driver = NULL;
return ret;
}
上述代码片段说明 udc
与 driver
绑定其实就是一个“它们的结构体成员互相赋值”的过程:
udc->driver = driver;
udc->dev.driver = &driver->driver;
udc->gadget->dev.driver = &driver->driver;
这样我包含你,你包含我。最后回调了 driver
的 bind
函数,这个 bind
回调函数在 composite_driver_template
实例中赋值。最后,调用 usb_otg_register_gadget()
将 gadget
注册到 otg core
去,然后开启 udc
功能(usb_gadget_udc_start(udc)
)。对应 BBB 板用到的 TI 芯片 am3358 来说,OTG 用到了 Mentor Graphics 公司的 musb ip 核。usb_gadget_udc_start(udc)
事实上是回调了 drivers/usb/musb/musb_gadget.c
(对应 musb_hdrc.ko
驱动)的 musb_gadget_start()
函数进行操控 OTG 硬件寄存器。udc
驱动我们这里不作讨论!
当然了,如果 USB 控制器不支持 OTG,kernel 也没有加入 CONFIG_USB_OTG
的话,usb_otg_register_gadget()
其实是空函数,返回不支持(return -ENOTSUPP;
)。
下面看下 composite_bind()
static int composite_bind(struct usb_gadget *gadget,
struct usb_gadget_driver *gdriver)
{
struct usb_composite_dev *cdev;
struct usb_composite_driver *composite = to_cdriver(gdriver);
int status = -ENOMEM;
cdev = kzalloc(sizeof *cdev, GFP_KERNEL);
if (!cdev)
return status;
spin_lock_init(&cdev->lock);
cdev->gadget = gadget;
set_gadget_data(gadget, cdev);
INIT_LIST_HEAD(&cdev->configs);
INIT_LIST_HEAD(&cdev->gstrings);
status = composite_dev_prepare(composite, cdev);
if (status)
goto fail;
/* composite gadget needs to assign strings for whole device (like
* serial number), register function drivers, potentially update
* power state and consumption, etc
*/
status = composite->bind(cdev);
if (status < 0)
goto fail;
if (cdev->use_os_string) {
status = composite_os_desc_req_prepare(cdev, gadget->ep0);
if (status)
goto fail;
}
update_unchanged_dev_desc(&cdev->desc, composite->dev);
/* has userspace failed to provide a serial number? */
if (composite->needs_serial && !cdev->desc.iSerialNumber)
WARNING(cdev, "userspace failed to provide iSerialNumber\n");
INFO(cdev, "%s ready\n", composite->name);
return 0;
fail:
__composite_unbind(gadget, false);
return status;
}
其中,composite_dev_prepare()
主要是初始化 ep0
端点,申请了 ep0
的请求队列(struct usb_request *req
)、创建 sysfs 文件系统的属性文件、最后填充 struct usb_request *req
的 complete
完成函数。我们知道 ep0
是设备控制传输用到的端点,尤其在设备枚举、属性配置上起关键作用,所以我们要首先初始化。
int composite_dev_prepare(struct usb_composite_driver *composite,
struct usb_composite_dev *cdev)
{
struct usb_gadget *gadget = cdev->gadget;
int ret = -ENOMEM;
/* preallocate control response and buffer */
cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);
if (!cdev->req)
return -ENOMEM;
cdev->req->buf = kmalloc(USB_COMP_EP0_BUFSIZ, GFP_KERNEL);
if (!cdev->req->buf)
goto fail;
ret = device_create_file(&gadget->dev, &dev_attr_suspended);
if (ret)
goto fail_dev;
cdev->req->complete = composite_setup_complete;
cdev->req->context = cdev;
gadget->ep0->driver_data = cdev;
cdev->driver = composite;
/*
* As per USB compliance update, a device that is actively drawing
* more than 100mA from USB must report itself as bus-powered in
* the GetStatus(DEVICE) call.
*/
if (CONFIG_USB_GADGET_VBUS_DRAW <= USB_SELF_POWER_VBUS_MAX_DRAW)
usb_gadget_set_selfpowered(gadget);
/* interface and string IDs start at zero via kzalloc.
* we force endpoints to start unassigned; few controller
* drivers will zero ep->driver_data.
*/
usb_ep_autoconfig_reset(gadget);
return 0;
fail_dev:
kfree(cdev->req->buf);
fail:
usb_ep_free_request(gadget->ep0, cdev->req);
cdev->req = NULL;
return ret;
}
紧跟着,status = composite->bind(cdev);
实际上是回调 mass_storage.c
的 int msg_bind(struct usb_composite_dev *cdev)
,而 cdev
在 composite_bind()
函数开头通过 kzalloc
创建,并把 gadget
驱动等填充好 struct usb_composite_dev
结构体对象。msg_bind
(这个函数下一节再讲,这个函数比较复杂)。而 update_unchanged_dev_desc()
主要用于填充新的设备描述符信息,因为调用了 msg_bind()
后信息可能会有改变。
这就完成了 U 盘设备驱动的初始化(注册)了,但 U 盘为什么能工作起来,这里貌似没有体现出来,下篇文章将从 msg_bind()
说起。
说个题外话,除了可以使用 int usb_gadget_probe_driver(struct usb_gadget_driver *driver)
来注册外,还可以使用 int usb_udc_attach_driver(const char *name, struct usb_gadget_driver *driver);
该函数有个额外参数 name
,可以指定用哪个 USB 口来初始化 gadget
驱动,譬如 am3358 含有两个 USB 控制器 USB0 和 USB1,我们可以使用 ret = usb_udc_attach_driver("musb-hdrc.0.auto", gadget_driver);
来指定将 gadget
驱动挂到 USB0 上,这样该 U 盘 gadget
设备就可以固定在 USB0 这个 USB 口上模拟出一个 U 盘了,USB1 口则可以作它用。“musb-hdrc.0.auto” 这个名字似曾相识吧?对,在上篇文章中使用 configfs
来启动 U 盘时,通过 echo "musb-hdrc.1.auto" > UDC
,底层就是调用了这个 usb_udc_attach_driver()
函数,来指定使用 USB0。
其中,“ls /sys/class/udc/
”可以列举出当前系统注册了多少个 UDC(USB 设备控制器)。譬如,在 am3358 的 kernel 里那两个 USB 的 UDC 实例分别被命名为 musb-hdrc.0.auto
和 musb-hdrc.1.auto
。其他芯片的有自己的命名,一样可以通过 /sys/class/udc/
查看。
Linux USB Gadget 驱动详解(四)
mkelehk
2020-07-04 09:06:24
现从 msg_bind()
函数(drivers/usb/gadget/legacy/mass_storage.c
)开始讲起。
U 盘的 Gadget
驱动比较复杂,因为它包含几部分,包括 Gadget
驱动、U 盘相关的处理(SCSI 命令的处理、LUN 概念等)以及虚拟文件系统的读写操作(因为我们虚拟出来的 U 盘是用文件系统来保存的)等。
// 不失一般性,删除一些错误处理代码
static int msg_bind(struct usb_composite_dev *cdev)
{
static const struct fsg_operations ops = {
.thread_exits = msg_thread_exits,
};
struct fsg_opts *opts;
struct fsg_config config;
int status;
fi_msg = usb_get_function_instance("mass_storage");
fsg_config_from_params(&config, &mod_data, fsg_num_buffers);
opts = fsg_opts_from_func_inst(fi_msg);
opts->no_configfs = true;
status = fsg_common_set_num_buffers(opts->common, fsg_num_buffers);
fsg_common_set_ops(opts->common, &ops);
status = fsg_common_set_cdev(opts->common, cdev, config.can_stall);
fsg_common_set_sysfs(opts->common, true);
status = fsg_common_create_luns(opts->common, &config);
fsg_common_set_inquiry_string(opts->common, config.vendor_name,
config.product_name);
status = usb_string_ids_tab(cdev, strings_dev);
msg_device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id;
if (gadget_is_otg(cdev->gadget) && !otg_desc[0]) {
struct usb_descriptor_header *usb_desc;
usb_desc = usb_otg_descriptor_alloc(cdev->gadget);
if (!usb_desc)
goto fail_string_ids;
usb_otg_descriptor_init(cdev->gadget, usb_desc);
otg_desc[0] = usb_desc;
otg_desc[1] = NULL;
}
status = usb_add_config(cdev, &msg_config_driver, msg_do_config);
usb_composite_overwrite_options(cdev, &coverwrite);
return 0;
}
内核有个特点,就是驱动分层,Gadget
也不例外,它有一系列的 function
驱动(源码在 drivers/usb/gadget/function
目录下),譬如 f_mass_storage.c
(usb_f_mass_storage.ko
)就是实现 U 盘的功能,而 f_hid.c
(usb_f_hid.ko
)则是实现 HID 键鼠功能。为什么这么做呢?因为一个 USB 设备不一定只有一种功能,譬如“U 盘 + 网卡”、“摄像头 + 串口” 等等这种复合设备,其中 drivers/usb/gadget/legacy/multi.c
就是一个复合设备的例子。只是我们现在分析的 mass_storage.c
刚好只有一种功能(U 盘),你觉得没必要分层而已,毕竟驱动分层后变复杂了很多。你看 fi_msg = usb_get_function_instance("mass_storage")
,不跟进去根本不知道它做了什么,除非你比较熟悉 Gadget
的 API 接口。它其实是会得到由 drivers/usb/gadget/function/f_mass_storage.c
这个驱动(usb_f_mass_storage.ko
)所注册的“USB 功能实例”(struct usb_function_instance *
),而在 f_mass_storage.c
这边是预先通过 DECLARE_USB_FUNCTION_INIT
宏(在 include/linux/usb/composite.h
中定义)进行注册的:
// f_mass_storage.c
DECLARE_USB_FUNCTION_INIT(mass_storage, fsg_alloc_inst, fsg_alloc);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Michal Nazarewicz");
每调用 DECLARE_USB_FUNCTION_INIT
宏注册一个 function
实例,就会添加到一个全局链表变量的尾部(list_add_tail
),所以当在其他驱动中调用 usb_get_function_instance("mass_storage")
,就可以通过“mass_storage”名称在全局链表中找到对应的“USB 功能实例”(struct usb_function_instance *
)。
好了,知道这一层关系后,调用 usb_get_function_instance("mass_storage")
其实就是调用 f_mass_storage.c
的 fsg_alloc_inst()
函数:
// 去掉错误处理代码
static struct usb_function_instance *fsg_alloc_inst(void)
{
struct fsg_opts *opts;
struct fsg_lun_config config;
int rc;
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
...
mutex_init(&opts->lock);
opts->func_inst.free_func_inst = fsg_free_inst;
opts->common = fsg_common_setup(opts->common);
...
rc = fsg_common_set_num_buffers(opts->common,
CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS);
...
memset(&config, 0, sizeof(config));
config.removable = true;
rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0",
(const char **)&opts->func_inst.group.cg_item.ci_name);
...
opts->lun0.lun = opts->common->luns[0];
opts->lun0.lun_id = 0;
config_group_init_type_name(&opts->lun0.group, "lun.0", &fsg_lun_type);
opts->default_groups[0] = &opts->lun0.group;
opts->func_inst.group.default_groups = opts->default_groups;
config_group_init_type_name(&opts->func_inst.group, "", &fsg_func_type);
return &opts->func_inst;
}
在 fsg_alloc_inst()
中我们看到它先 malloc
了一个 struct fsg_opts
对象,该对象在 fsg
(File Storage Gadget)中算是比较重要的:
struct fsg_opts {
struct fsg_common *common;
struct usb_function_instance func_inst;
struct fsg_lun_opts lun0;
struct config_group *default_groups[2];
bool no_configfs; /* for legacy gadgets */
/*
* Read/write access to configfs attributes is handled by configfs.
*
* This is to protect the data from concurrent access by read/write
* and create symlink/remove symlink.
*/
struct mutex lock;
int refcnt;
};
因为它里面保存了 struct fsg_common *common
和 struct usb_function_instance func_inst
结构,而 no_configfs
字段则是指明是否使用 configfs
配置,这个参数在 msg_bind()
会配置,我们在第二篇文章中有介绍过使用 configfs
进行配置 Gadget
驱动,很是方便,只是代码讲解我选用了 legacy
驱动来分析,就不细说 configfs
相关的代码了。U 盘 Gadget
的主要全局变量都是聚集在 struct fsg_common
结构体中,f_mass_storage.c
大部分函数都是传入 struct fsg_common *
参数进去,是本驱动最重要的结构体!struct fsg_common *common
就是在这个时候分配好空间(fsg_common_setup()
)的,并且把 common
用到的自旋锁、完成量(completion
)等等初始化一遍,方便后续使用。然后分配 buffer
,譬如 32 个 16KB 的缓冲区,用于 U 盘数据传输,多个缓冲区能增加数据读写吞吐量。最后调用 fsg_common_create_lun()
创建逻辑单元(Logical Units/LUNs),也是为了后续 U 盘操作做准备。最后 return &opts->func_inst
返回 struct usb_function_instance *
。
后面两个函数是关于 configfs
的,毕竟要支持从 configfs
配置驱动,肯定要有相应的 API 支持,后面遇到 configs
相关的代码我都直接忽略。
OK,回到 msg_bind()
,我们这时得到了上述由 fsg_alloc_inst()
负责填充好并返回的 struct usb_function_instance *fi_msg
(全局变量),另外,我们都知道可以根据 struct usb_function_instance *
通过 container_of()
找回 struct fsg_opts
对象,方便后续继续配置(初始化)。
另外在 msg_bind()
中我们还出现了一个 struct fsg_config config
结构体,该结构体主要用于配置 U 盘 Gadget
支持的功能,譬如我们可以配置该 U 盘是否 removable
的,是否 CD/ROM,是否只读,是否 nofua
,有多少个逻辑块(LUNs),是否支持端点挂起(stall),以及为各个 LUN 指定映射的文件路径,因为一个 LUN 代表一个逻辑分区,既然我的 U 盘的底层存储是基于文件系统,自然要为每个分区都指定文件路径。这些参数从哪定义?自然是加载驱动时把参数带进去,譬如(modprobe g_mass_storage luns=2 file=/var/sdcard/disk.img,/var/sdcard/disk1.img
等等)。为什么我会知道使用 file=filename[,filename...]
,原因是内核有个文档专门描述怎么配置,具体在内核源码目录下 Documentation/usb/mass-storage.txt
)。struct fsg_config
的具体定义如下:
struct fsg_config {
unsigned nluns;
struct fsg_lun_config luns[FSG_MAX_LUNS];
/* Callback functions. */
const struct fsg_operations *ops;
/* Gadget's private data. */
void *private_data;
const char *vendor_name; /* 8 characters or less */
const char *product_name; /* 16 characters or less */
char can_stall;
unsigned int fsg_num_buffers;
};
其实就是在 msg_bind()->fsg_config_from_params()
里解释参数,不深入了。在这里又调用 fsg_common_set_num_buffers()
一次,之前在 f_mass_storage.c
的 fsg_alloc_inst()
也有初始化过,但没有释放之前的,貌似有内存泄漏了。后面通过 fsg_common_create_luns()
根据用户设置的 LUNs 数量重新创建 LUN。fsg_common_set_inquiry_string()
函数有意思,我们可以改变里面的内容,让 PC 插入我们的 U 盘时,下发 SCSI INQUIRY 命令时,返回的字符串。这个可以用 Bus Hound 或者 USBlyzer 抓包看到。usb_string_ids_tab(cdev, strings_dev)
是利用 strings_dev
结构生成对应的字符串描述符,用于 USB 枚举阶段返回给 USB host。接着是 OTG 相关,我们跳过。最重要的函数来了,usb_add_config(cdev, &msg_config_driver, msg_do_config)
。因为调用完它之后,U 盘就算初始化完毕!我们看下它的定义,它的具体实现我们不分析,有兴趣的朋友可以自行研究:
// composite.c
/**
* usb_add_config() - add a configuration to a device.
* @cdev: wraps the USB gadget
* @config: the configuration, with bConfigurationValue assigned
* @bind: the configuration's bind function
* Context: single threaded during gadget setup
*
* One of the main tasks of a composite @bind() routine is to
* add each of the configurations it supports, using this routine.
*
* This function returns the value of the configuration's @bind(), which
* is zero for success else a negative errno value. Binding configurations
* assigns global resources including string IDs, and per-configuration
* resources such as interface IDs and endpoints.
*/
int usb_add_config(struct usb_composite_dev *cdev,
struct usb_configuration *config,
int (*bind)(struct usb_configuration *))
{
...
}
功能是“为一个 USB 设备增加配置”,我们还记得 USB 的描述符结构吧?USB 设备描述符 -> 配置描述符 -> 接口描述符 -> 端点描述符…,其中,usb_add_config()
的 int (*bind)(struct usb_configuration *)
函数指针就是用于“为配置增加接口”,它会在调用 usb_add_config()
时被回调,对应我们的 U 盘 Gadget
就是 msg_do_config()
。我们先来看两个实参的定义:
static struct usb_configuration msg_config_driver = {
.label = "Linux File-Backed Storage",
.bConfigurationValue = 1,
.bmAttributes = USB_CONFIG_ATT_SELFPOWER,
};
static int msg_do_config(struct usb_configuration *c)
{
struct fsg_opts *opts;
int ret;
if (gadget_is_otg(c->cdev->gadget)) {
c->descriptors = otg_desc;
c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
}
opts = fsg_opts_from_func_inst(fi_msg);
f_msg = usb_get_function(fi_msg);
if (IS_ERR(f_msg))
return PTR_ERR(f_msg);
ret = usb_add_function(c, f_msg);
if (ret)
goto put_func;
return 0;
put_func:
usb_put_function(f_msg);
return ret;
}
msg_config_driver
结构主要是指定 .bConfigurationValue = 1
,使用第一个配置,因为到时 USB host(譬如 PC)在枚举本 U 盘阶段,会下发 SetConfiguration()
进行配置,这时 U 盘事实上要准备好 USB 传输相关的东西,如端点分配等,因为紧接着 PC 就会用端点描述符上指定的端点进行通信,你得提前准备好。
上面的代码片段 msg_do_config
中的 fi_msg
变量就是在 msg_bind()
中通过 usb_get_function_instance("mass_storage")
得到的全局变量。它先是 struct usb_function *f_msg = usb_get_function(fi_msg)
,该函数事实上是调用了 f_mass_storage.c
注册好的 fsg_alloc()
:
static struct usb_function *fsg_alloc(struct usb_function_instance *fi)
{
struct fsg_opts *opts = fsg_opts_from_func_inst(fi);
struct fsg_common *common = opts->common;
struct fsg_dev *fsg;
fsg = kzalloc(sizeof(*fsg), GFP_KERNEL);
if (unlikely(!fsg))
return ERR_PTR(-ENOMEM);
mutex_lock(&opts->lock);
opts->refcnt++;
mutex_unlock(&opts->lock);
fsg->function.name = FSG_DRIVER_DESC;
fsg->function.bind = fsg_bind;
fsg->function.unbind = fsg_unbind;
fsg->function.setup = fsg_setup;
fsg->function.set_alt = fsg_set_alt;
fsg->function.disable = fsg_disable;
fsg->function.free_func = fsg_free;
fsg->common = common;
return &fsg->function;
}
这个函数超级重要,很明显它的作用是填充一些回调函数,以及返回 struct usb_function *f_msg
,在我们前面的代码分析就已经知道 struct fsg_opts *opts
和 struct fsg_common *common
结构早在 fsg_alloc_inst()
时就已经分配好空间了,这里只是拿回指针进行填充(初始化)而已。其中 fsg_setup
和 fsg_bind
回调十分重要,未来会被 composite.c
框架回调,用于处理 USB host 下发 USB 枚举以及 U 盘的实际读写操作。后面再单独分析。
msg_do_config
最后调用的一个函数是 usb_add_function(c, f_msg)
,这样就把上面填充的 fsg_setup
和 fsg_bind
等回调注册到 composite.c
框架了。其中参数 c
是在 static struct usb_configuration msg_config_driver
基础上通过 usb_add_config()->usb_add_config_only()
进行进一步配置,剩下就是 composite.c
框架处理的事情了。那么 fsg_setup()
和 fsg_bind()
在什么情况下会被回调:
/**
* usb_add_function() - add a function to a configuration
* @config: the configuration
* @function: the function being added
* Context: single threaded during gadget setup
*
* After initialization, each configuration must have one or more
* functions added to it. Adding a function involves calling its @bind()
* method to allocate resources such as interface and string identifiers
* and endpoints.
*
* This function returns the value of the function's bind(), which is
* zero for success else a negative errno value.
*/
int usb_add_function(struct usb_configuration *config,
struct usb_function *function)
{
...
...
/* REVISIT *require* function->bind? */
if (function->bind) {
value = function->bind(config, function);
...
} else
value = 0;
...
...
}
我们发现 msg_do_config()
在调用 usb_add_function()
后,fsg_bind()
就会被回调,这时就越来越接近我们的初衷:“为啥它能让 PC 识别成‘可移动磁盘’,以及它可以像市面上的 U 盘一样能读写文件”。
Linux USB Gadget 驱动详解(五)
mkelehk
2020-07-04 12:46:20
现从 fsg_bind()
讲起。
// 不失一般性,删掉错误处理和 configfs 相关代码
static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
{
struct fsg_dev *fsg = fsg_from_func(f);
struct fsg_common *common = fsg->common;
struct usb_gadget *gadget = c->cdev->gadget;
int i;
struct usb_ep *ep;
unsigned max_burst;
int ret;
struct fsg_opts *opts;
/* Don't allow to bind if we don't have at least one LUN */
ret = _fsg_common_get_max_lun(common);
opts = fsg_opts_from_func_inst(f->fi);
if (!common->thread_task) {
common->state = FSG_STATE_IDLE;
common->thread_task =
kthread_create(fsg_main_thread, common, "file-storage");
if (IS_ERR(common->thread_task)) {
...
}
wake_up_process(common->thread_task);
}
fsg->gadget = gadget;
/* New interface */
i = usb_interface_id(c, f);
fsg_intf_desc.bInterfaceNumber = i;
fsg->interface_number = i;
/* Find all the endpoints we will use */
ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc);
fsg->bulk_in = ep;
ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc);
fsg->bulk_out = ep;
/* Assume endpoint addresses are the same for both speeds */
fsg_hs_bulk_in_desc.bEndpointAddress =
fsg_fs_bulk_in_desc.bEndpointAddress;
fsg_hs_bulk_out_desc.bEndpointAddress =
fsg_fs_bulk_out_desc.bEndpointAddress;
/* Calculate bMaxBurst, we know packet size is 1024 */
max_burst = min_t(unsigned, FSG_BUFLEN / 1024, 15);
fsg_ss_bulk_in_desc.bEndpointAddress =
fsg_fs_bulk_in_desc.bEndpointAddress;
fsg_ss_bulk_in_comp_desc.bMaxBurst = max_burst;
fsg_ss_bulk_out_desc.bEndpointAddress =
fsg_fs_bulk_out_desc.bEndpointAddress;
fsg_ss_bulk_out_comp_desc.bMaxBurst = max_burst;
ret = usb_assign_descriptors(f, fsg_fs_function, fsg_hs_function,
fsg_ss_function);
...
return 0;
}
可以看到该函数主要是通过 kthread_create + wake_up_process
的组合创建了一个内核线程 fsg_main_thread
,名称是“file-storage”,通过 shell 的 ps
可以看到。另外就是利用 usb_interface_id()
分配一个接口号,填充进接口描述符,以便在设备枚举时返回给 USB host,最后利用 composite.c
框架所创建的 gadget
对象对 U 盘的 IN/OUT 端点初始化:
// storage_common.c
/*
* Three full-speed endpoint descriptors: bulk-in, bulk-out, and
* interrupt-in.
*/
struct usb_endpoint_descriptor fsg_fs_bulk_in_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
/* wMaxPacketSize set by autoconfiguration */
};
struct usb_endpoint_descriptor fsg_fs_bulk_out_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
/* wMaxPacketSize set by autoconfiguration */
};
/* Find all the endpoints we will use */
ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc);
fsg->bulk_in = ep;
ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc);
fsg->bulk_out = ep;
因为只有端点(FIFO)初始化完,未来才可以利用由 usb_ep_queue()
传输 USB 数据,而我们的 U 盘 Gadget
驱动就利用 usb_ep_queue()
封装而成以下两个函数用于传输 U 盘数据:
static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh);
static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh);
当然现在只是初始化,U 盘还不能正常工作,毕竟现在连 fsg_setup()
都没有调用!也就是说还没被 USB host 枚举到,也没有 SetConfiguration()
等操作。那么究竟什么时候调用 fsg_setup()
回调??
事实上,我们无需关心,因为在 composite.c
(libcomposite.ko
)框架已经帮我们处理好细节了,在 composite_setup()
函数中被处理,该函数处于中断上下文中,不要放入 sleep
或者切换调度之类的代码。相当于当我们插入我们的 U 盘到 PC 上,它就会在 composite_setup()
回调我们的 fsg_setup()
。
fsg_setup()
中主要处理了两个 Mass Storage Class 相关的请求:US_BULK_RESET_REQUEST
和 US_BULK_GET_MAX_LUN
,这些请求都是由 USB host(电脑的 U 盘驱动)下发给 U 盘的,U 盘只有按要求处理即可。
想要深入理解 Gadget
,还是需要仔细阅读 libcomposite.c
(libcomposite.ko
)的实现,否则我们就只会调调 Gadget
的 API,以后我再讲解 libcomposite.ko
和 udc
驱动的流程。
下面主要分析 fsg_main_thread()
;基本上 U 盘的所有读写操作都是靠它完成,十分重要的一个函数!
static int fsg_main_thread(void *common_)
{
struct fsg_common *common = common_;
/*
* Allow the thread to be killed by a signal, but set the signal mask
* to block everything but INT, TERM, KILL, and USR1.
*/
allow_signal(SIGINT);
allow_signal(SIGTERM);
allow_signal(SIGKILL);
allow_signal(SIGUSR1);
/* Allow the thread to be frozen */
set_freezable();
/*
* Arrange for userspace references to be interpreted as kernel
* pointers. That way we can pass a kernel pointer to a routine
* that expects a __user pointer and it will work okay.
*/
set_fs(get_ds());
/* The main loop */
while (common->state != FSG_STATE_TERMINATED) {
if (exception_in_progress(common) || signal_pending(current)) {
handle_exception(common);
continue;
}
if (!common->running) {
sleep_thread(common, true);
continue;
}
if (get_next_command(common))
continue;
spin_lock_irq(&common->lock);
if (!exception_in_progress(common))
common->state = FSG_STATE_DATA_PHASE;
spin_unlock_irq(&common->lock);
if (do_scsi_command(common) || finish_reply(common))
continue;
spin_lock_irq(&common->lock);
if (!exception_in_progress(common))
common->state = FSG_STATE_STATUS_PHASE;
spin_unlock_irq(&common->lock);
if (send_status(common))
continue;
spin_lock_irq(&common->lock);
if (!exception_in_progress(common))
common->state = FSG_STATE_IDLE;
spin_unlock_irq(&common->lock);
}
spin_lock_irq(&common->lock);
common->thread_task = NULL;
spin_unlock_irq(&common->lock);
if (!common->ops || !common->ops->thread_exits
|| common->ops->thread_exits(common) < 0) {
int i;
down_write(&common->filesem);
for (i = 0; i < ARRAY_SIZE(common->luns); --i) {
struct fsg_lun *curlun = common->luns[i];
if (!curlun || !fsg_lun_is_open(curlun))
continue;
fsg_lun_close(curlun);
curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT;
}
up_write(&common->filesem);
}
/* Let fsg_unbind() know the thread has exited */
complete_and_exit(&common->thread_notifier, 0);
}
它先是声明可以被信号 kill
调该内核线程,以及能冻结,譬如 kill -STOP
、kill -CONT
之类的。它主要是靠如下几个函数工作:get_next_command(common)
、do_scsi_command(common) || finish_reply(common)
和 send_status(common)
。
Bulk only 的传输协议可阅读《usbmassbulk_10.pdf》文档,下面只是截取其中一部分:
以及
以及阅读 SCSI 命令文档。本 U 盘 Gadget
只是实现其中一些常用的 SCSI 命令子集而已,我们就挑读(READ_10
)和写(WRITE_10
)这两个操作:
和
可以看到主要是 do_read
和 do_write
。因为流程比较繁杂,这里只简单描述,有兴趣的朋友可以逐行代码分析研究,do_write()
是通过 start_out_transfer()
从 USB host 获取到文件数据,然后调用 vfs_write()
写入文件系统,完成了将文件写入 U 盘的过程;而 do_read()
则是先通过 vfs_read()
从文件系统(加载驱动时指定的文件路径 file=filename[,filename...]
)中读取文件,然后调用 start_in_transfer()
写入 USB host,完成了读取 U 盘内的文件到 PC。
终于把 U 盘 Gadget
驱动讲解了一遍,当然只是粗略走读了一下,代码细节上还是需要大家仔细研究,譬如没有深入到 composite.c
(libcomposite.ko
)Gadget
框架的具体实现,U 盘方面也没有细节到每个 SCSI 命令的讲解,以及没有讲解 CBW/CSW 的细节处理(有兴趣可以对照《usbmassbulk_10.pdf》阅读代码)等。
via:
-
Linux: USB Gadget 驱动简介 - CSDN 博客
https://blog.csdn.net/JiMoKuangXiangQu/article/details/131749565 -
linux usb gadget 驱动详解(一)-CSDN 博客
https://blog.csdn.net/litao31415/article/details/97612362 -
linux usb gadget 驱动详解(二)_linux usb gadget 盘符 - CSDN 博客
https://blog.csdn.net/litao31415/article/details/97613438- STM32F103C8T6 的 USB HID 复合设备的实现要点_c8t6 按键 hidusb-CSDN 博客
https://blog.csdn.net/litao31415/article/details/80260063
- STM32F103C8T6 的 USB HID 复合设备的实现要点_c8t6 按键 hidusb-CSDN 博客
-
linux usb gadget 驱动详解(三)_linux gadget u 盘驱动 - CSDN 博客
https://blog.csdn.net/litao31415/article/details/97622407 -
linux usb gadget 驱动详解(四)-CSDN 博客
https://blog.csdn.net/litao31415/article/details/99689399 -
linux usb gadget 驱动详解(五)_libcomposite-CSDN 博客
https://blog.csdn.net/litao31415/article/details/107121767