Linux USB Gadget 框架中数据结构体之间的关系

47 阅读9分钟

Linux USB Gadget 框架中数据结构体之间的关系

1. 什么是 USB Gadget 框架?

简单来说,USB Gadget 框架 是 Linux 内核中一套允许设备作为 USB 从设备(或称为“外设”)运行的软件基础设施。

  • USB Host(主机): 通常是你的电脑、手机或充电器。它控制总线,为设备供电。我们电脑上的 USB 口就是 Host 口。
  • USB Gadget(外设): 通常是 U 盘、键盘、鼠标、打印机等。它们响应主机的请求。

当你的嵌入式设备(如树莓派、智能手机、IoT 设备)通过 USB 线连接到电脑时,USB Gadget 框架就决定了这个嵌入式设备在电脑眼中“是什么”。它可以让你的开发板在电脑上显示为一个 U 盘、一个网卡、一个串口,或者多种功能的复合设备。

核心思想: 它提供了一个标准化的、可扩展的软件接口,让开发者能够在不关心底层 USB 控制器的具体硬件差异的情况下,配置和实现各种 USB 设备功能。

2. 核心组件与层次结构

USB Gadget 框架采用分层的架构,如下图所示(概念图):

+---------------------------------------------------+
|             Gadget Function Drivers               |
|  (e.g., f_acm.ko, f_serial.ko, f_mass_storage.ko  |
+---------------------------------------------------+
|                Gadget API (UDC Core)              |
|        (drivers/usb/gadget/udc/core.c)            |
+---------------------------------------------------+
|            USB Device Controller (UDC) Driver     |
|   (e.g., dwc3.ko, udc-core.c, chip-specific)      |
+---------------------------------------------------+
|            Hardware (USB Device Controller)       |
+---------------------------------------------------+

让我们自底向上地理解这些组件:

2.1 USB 设备控制器(UDC)硬件

这是 SoC 或微控制器内部的一个物理硬件模块,负责处理底层的 USB 协议、电气信号和数据处理。例如,DWC3。

2.2 UDC 驱动

这是直接操作 UDC 硬件的内核驱动程序。它负责:

  • 初始化硬件。
  • 管理 USB 端点(Endpoints)。
  • 处理 USB 请求块(URB)。
  • 向 Gadget 核心层注册自己,表明“我这个硬件控制器可以被用来做 Gadget”。

关键结构体: struct usb_udc 和 struct usb_gadget_ops(包含一系列操作硬件的函数指针)。

2.3 Gadget API / UDC 核心层

这是框架的核心,位于 drivers/usb/gadget/udc/core.c。它充当 UDC 驱动和上层功能驱动的“粘合剂”。它提供了一组统一的 API,使得功能驱动无需关心底层是哪种 UDC 硬件。

2.4 Gadget Function 驱动

这是实现具体 USB 设备功能的驱动程序。例如:

  • f_acm: 实现 USB CDC 串行(ACM)功能驱动程序。
  • f_serial: 实现通用 USB 串行功能驱动程序。
  • f_mass_storage: 实现大容量存储设备(U盘)的驱动程序。

关键结构体: struct usb_function 和 struct usb_configuration。

一个 Function 驱动定义了:

  • USB 设备描述符: 告诉主机“我是什么”。
  • 接口 和 端点: 实现功能所需的通信管道。
  • 数据处理回调函数: 当主机发送或请求数据时应该做什么。

3. USB Gadget 数据结构之间的关系

  • 分析所用代码基于linux kernel5.4

先上个根据分析结果绘制的数据结构关系图:

usb-gadget.png

3.1 注册 Gadget Function 的驱动usb_function_driver

在数据结构关系图靠下的位置有个struct list_head类型的全局变量 func_list 。func_list 是全局usb_function_driver实例链表,用于管理所有已注册的 usb_function_driver实例。

以 f_acm 为例看下usb_function_driver如何注册到 func_list

drivers/usb/gadget/function/f_acm.c

DECLARE_USB_FUNCTION_INIT(acm, acm_alloc_instance, acm_alloc_func);

DECLARE_USB_FUNCTION_INIT宏定义实现:
include/linux/usb/composite.h

#define DECLARE_USB_FUNCTION(_name, _inst_alloc, _func_alloc)		\
	static struct usb_function_driver _name ## usb_func = {		\
		.name = __stringify(_name),				\
		.mod  = THIS_MODULE,					\
		.alloc_inst = _inst_alloc,				\
		.alloc_func = _func_alloc,				\
	};								\
	MODULE_ALIAS("usbfunc:"__stringify(_name));

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

所以DECLARE_USB_FUNCTION_INIT(acm, acm_alloc_instance, acm_alloc_func);展开后:

static struct usb_function_driver acmusb_func = {
    .name = __stringify(acm),
    .mod  = THIS_MODULE,
    .alloc_inst = acm_alloc_instance,
    .alloc_func = acm_alloc_func,
};
MODULE_ALIAS("usbfunc:"__stringify(acm));

static int __init acmmod_init(void)
{
    return usb_function_register(&acmusb_func);
}
static void __exit acmmod_exit(void)
{
    usb_function_unregister(&acmusb_func);
}
module_init(acmmod_init);
module_exit(acmmod_exit);

kernel 启动后, 调用 acmmod_init() --> usb_function_register(&acmusb_func) 直接把 acmusb_func : usb_function_driver 注册到 func_list.

所有 usb_function_driver 注册完成后, func_list 链表内容示例如下:

func_list (全局 USB 函数驱动链表)
    │
    ├── usb_function_driver (acm)
    │       ├── name = "acm"
    │       ├── mod = f_acm.ko 模块
    │       ├── alloc_inst = acm_alloc_instance()
    │       ├── alloc_func = acm_alloc_func()
    │       └── list (链表节点)
    │
    ├── usb_function_driver (gser)
    │       ├── name = "gser"
    │       ├── mod = f_serial.ko 模块
    │       ├── alloc_inst = gser_alloc_inst()
    │       ├── alloc_func = gser_alloc()
    │       └── list
    │
    └── usb_function_driver (mass_storage)
            ├── name = "mass_storage"
            ├── mod = f_mass_storage.ko 模块
            ├── alloc_inst = fsg_alloc_inst()
            ├── alloc_func = fsg_alloc()
            └── list

3.2 用户空间通过 configfs 配置 USB Gadget 的步骤说明

从 Linux 3.11 开始,引入了 USB Gadget ConfigFS。它通过一个虚拟文件系统 (/config/usb-gadget) 允许用户在用户空间动态地、无需编译地创建和配置 USB Gadget。

3.2.1 挂载 configfs 虚拟文件系统
mount configfs none /config
  • 内核虽然支持 configfs,但默认不挂载
  • 挂载后才能在 /config 目录下看到配置接口
  • 通过文件操作(创建目录/文件、写入配置)来与内核交互
3.2.2 创建 gadget

命令:

mkdir /config/usb_gadget/g1 0770 shell shell

代码:
drivers/usb/gadget/configfs.c

static struct config_group *gadgets_make(
        struct config_group *group,
        const char *name)
{
    struct gadget_info *gi;
    gi = kzalloc(sizeof(*gi), GFP_KERNEL);
    gi->composite.bind = configfs_do_nothing;
    gi->composite.gadget_driver = configfs_driver_template;

    gi->composite.gadget_driver.function = kstrdup(name, GFP_KERNEL);
    gi->composite.name = gi->composite.gadget_driver.function;
}
static struct configfs_group_operations gadgets_ops = {
    .make_group     = &gadgets_make,
    .drop_item      = &gadgets_drop,
};

static const struct config_item_type gadgets_type = {
    .ct_group_ops   = &gadgets_ops,
    .ct_owner       = THIS_MODULE,
};
static struct configfs_subsystem gadget_subsys = {
    .su_group = {
        .cg_item = {
            .ci_namebuf = "usb_gadget", // 在 /config/ 下的目录名
            .ci_type = &gadgets_type, // 绑定类型
        },
    },
    .su_mutex = __MUTEX_INITIALIZER(gadget_subsys.su_mutex),
};
static int __init gadget_cfs_init(void)
{
    int ret;
    config_group_init(&gadget_subsys.su_group); // 初始化组结构
    ret = configfs_register_subsystem(&gadget_subsys); // 注册子系统
    return ret;
}
module_init(gadget_cfs_init);
  • configfs 的初始化函数 gadget_cfs_init 注册 "usb_gadget" 子系统到 configfs
  • gadget_subsys.cg_item.ci_type 绑定类型 gadgets_type
  • gadgets_type 的 ct_group_ops 配置了操作函数 gadgets_ops
  • make_group: 当用户在 /config/usb_gadget/ 下创建新目录时调用的函数
  • drop_item: 当用户删除该目录时调用的清理函数

当创建目录 /config/usb_gadget/g1 时,内核会调用 gadgets_make():

  • 创建代表 g1 的 struct gadget_info
  • 初始化 usb_composite_driver
  • 初始化 usb_composite_dev
  • 将 configfs_driver_template 赋值给 gi->composite.gadget_driver
3.2.3 创建配置

命令:

mkdir /config/usb_gadget/g1/configs/b.1 0770 shell shell

代码:
drivers/usb/gadget/configfs.c

/* 定义配置描述符的组操作 */
static struct configfs_group_operations config_desc_ops = {
    .make_group     = &config_desc_make, // 创建子组时调用
    .drop_item      = &config_desc_drop, // 删除子组时调用
};
/* 定义配置描述符类型 */
static const struct config_item_type config_desc_type = {
    .ct_group_ops   = &config_desc_ops, // 绑定组操作
    .ct_owner       = THIS_MODULE,   
};
static struct config_group *gadgets_make(
        struct config_group *group,
        const char *name)
{
    /* 初始化子目录"configs" */
    config_group_init_type_name(&gi->configs_group, "configs",
            &config_desc_type);
}

函数 config_desc_make():

static struct config_group *config_desc_make(
        struct config_group *group,
        const char *name)
{
    struct gadget_info *gi;
    struct config_usb_cfg *cfg;

    gi = container_of(group, struct gadget_info, configs_group);
    cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
    cfg->c.label = kstrdup(buf, GFP_KERNEL); //'b'
    cfg->c.bConfigurationValue = num; // 1
    ret = usb_add_config_only(&gi->cdev, &cfg->c);
}

因此在 configs 目录下创建子目录时会调用 config_desc_make():

  • 创建代表 b.1 的 struct config_usb_cfg
  • 初始化 config_usb_cfg 里的 usb_configuration

usb_add_config_only():

  • 将步骤 3.2.2 中的 usb_composite_dev 赋值给 usb_configuration.cdev
  • 将 usb_configuration 加入到步骤 3.2.2 中的 usb_composite_dev.configs

经过 usb_add_config_only() 后:

  • gadget_info 可以通过 usb_composite_dev 遍历 usb_configuration 和 config_usb_cfg 实例。
  • config_usb_cfg 也可以通过 usb_configuration.cdev 取得对应的 gadget_info 实例, 例如 config_usb_cfg_link() 里的操作。
3.2.4 创建 function

命令:

mkdir /config/usb_gadget/g1/functions/acm.gs0 0770 shell shell

代码:
drivers/usb/gadget/configfs.c

static struct configfs_group_operations functions_ops = {
    .make_group     = &function_make,
    .drop_item      = &function_drop,
};

static const struct config_item_type functions_type = {
    .ct_group_ops   = &functions_ops,
    .ct_owner       = THIS_MODULE,
};
static struct config_group *gadgets_make(
        struct config_group *group,
        const char *name)
{
    config_group_init_type_name(&gi->functions_group, "functions",
            &functions_type);
}

在 functions 目录下创建子目录时会调用 function_make():

static struct config_group *function_make(
        struct config_group *group,
        const char *name)
{
    struct gadget_info *gi;
    struct usb_function_instance *fi;
    char buf[MAX_NAME_LEN];
    char *func_name;
    char *instance_name;
    int ret;
    /* name = acm.gs0 */
    ret = snprintf(buf, MAX_NAME_LEN, "%s", name);

    func_name = buf;
    instance_name = strchr(func_name, '.');

    *instance_name = '\0'; // func_name = acm
    instance_name++; // instance_name = gs0

    fi = usb_get_function_instance(func_name);

    gi = container_of(group, struct gadget_info, functions_group);

    list_add_tail(&fi->cfs_list, &gi->available_func);
}

获取 usb_function_instance 实例的代码:
drivers/usb/gadget/functions.c

struct usb_function_instance *usb_get_function_instance(const char *name)
{
    return try_get_usb_function_instance(name);
}
static struct usb_function_instance *try_get_usb_function_instance(const char *name)
{
    struct usb_function_driver *fd;
    struct usb_function_instance *fi;
    list_for_each_entry(fd, &func_list, list) {
        if (strcmp(name, fd->name))
            continue;
        fi = fd->alloc_inst();
        fi->fd = fd;
        break;
    }
    return fi;
}

try_get_usb_function_instance():

  • 根据传入的 name 从 3.1 中的 func_list 找出已注册的 usb_function_driver
  • 调用 alloc_inst() 创建 usb_function_instance
  • acm 对应的 alloc_inst() = acm_alloc_instance(), acm_alloc_instance() 会创建 f_serial_opts 和 usb_function_instance实例
  • usb_function_driver 赋值给 usb_function_instance.fd
  • 返回 usb_function_instance 直至 function_make

function_make() 获取到 usb_function_instance 后:

  • 将 usb_function_instance 关联至 gadget_info.available_func
3.2.5 链接 function 到配置

命令:

symlink /config/usb_gadget/g1/functions/acm.gs0 /config/usb_gadget/g1/configs/b.1/f5

代码:
drivers/usb/gadget/configfs.c

static struct configfs_item_operations gadget_config_item_ops = {
    .allow_link             = config_usb_cfg_link,
    .drop_link              = config_usb_cfg_unlink,
};
static const struct config_item_type gadget_config_type = {
    .ct_item_ops    = &gadget_config_item_ops,
};
static struct config_group *config_desc_make(
        struct config_group *group,
        const char *name)
{
    config_group_init_type_name(&cfg->group, name,
                &gadget_config_type);
}

上面代码配置了 configs 的子目录(如 b.1) 下执行 symlink 操作会调用的函数:config_usb_cfg_link()
drivers/usb/gadget/configfs.c

/* 
 * usb_cfg_ci 代表 configs/b.1/f5
 * usb_func_ci 代表 functions/acm.gs0
 */
static int config_usb_cfg_link(
    struct config_item *usb_cfg_ci,
    struct config_item *usb_func_ci)
{
    struct config_usb_cfg *cfg = to_config_usb_cfg(usb_cfg_ci);
    struct usb_composite_dev *cdev = cfg->c.cdev;
    struct gadget_info *gi = container_of(cdev, struct gadget_info, cdev);

    struct config_group *group = to_config_group(usb_func_ci);
        /* 获取 acm 对应的 usb_function_instance */
    struct usb_function_instance *fi = container_of(group,
            struct usb_function_instance, group);
    struct usb_function_instance *a_fi;
    struct usb_function *f;

    /* 创建 acm 对应的 usb_funcrion */
    f = usb_get_function(fi);
    /* usb_funcrion 绑定到 config_usb_cfg.func_list */
    list_add_tail(&f->list, &cfg->func_list);
}

usb_get_function() 调用 usb_function_driver.alloc_func 创建 usb_function:
drivers/usb/gadget/functions.c

struct usb_function *usb_get_function(struct usb_function_instance *fi)
{
    struct usb_function *f;

    f = fi->fd->alloc_func(fi);
    if (IS_ERR(f))
        return f;
    f->fi = fi;
    return f;
}

对 acm 来说, fi->fd->alloc_func = acm_alloc_func:
drivers/usb/gadget/function/f_acm.c

/*
struct f_acm {
    struct gserial    port;
}
struct gserial {
    struct usb_function    func;
}
*/
static struct usb_function *acm_alloc_func(struct usb_function_instance *fi)
{
    struct f_serial_opts *opts;
    struct f_acm *acm;
    acm = kzalloc(sizeof(*acm), GFP_KERNEL);

    acm->port.func.name = "acm";
    acm->port.func.strings = acm_strings;
    /* descriptors are per-instance copies */
    acm->port.func.bind = acm_bind;
    acm->port.func.set_alt = acm_set_alt;
    acm->port.func.setup = acm_setup;

    opts = container_of(fi, struct f_serial_opts, func_inst);
    acm->port_num = opts->port_num;

    return &acm->port.func;
}

总结 config_usb_cfg_link() 做的事情:

  • 创建 usb_function 实例,
  • 将 usb_function 实例添加到 config_usb_cfg.func_list
3.2.6. 启用 UDC

命令:

write /config/usb_gadget/g1/UDC ${sys.usb.controller} //"20000000.dwc3"

调用链:

用户空间
    ↓
echo "20000000.dwc3" > /config/usb_gadget/g1/UDC
    ↓
gadget_dev_desc_UDC_store()
    ↓
usb_gadget_probe_driver(gadget_info.composite.gadget_driver) //传入步骤3.2.2 的 configfs_driver_template
    ↓
从全局变量 udc_list 找出 "20000000.dwc3" 的 usb_udc
    ↓
udc_bind_to_driver()
    ↓
driver->bind() = configfs_composite_bind()
    ↓
遍历 gadget_info.cdev.configs 里所有 usb_configuration (如: configs/b.1)
    ↓
取出包含 usb_configuration 的 config_usb_cfg
    ↓
遍历 config_usb_cfg.func_list 中的 usb_function (如: acm.gs0)
    ↓
usb_add_function() 绑定 function 到 usb_configuration.functions
    ↓
执行 function->bind() (如: acm_bind())
    ↓
usb_gadget_udc_start() 启动 udc
    ↓
USB 设备就绪

启用 UDC 后,主机端就会检测到一个新的 USB 设备(如: acm)。