作者: baron
个人博客: baron-z.cn
usb 驱动集合:
linux usb 驱动 - hcd 驱动框架
linux usb 驱动 - configfs 文件系统
linux usb 驱动 - udc 驱动框架
usb 是什么, usb 只是一种数据传输的方式, usb 的作用就是数据传输, 我们接受到了数据时候做什么, 是我们自己决定, 在usb 中则通过描述符来确定这些数据要来干什么, 设备描述符用于描述当前是什么 usb 设备, 以及这个设备支持什么样的功能, 这个支持的功能(串口, uvc等)则由 function
来描述. 设备的描述符以及功能等由 usb_composite_dev
进行统一的管理维护.
configfs
则将这种管理进行动态化, 使得我们能够动态的去配置我们的 usb 为特定的 usb 设备. 当然我们也可以不用 configfs
自己去写死 usb 支持的功能, 甚至驱动不做处理仅仅导出 libusb
库, 应用程序自己实现 usb 设备支持的功能的各种描述符. 因为 usb 只是数据传输, 具体的功能实现可以是驱动也可以应用, 取决与开发的需求.本文将以 configfs
以及串口设备为例分析 usb
做 device
的驱动框架.
阅读这篇文章之前请先阅读我博客里面的 linux usb 驱动 - configfs 文件系统 这篇文章, 因为后文都是默认已经知道了 configfs 文件系统的调用流程, 不会对 configfs 调用流程做过多解释.
一、 usb device 驱动框架
USB 有两种基本模式: Host 模式和 Device 模式.
-
在 Host 模式下,设备作为主机,负责控制 USB 总线并管理与外设的通信, 通常是电脑、智能手机等主控设备, 它们发起通信并配置外设.
-
在 Device 模式下, 设备作为外设, 等待主机发起通信请求并提供特定功能, 例如 USB 存储设备、键盘、鼠标等.
-
USB 还有一个 OTG(On-The-Go) 模式, 它允许设备根据需要在 Host 模式和 Device 模式之间切换. 模式的切换通常是通过 id 引脚来实现的, 在 usb otg 模式中, id 引脚的电平状态决定了设备是 Host 还是 Device 模式. 一般情况 id 脚逻辑低表示 Host, id 脚逻辑高表示高电平. 具体由 usb 控制器的实现来决定.
usb device 的框架整体分为 3 层, gadget、compiste、function、用户空间通过 configs 对它们进行配置以及交互.
- Gadget 实现了 usb 设备的底层数据传输和通信等功能
- Composite 则通过 usb_composite_dev 数据结构用来描述一个完成的 usb 设备, 当于 host 端的 usb device. 他是整体的描述.
- function 层描述了具体的 usb 设备接口, 如 usb 存储设备、网络适配器、音频设备等. 它对应 host 端的接口设备.
- configfs 则是用户可以动态的对 usb 进行配置, 它包括 usb_composite_dev 中对设备描述符的配置、function 中具体的功能的配置等, 同时将这些属性信息导出到用户空间.
二、 gadget
gadget 的分配和初始化流程如下图所示.
注册流程就是对核心的数据结构进行初始化, 核心调用接口如下.
dwc2_init() -->
hsotg = devm_kzalloc(); --> //分配 hostg, gadget 作为其成员变量在这里分配.
dwc2_gadget_init() -->
hsotg->gadget.ops = &dwc2_hsotg_gadget_ops; --> // 设置 ops 回调这里就是 udc 的底层操作
hsotg->ctrl_buff = devm_kzalloc(); --> // 为端点分配控制传输的 buffer
hsotg->ep0_buff = devm_kzalloc(); --> // 为端点分配控制传输的 buffe
ret = devm_request_irq(); --> // 注册 gadget 中断
hsotg->ctrl_req = dwc2_hsotg_ep_alloc_request(); --> // 为控制传输分配 req
for (epnum = 0; epnum < hsotg->num_of_eps; epnum++) --> // 为每一个端点分配内存
dwc2_hsotg_initep();
usb_add_gadget_udc() -->
usb_add_gadget_udc_release() -->
- 分配
hostg
结构,gadget
作为其成员变量在这里分配. - 设置
gadget.ops
回调函数, 这个回调就是hcd
相关的底层接口 - 分配
ctrl_buff
和ep0_buff
用来做端点分配控制传输的buffer
- 注册
gadget
中断, 为控制传输分配ctrl_req
dwc2_hsotg_initep
为每一个端点分配内存, 即分配eps_in[epnum]
数组. 有个小细节,ep0
并没有链接到gadget->ep_list
.- 分配
usb_udc
将其链接到udc_list
链表, 注册对应设备到设备模型 - 更新 udc 电源状态, 设置
udc->gadget = gadget
和adget->udc = udc
注册完成之后各个数据结构关系如下所示.
这个数据结构图很重要, 通过这个图我们可以总结出一下信息:
- 所有的端点公用
dwc2_hsotg_ep_ops
操作接口, 它提供了端点寄存器相关的底层操作 - 所有的端点由
dwc2_hsotg
进行统一管理, 统一被存放到eps_in[]/eps_out[]
数组 - gadget 向 udc 提供
dwc2_hsotg_gadget_ops
接口用于 udc 基本的初始化等操作.
这个阶段是自动创建的, 配置好 dts 之后, 开机的时候会自动加载 usb 的 udc 驱动.这部分配置每个平台都会有点区别, 具体需要找平台要配置手册.
三、配置 usb 串口
Linux3.11 版本配引入了 configfs 之后, usb 设备的配置就通过 configfs 配置. 本文以 usb 串口配置为了进行分析. 这个脚本完整的展示了一个串口 usb 设备的配置流程, 它分为以下步骤: 创建配置根目录、配置设备描述符、配置配置描述符、配置功能描述符、配置字符串描述符、关联 compiste 和 gadget
echo "Creating the USB gadget..."
# 挂载 configfs 文件系统,configfs 允许动态配置 USB 设备
mount -t configfs none /sys/kernel/config
# 创建名为 "serial_demo" 的 USB 设备目录,并进入该目录
mkdir /sys/kernel/config/usb_gadget/serial_demo
cd /sys/kernel/config/usb_gadget/serial_demo
# 配置设备描述符
echo "Setting Device Descriptor..."
echo "0x02" > bDeviceClass # 设置设备类别,0x02 表示通信设备类(如串口)
echo "0x00" > bDeviceSubClass # 设置设备子类别,0x00 表示子类为普通串口
echo "0x00" > bDeviceProtocol # 设置设备协议,0x00 表示没有特定协议
echo "0x0200" > bcdUSB # 设置 USB 版本号,0x0200 表示 USB 2.0
echo "0x2400" > bcdDevice # 设置设备的设备编号,0x2400 表示特定的设备版本
echo $VID > idVendor # 设置设备厂商 ID,$VID 是变量,通常由主机配置
echo $PID > idProduct # 设置设备产品 ID,$PID 是变量,通常由主机配置
# 配置字符串描述符(用于标识设备的字符串,如制造商名、产品名、序列号等)
echo "Setting English strings..."
mkdir strings/0x409 # 创建 0x409(表示英语)语言的字符串目录
echo "INGENIC" > strings/0x409/manufacturer # 设置制造商为 INGENIC
echo "Gadget Serial v2.4" > strings/0x409/product # 设置产品名称
echo "ingenic-serial" > strings/0x409/serialnumber # 设置设备序列号
# 配置配置描述符(表示 USB 设备的配置属性,如最大功率、设备的属性等)
echo "Creating Config..."
mkdir configs/c.1 # 创建配置文件夹,配置 1(c.1)
echo "120" > configs/c.1/MaxPower # 设置最大功率为 120 毫安
echo "0x80" > configs/c.1/bmAttributes # 设置配置的属性,0x80 表示支持自供电且支持远程唤醒
mkdir configs/c.1/strings/0x409 # 为配置创建语言描述符
echo "INGENIC" > configs/c.1/strings/0x409/configuration # 设置配置的描述符
# 配置功能描述符
echo "Creating functions..."
# 创建一个名为 "acm.0" 的功能目录,"acm" 表示串口设备类,"0" 是实例编号
mkdir functions/acm.0
# 将刚创建的功能(acm.0)链接到配置 "c.1" 中,表示将该功能添加到配置中
ln -s functions/acm.0 configs/c.1
# 关联 compiste 和 gadget
echo `ls /sys/class/udc/` > UDC # 这个步骤将设备连接到 USB 设备控制器(UDC)并启动 USB 外设
四、compiste
composite 的核心数据结构是 usb_composite_dev
它用来管理并描述具体的 usb 设备. 和 composite 相关的配置流程有, 创建配置根目录、配置设备描述符、配置字符串描述符、配置配置描述符.
1、创建配置根目录
mkdir /sys/kernel/config/usb_gadget/serial_demo
这个操作是创建一个 configfs 文件系根目录, 由 configfs 文件系统的特性可以知道会回调到 gadgets_make 接口. 调用流程如下.
mkdir /sys/kernel/config/usb_gadget/serial_demo -->
syscall_common() -->
sys_mkdirat() -->
vfs_mkdir() -->
dir->i_op->mkdir() -->
configfs_mkdir() -->
type->ct_group_ops->make_group() --> // 在这里填充子目录的 group 配置项
gadgets_make 是一个关键接口, 我们看看他的实现.
static struct config_group *gadgets_make(
struct config_group *group,
const char *name)
{
struct gadget_info *gi;
printk("%s\n", __func__);
gi = kzalloc(sizeof(*gi), GFP_KERNEL);
if (!gi)
return ERR_PTR(-ENOMEM);
// 设置默认的配置组, 就是当前目录的子目录
gi->group.default_groups = gi->default_groups;
gi->group.default_groups[0] = &gi->functions_group; // functiong 子目录
gi->group.default_groups[1] = &gi->configs_group; // configs 子目录
gi->group.default_groups[2] = &gi->strings_group; // string 子目录
gi->group.default_groups[3] = &gi->os_desc_group; // os_desc 子目录
// 设置目录名称以及 type, type 里面就包含有默认的属性文件.
config_group_init_type_name(&gi->functions_group, "functions",
&functions_type);
config_group_init_type_name(&gi->configs_group, "configs",
&config_desc_type);
config_group_init_type_name(&gi->strings_group, "strings",
&gadget_strings_strings_type);
config_group_init_type_name(&gi->os_desc_group, "os_desc",
&os_desc_type);
gi->composite.bind = configfs_do_nothing; // 设置 bind 接口, 这里设置为空函数
gi->composite.unbind = configfs_do_nothing; // 设置 unbind 接口, 这里设置为空函数
gi->composite.suspend = NULL; // 设置 susupend 回调为 null
gi->composite.resume = NULL; // 设置 resume 回调为 null
gi->composite.max_speed = USB_SPEED_SUPER; // 设置速度为高速
spin_lock_init(&gi->spinlock);
mutex_init(&gi->lock);
INIT_LIST_HEAD(&gi->string_list);
INIT_LIST_HEAD(&gi->available_func);
composite_init_dev(&gi->cdev);
gi->cdev.desc.bLength = USB_DT_DEVICE_SIZE; // 初实话设备描述符 usb_device_descriptor 结构体的大小
gi->cdev.desc.bDescriptorType = USB_DT_DEVICE; // 设置设备类型
gi->cdev.desc.bcdDevice = cpu_to_le16(get_default_bcdDevice()); // 设置出厂编码
// 设置 gadget_driver
gi->composite.gadget_driver = configfs_driver_template;
// 设置名称
gi->composite.gadget_driver.function = kstrdup(name, GFP_KERNEL);
gi->composite.name = gi->composite.gadget_driver.function;
if (!gi->composite.gadget_driver.function)
goto err;
if (android_device_set_drvdata(gi) < 0)
goto err;
// 同样的为为 gadget 创建一个目录
config_group_init_type_name(&gi->group, name,
&gadget_root_type);
return &gi->group;
err:
kfree(gi);
return ERR_PTR(-ENOMEM);
}
函数调用完成之后将会创建以 usb_composite_dev 为中心的 compiste 层对应的数据结构关系.
总结一下函数的功能:
- 分配 gadget_info 数据结构,
usb_composite_driver
和usb_composite_dev
作为其成员变量同时被分配 - 设置
functions
、configs
、strings
、os_desc
等默认的子目录配置项.即创建子目录与子目录的属性文件. - 简单初始化设备描述符
cdev.desc
的基本信息 - 初始化
composite
并且填充gadget_driver
2、配置设备描述符
# 配置设备描述符
echo "Setting Device Descriptor..."
echo "0x02" > bDeviceClass # 设置设备类别,0x02 表示通信设备类(如串口)
echo "0x00" > bDeviceSubClass # 设置设备子类别,0x00 表示子类为普通串口
echo "0x00" > bDeviceProtocol # 设置设备协议,0x00 表示没有特定协议
echo "0x0200" > bcdUSB # 设置 USB 版本号,0x0200 表示 USB 2.0
echo "0x2400" > bcdDevice # 设置设备的设备编号,0x2400 表示特定的设备版本
echo $VID > idVendor # 设置设备厂商 ID,$VID 是变量,通常由主机配置
echo $PID > idProduct # 设置设备产品 ID,$PID 是变量,通常由主机配置
我们在内核中找到对应的属性接口的定义.
GI_DEVICE_DESC_SIMPLE_R_u16(bcdUSB);
GI_DEVICE_DESC_SIMPLE_RW(bDeviceClass, u8);
GI_DEVICE_DESC_SIMPLE_RW(bDeviceSubClass, u8);
GI_DEVICE_DESC_SIMPLE_RW(bDeviceProtocol, u8);
GI_DEVICE_DESC_SIMPLE_RW(bMaxPacketSize0, u8);
GI_DEVICE_DESC_SIMPLE_RW(idVendor, u16);
GI_DEVICE_DESC_SIMPLE_RW(idProduct, u16);
GI_DEVICE_DESC_SIMPLE_R_u16(bcdDevice);
#define GI_DEVICE_DESC_SIMPLE_RW(_name, _type) \
GI_DEVICE_DESC_SIMPLE_R_##_type(_name) \
GI_DEVICE_DESC_SIMPLE_W_##_type(_name)
// 这里只保留一个其他都是一样的.
// 展开后就是对 cdev 中的设备描述符中的变量进行读写.
#define GI_DEVICE_DESC_SIMPLE_R_u16(__name) \
static ssize_t gadget_dev_desc_##__name##_show(struct config_item *item, \
char *page) \
{ \
return sprintf(page, "0x%04x\n", \
le16_to_cpup(&to_gadget_info(item)->cdev.desc.__name)); \
}
我们把这个展开之后发现, 应用配置配置配置描述符, 就是通过属性文件直接读写 usb_composite_dev
的 usb_device_descriptor
设备描述符结构体的成员变量.
3、配置字符串描述符
# 配置字符串描述符(用于标识设备的字符串,如制造商名、产品名、序列号等)
echo "Setting English strings..."
mkdir strings/0x409 # 创建 0x409(表示英语)语言的字符串目录
echo "INGENIC" > strings/0x409/manufacturer # 设置制造商为 INGENIC
echo "Gadget Serial v2.4" > strings/0x409/product # 设置产品名称
echo "ingenic-serial" > strings/0x409/serialnumber # 设置设备序列号
1. strings
strings 目录在这里有点特殊单独讨论, strings 从名字可以知道是对字符串进行管理. configs 将字符串管理的 api 进行了统一, 对应的在 gadget_configfs.h
这头文件里面.
// include/linux/usb/gadget_configfs.h
#ifndef __GADGET_CONFIGFS__
#define __GADGET_CONFIGFS__
#include <linux/configfs.h>
int check_user_usb_string(const char *name,
struct usb_gadget_strings *stringtab_dev);
// 这个宏用于创建一个 store 函数
// 注意这里的 to_##__struct(item) 需要我们自己实现
#define GS_STRINGS_W(__struct, __name) \
static ssize_t __struct##_##__name##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
struct __struct *gs = to_##__struct(item); \
int ret; \
\
ret = usb_string_copy(page, &gs->__name); \
if (ret) \
return ret; \
return len; \
}
// 这个宏用于创建一个 show 函数
// 注意这里的 to_##__struct(item) 需要我们自己实现
#define GS_STRINGS_R(__struct, __name) \
static ssize_t __struct##_##__name##_show(struct config_item *item, char *page) \
{ \
struct __struct *gs = to_##__struct(item); \
return sprintf(page, "%s\n", gs->__name ?: ""); \
}
// 创建 show 和 store 函数并将其填充掉 CONFIGFS_ATTR 创建的 configfs_attribute 结构中
#define GS_STRINGS_RW(struct_name, _name) \
GS_STRINGS_R(struct_name, _name) \
GS_STRINGS_W(struct_name, _name) \
CONFIGFS_ATTR(struct_name##_, _name)
// 创建一个 config_item_type struct_in##_langid_type 数据结构并且填充对应的属性数组为 struct_in##_langid_attrs
#define USB_CONFIG_STRING_RW_OPS(struct_in) \
static struct configfs_item_operations struct_in##_langid_item_ops = { \
.release = struct_in##_attr_release, \
}; \
\
static struct config_item_type struct_in##_langid_type = { \
.ct_item_ops = &struct_in##_langid_item_ops, \
.ct_attrs = struct_in##_langid_attrs, \
.ct_owner = THIS_MODULE, \
}
// 创建对应配置的 make 回调接口和相关的数据接口. 这个函数中会用到前面创建的 config_item_type struct_in##_langid_type.
#define USB_CONFIG_STRINGS_LANG(struct_in, struct_member) \
static struct config_group *struct_in##_strings_make( \
struct config_group *group, \
const char *name) \
{ \
struct struct_member *gi; \
struct struct_in *gs; \
struct struct_in *new; \
int langs = 0; \
int ret; \
\
new = kzalloc(sizeof(*new), GFP_KERNEL); \
if (!new) \
return ERR_PTR(-ENOMEM); \
\
ret = check_user_usb_string(name, &new->stringtab_dev); \
if (ret) \
goto err; \
config_group_init_type_name(&new->group, name, \
&struct_in##_langid_type); \
\
gi = container_of(group, struct struct_member, strings_group); \
ret = -EEXIST; \
list_for_each_entry(gs, &gi->string_list, list) { \
if (gs->stringtab_dev.language == new->stringtab_dev.language) \
goto err; \
langs++; \
} \
ret = -EOVERFLOW; \
if (langs >= MAX_USB_STRING_LANGS) \
goto err; \
\
list_add_tail(&new->list, &gi->string_list); \
return &new->group; \
err: \
kfree(new); \
return ERR_PTR(ret); \
} \
\
static void struct_in##_strings_drop( \
struct config_group *group, \
struct config_item *item) \
{ \
config_item_put(item); \
} \
\
static struct configfs_group_operations struct_in##_strings_ops = { \
.make_group = &struct_in##_strings_make, \
.drop_item = &struct_in##_strings_drop, \
}; \
\
static struct config_item_type struct_in##_strings_type = { \
.ct_group_ops = &struct_in##_strings_ops, \
.ct_owner = THIS_MODULE, \
}
#endif
这个要配合 configfs 的机制来看, 直接看它的使用实例.
// drivers/usb/gadget/configfs.c
GS_STRINGS_RW(gadget_strings, manufacturer); // 创建 gadget_strings 对应的 manufacturer 成员变量的属性接口以及对应的 configfs_attribute 结构
GS_STRINGS_RW(gadget_strings, product); // 创建 gadget_strings 对应的 product 成员变量的属性接口以及对应的 configfs_attribute 结构
GS_STRINGS_RW(gadget_strings, serialnumber); // 创建 gadget_strings 对应的 serialnumber 成员变量的属性接口以及对应的 configfs_attribute 结构
// 将前面通过 GS_STRINGS_RW 创建的 configfs_attribute 添加到 *_langid_attrs 数组,
// 这个数组是USB_CONFIG_STRING_RW_OPS 创建的 gadget_strings_langid_type 的成员变量.
static struct configfs_attribute *gadget_strings_langid_attrs[] = {
&gadget_strings_attr_manufacturer,
&gadget_strings_attr_product,
&gadget_strings_attr_serialnumber,
NULL,
};
// 创建 gadget_strings_attr_release 函数
// 也是 USB_CONFIG_STRING_RW_OPS 创建的 gadget_strings_langid_type 的成员变量.
static void gadget_strings_attr_release(struct config_item *item)
{
struct gadget_strings *gs = to_gadget_strings(item);
kfree(gs->manufacturer);
kfree(gs->product);
kfree(gs->serialnumber);
list_del(&gs->list);
kfree(gs);
}
// 创建 config_item_type 类型的 gadget_strings_langid_type 数据结构
// 并且自动使用前面的 gadget_strings_attr_release 和 gadget_strings_langid_attrs 进行填充
// 也就是为 strings 子目录创建属性文件
USB_CONFIG_STRING_RW_OPS(gadget_strings);
/******************** 这前面创建的数据结构都是子目录的 *******************/
// 使用前面的创建 gadget_strings_langid_type 这个 config_item_type 作为子目录的配置项操作函数,
// 创建 gadget_strings_strings_type 这个配置项回调, 并且为它创建 gadget_strings_strings_make 函数.
USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);
// 在 gadgets_make 中填充 gadget_strings_strings_type
static struct config_group *gadgets_make(struct config_group *group, const char *name)
{
......
config_group_init_type_name(&gi->strings_group, "strings",
&gadget_strings_strings_type);
......
}
这里的描述看不懂, 可以参考linux usb 驱动 - configfs 文件系统这篇文章, 配合源码一起看,因为这里涉及到 configfs 的机制可能有点难以理解, 没关系我们直接给出总结.
2. strings 总结
-
- configs 中的 strings 子目录下创建的子目录默认支持
manufacturer
,manufacturer
,manufacturer
这三个属性文件.
- configs 中的 strings 子目录下创建的子目录默认支持
-
- 这三个属性文件对应的属性在
gadget_strings
这个数据结构中, 它们链接到了gadget_info->string_list
链表.可以通过这个链表找到它.
- 这三个属性文件对应的属性在
struct gadget_strings {
struct usb_gadget_strings stringtab_dev; // 这个用数据结构来描述支持的语言格式, 从这里知道, 只支持一个国家的语言. 因为只有一个 stringtab_dev
struct usb_string strings[USB_GADGET_FIRST_AVAIL_IDX]; // 将manufacturer, product, serialnumber 三个属性转换为 usb_string 存到这里
char *manufacturer; // manufacturer 属性文件对应的属性
char *product; // product 属性文件对应的属性
char *serialnumber; // serialnumber 属性文件对应的属性
struct config_group group; // 当前目录下的子目录配置项
struct list_head list; // 链表连接到所属的 gadget_info
};
struct usb_gadget_strings {
u16 language; /* 0x0409 for en-us */
struct usb_string *strings;
};
-
- strings 目录的子目录的目录名用于表示支持的语言, 必须符合指定的格式, 他会通过
check_user_usb_string
函数进行检查. 表格给出了一些常见的国家.
- strings 目录的子目录的目录名用于表示支持的语言, 必须符合指定的格式, 他会通过
语言 | 语言标识符(Language ID) |
---|---|
英语(美国) | 0x0409 |
中文(简体) | 0x0804 |
法语 | 0x040c |
德语 | 0x0407 |
西班牙语 | 0x0c0a |
注: 这里说的 strings 目录是 /sys/kernel/config/usb_gadget/xxx/strings
目录, 不是 configs/c.1/strings/
目录.
strings 目录配置完之后的 composite 数据结构增加了 gadget_strings
.
4、配置配置描述符
# 配置配置描述符(表示 USB 设备的配置属性,如最大功率、设备的属性等)
echo "Creating Config..."
mkdir configs/c.1 # 创建配置文件夹,配置 1(c.1)\
echo "120" > configs/c.1/MaxPower # 设置最大功率为 120 毫安
echo "0x80" > configs/c.1/bmAttributes # 设置配置的属性,0x80 表示支持自供电且支持远程唤醒
这里分为两步首先是创建 configs/c.1 这个目录, 最终回调 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;
char buf[MAX_NAME_LEN];
char *num_str;
u8 num;
int ret;
// 获取到 gadget_info
gi = container_of(group, struct gadget_info, configs_group);
ret = snprintf(buf, MAX_NAME_LEN, "%s", name); // 这里 name 是 c.1
if (ret >= MAX_NAME_LEN)
return ERR_PTR(-ENAMETOOLONG);
num_str = strchr(buf, '.'); // 指针指向 .
if (!num_str) {
pr_err("Unable to locate . in name.bConfigurationValue\n");
return ERR_PTR(-EINVAL);
}
*num_str = '\0'; // 把 . 换成'\0'
num_str++; // 指向 '1'
if (!strlen(buf))
return ERR_PTR(-EINVAL);
// 把 1 换成 0x1
ret = kstrtou8(num_str, 0, &num);
if (ret)
return ERR_PTR(ret);
// 分配 config_usb_cfg 数据结构
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
if (!cfg)
return ERR_PTR(-ENOMEM);
// 设置 label 为 name
cfg->c.label = kstrdup(buf, GFP_KERNEL);
if (!cfg->c.label) {
ret = -ENOMEM;
goto err;
}
cfg->c.bConfigurationValue = num; // 设置配置编号, c.1 对应的是 1
cfg->c.MaxPower = CONFIG_USB_GADGET_VBUS_DRAW; // 设备从总线提取的最大电流
cfg->c.bmAttributes = USB_CONFIG_ATT_ONE; // 设备配置的电源特性和功能
INIT_LIST_HEAD(&cfg->string_list);
INIT_LIST_HEAD(&cfg->func_list);
// 设置默认的子目录 srting 目录
cfg->group.default_groups = cfg->default_groups;
cfg->default_groups[0] = &cfg->strings_group;
// 设置目录 c.1 的配置项, 同时设置属性接口.
config_group_init_type_name(&cfg->group, name,
&gadget_config_type);
// 设置子目录 string 的配置项.
config_group_init_type_name(&cfg->strings_group, "strings",
&gadget_config_name_strings_type);
// 添加配置项
ret = usb_add_config_only(&gi->cdev, &cfg->c);
if (ret)
goto err;
return &cfg->group;
err:
kfree(cfg->c.label);
kfree(cfg);
return ERR_PTR(ret);
}
// c.1 支持的两个默认的属性文件.
static struct configfs_attribute *gadget_config_attrs[] = {
&gadget_config_desc_attr_MaxPower,
&gadget_config_desc_attr_bmAttributes,
NULL,
};
static struct config_item_type gadget_config_type = {
.ct_item_ops = &gadget_config_item_ops,
.ct_attrs = gadget_config_attrs,
.ct_owner = THIS_MODULE,
};
- 在 confgs 目录下创建的子目录的名称
c.1
中的数字(如 .1)表示配置编号,而前缀(如 c、a、xxx)可以随意更改, 只要确保每个配置编号唯一即可. - 创建默认的属性文件
MaxPower
和bmAttributes
- 创建子目录 strings, 以及其属性文件
ls 查看生成的目录
# pwd
/sys/kernel/config/usb_gadget/serial_demo/configs/c.1
# ls
MaxPower acm.0 bmAttributes strings
然后在 strings 目录下创建 0x409 目录表示配置支持英文.
mkdir configs/c.1/strings/0x409 # 为配置创建语言描述符
echo "INGENIC" > configs/c.1/strings/0x409/configuration # 设置配置的描述符
它对应的内核代码如下.
struct gadget_config_name {
struct usb_gadget_strings stringtab_dev; // 目录名称保存在这里, 表示支持的语言格式
struct usb_string strings; // 只有一个属性 configuration
char *configuration; // 属性文件对应的属性
struct config_group group; // 子目录的配置项
struct list_head list; // 用来挂接到 config_usb_cfg
};
struct config_usb_cfg {
struct config_group group; // 子目录的配置项
struct config_group strings_group; // strings 子目录的配置项
struct config_group *default_groups[2];
struct list_head string_list; // gadget_config_name 被链接到这里
struct usb_configuration c; // 配置相关结构
struct list_head func_list; // 支持的 function
struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1]; // 可以支持多语言, 每个语言就是一个目录.
};
static struct configfs_attribute *gadget_config_name_langid_attrs[] = {
&gadget_config_name_attr_configuration,
NULL,
};
static void gadget_config_name_attr_release(struct config_item *item)
{
struct gadget_config_name *cn = to_gadget_config_name(item);
kfree(cn->configuration);
list_del(&cn->list);
kfree(cn);
}
USB_CONFIG_STRING_RW_OPS(gadget_config_name);
USB_CONFIG_STRINGS_LANG(gadget_config_name, config_usb_cfg);
static struct config_group *config_desc_make(struct config_group *group, const char *name)
{
......
config_group_init_type_name(&cfg->strings_group, "strings",
&gadget_config_name_strings_type);
......
}
这个和前面是一样的, 也就是 configs
下的子目录 c.1
的语言描述目录, 它只支持 configuration
这个属性.配置完成之后数据结构关系如下所示.
这个涉及到具体的 function 配置, 后文会详述这里为了完整性先给出.
五、function
usb 做 device 的时候具体的功能描述, 相当于 host 端的接口设备.通过 configs 配置功能描述符.
echo "Creating functions..."
# 创建一个名为 "acm.0" 的功能目录,"acm" 表示串口设备类,"0" 是实例编号
mkdir functions/acm.0
# 将刚创建的功能(acm.0)链接到配置 "c.1" 中,表示将该功能添加到配置中
ln -s functions/acm.0 configs/c.1
首先是通过 make 创建目录配置项, 最终调用到 function_make 函数.
1、 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.0
ret = snprintf(buf, MAX_NAME_LEN, "%s", name);
if (ret >= MAX_NAME_LEN)
return ERR_PTR(-ENAMETOOLONG);
func_name = buf; // 设置为 acm.0
// 找到 . 的位置
instance_name = strchr(func_name, '.');
if (!instance_name) {
pr_err("Unable to locate . in FUNC.INSTANCE\n");
return ERR_PTR(-EINVAL);
}
// 把 '.' 改成 '\0',
// 注意由于 instance_name 和 func_name 都是指向 buf,
// 所以 buf 的值一改, func_name 也被改了, 就变成了 "acm"
*instance_name = '\0';
instance_name++; // 指向下一位
// 这里传输的 name 是 acm
fi = usb_get_function_instance(func_name);
if (IS_ERR(fi))
return ERR_CAST(fi);
// 设置 cg_item->ci_name
ret = config_item_set_name(&fi->group.cg_item, "%s", name);
if (ret) {
usb_put_function_instance(fi);
return ERR_PTR(ret);
}
if (fi->set_inst_name) { // 设置 instance_name
ret = fi->set_inst_name(fi, instance_name);
if (ret) {
usb_put_function_instance(fi);
return ERR_PTR(ret);
}
}
// 获取 gadget_info
gi = container_of(group, struct gadget_info, functions_group);
// 将 fi->cfs_list 链接到 gi->available_func
mutex_lock(&gi->lock);
list_add_tail(&fi->cfs_list, &gi->available_func);
mutex_unlock(&gi->lock);
return &fi->group;
}
这个函数解析传入的 “acm.0” , 然后以 “acm” 作为参数调用 usb_get_function_instance
, 这个函数用于返回 usb_function_instance
结构, 然后将这个数据结构链接到 gadget_info
.
// 先尝试使用已有的驱动, 在 func_list 中查找 name 对应的 usb_function_driver, 调用 name 对应的 usb_function_driver->fd->alloc_inst() 创建一个 fi
// 如果驱动不存在,就尝试调用 request_module 动态加载
// 加载后再次尝试使用
struct usb_function_instance *usb_get_function_instance(const char *name)
{
struct usb_function_instance *fi;
int ret;
// 在 func_list 中查找 name 对应的 usb_function_driver
// 调用 name 对应的 usb_function_driver->fd->alloc_inst() 创建一个 fi
// 如果没有找到对应的 usb_function_driver 返回 ENOENT
fi = try_get_usb_function_instance(name);
if (!IS_ERR(fi))
return fi;
ret = PTR_ERR(fi);
if (ret != -ENOENT) // 如果是不是 ENOENT 直接返回
return fi;
// 尝试注册使用 MODULE_ALIAS 声明的同名 module.ko
ret = request_module("usbfunc:%s", name);
if (ret < 0)
return ERR_PTR(ret);
// 再次调用
return try_get_usb_function_instance(name);
}
EXPORT_SYMBOL_GPL(usb_get_function_instance);
usb_get_function_instance
先尝试使用已有的驱动, 在 func_list
中查找 name
为 acm
对应的 usb_function_driver
, 调用 name
为 acm
对应的 usb_function_driver->fd->alloc_inst()
创建一个 fi
.
从这里我们就可以知道, 我们需要提前往 func_list
中注册一个 usb_function_driver
用于分配我们需要的 usb_function_instance
.
// 创建一个 usb_function_driver 结构体 , 并且使用 _inst_alloc 和 _func_alloc 设置回调函数
#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));
// 首先调用 DECLARE_USB_FUNCTION 创建 usb_function_driver 结构体
// 然后在 module_init 中调用 usb_function_register 注册
#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)
// 往 fun_list 里面注册一个 usb_function_driver
int usb_function_register(struct usb_function_driver *newf)
{
struct usb_function_driver *fd;
int ret;
ret = -EEXIST;
mutex_lock(&func_lock);
// 遍历 func_list , 如果已经注册直接返回
list_for_each_entry(fd, &func_list, list) {
if (!strcmp(fd->name, newf->name))
goto out;
}
ret = 0;
// 添加到 func_list 链表
list_add_tail(&newf->list, &func_list);
out:
mutex_unlock(&func_lock);
return ret;
}
EXPORT_SYMBOL_GPL(usb_function_register);
展开之后可以知道 DECLARE_USB_FUNCTION
宏创建 usb_function_driver
结构体并设置 alloc_inst
和 alloc_func
回调函数, 然后将它注册到 func_list
链表中. 在 usb
串口驱动 f_acm.c
中注册.
// drivers/usb/gadget/function/f_acm.c
static struct usb_function_instance *acm_alloc_instance(void)
{
struct f_serial_opts *opts;
int ret;
// 分配 f_serial_opts 数据结构, usb_function_instance 作为其成员变量同时被分配
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
if (!opts)
return ERR_PTR(-ENOMEM);
opts->func_inst.free_func_inst = acm_free_instance; // 设置释放接口
// 1. 设置 usb 串口通讯模式为 8n1
// 2. 分配 1 个 gs_port , 并存放于 ports 数组中. tty_port 作为其成员变量同时分配.
// 3. 初始化 `gs_port->read_pool`, `gs_port->read_queue`, `gs_port->write_pool`, 链表分别用于读写.
// 4. 始化 tasklet 软中断 gs_port->push, 用于接受数据, 回调函数为 gs_rx_push
// 5. 使用 tty_port 分配的和 gs_tty_driver 注册进 tty core 生成 /dev/ttyGS* 节点, 最后返回分配的端口号.
ret = gserial_alloc_line(&opts->port_num);
if (ret) {
kfree(opts);
return ERR_PTR(ret);
}
// 设置 configs 目录配置项
config_group_init_type_name(&opts->func_inst.group, "",
&acm_func_type);
return &opts->func_inst;
}
2、function_make 总结
- 这个函数解析传入的 “acm.0” , 然后以 “acm” 作为参数调用
usb_get_function_instance
, 这个函数在 func_list 中查找 “acm” 对应的usb_function_driver
, 调用usb_function_driver->fd->alloc_inst()
创建一个 fi 并返回, 然后将这个数据结构链接到gadget_info
. alloc_inst
分配f_serial_opts
数据结构,usb_function_instance
作为其成员变量同时被分配.- 设置 usb 串口通讯模式为 8n1, usb 使用
usb_cdc_line_coding
数据结构描述串口模式. - 分配 1 个
gs_port
, 并存放于 ports 数组中.f_serial_opts->num
作为数组索引,tty_port
作为其成员变量同时分配. - 初始化
gs_port->read_pool
,gs_port->read_queue
,gs_port->write_pool
, 链表分别用于读写. - 始化
tasklet
软中断gs_port->push
, 用于接受数据, 回调函数为gs_rx_push
- 使用
tty_port
分配的和gs_tty_driver
注册进tty core
生成/dev/ttyGS*
节点, 最后返回分配的端口号port_num
. 这个port_num
很重要作为tty_port
的索引.acm
驱动可以通过端口号找到对应的数据结构. - 配置功能描述符之后的数据结构关系图如下所示
3. 关联 function 和 configs
ln -s functions/acm.0 configs/c.1
这个配置将 function 绑定到对应的配置描述符, 调用流程如下
// ln -s functions/acm.0 configs/c.1
configfs_symlink() -->
type->ct_item_ops->allow_link() -->
config_usb_cfg_link() -->
最终调用到 config_usb_cfg_link
接口.
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); // 找到 config_usb_cfg
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);
struct usb_function_instance *fi = container_of(group, struct usb_function_instance, group);
struct usb_function_instance *a_fi;
struct usb_function *f;
int ret;
mutex_lock(&gi->lock);
// 遍历 available_func 找到对应的 usb_function_instance
list_for_each_entry(a_fi, &gi->available_func, cfs_list) {
if (a_fi == fi)
break;
}
if (a_fi != fi) {
ret = -EINVAL;
goto out;
}
// 遍历 func_list 判断是否已经注册
list_for_each_entry(f, &cfg->func_list, list) {
if (f->fi == fi) {
ret = -EEXIST;
goto out;
}
}
// 回调 fi->fd->alloc_func
f = usb_get_function(fi);
if (IS_ERR(f)) {
ret = PTR_ERR(f);
goto out;
}
// 链接到 cfg->func_list
list_add_tail(&f->list, &cfg->func_list);
ret = 0;
out:
mutex_unlock(&gi->lock);
return ret;
}
这个函数最终调用到 acm_alloc_func
函数
static struct usb_function *acm_alloc_func(struct usb_function_instance *fi)
{
struct f_serial_opts *opts;
struct f_acm *acm;
// 分配 f_acm, 同时 gserial 作为其成员变量被分配, usb_function 则作为 gserial 的成员变量同时被分配.
acm = kzalloc(sizeof(*acm), GFP_KERNEL);
if (!acm)
return ERR_PTR(-ENOMEM);
spin_lock_init(&acm->lock);
// 初始化 gserial 数据结构的 acm_connect 、 acm_disconnect、`acm_send_break` 回调函数
acm->port.connect = acm_connect;
acm->port.disconnect = acm_disconnect;
acm->port.send_break = acm_send_break;
// 初始化 usb_function 成员变量的各个回调函数等
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;
acm->port.func.disable = acm_disable;
opts = container_of(fi, struct f_serial_opts, func_inst);
acm->port_num = opts->port_num; // 设置 acm->port_num 这个是 ports[] 数组的索引
acm->port.func.unbind = acm_unbind;
acm->port.func.free_func = acm_free_func;
return &acm->port.func; // 返回 usb_function
}
关联的总结如下:
- 分配
f_acm
, 同时gserial
作为其成员变量被分配,usb_function
则作为gserial
的成员变量同时被分配. - 初始化
gserial
数据结构的acm_connect
、acm_disconnect
、acm_send_break
回调函数 - 初始化
usb_function
成员变量的各个回调函数等 - 设置
acm->port_num
这个是ports[]
数组的索引 - 将
usb_function
链接到config_usb_cfg
- 关联 function 和 configs 之后数据结构如下.
六、关联 compiste 和 gadget
echo `ls /sys/class/udc/` > UDC # 这个步骤将设备连接到 USB 设备控制器(UDC)并启动 USB 外设
这个操作会调用到 gadget_dev_desc_UDC_store
这个属性文件操作接口, 调用流程如下.
这个过程主要作了这些事情.
- 绑定
udc->driver
和usb_gadget_driver
- 调用
gadget->ops
的dc_set_speed
接口设置 usb 设备支持的速度, 这个不是必须的 - 调用
gadget->ops
的bind
接口, 这个接口必须实现, 这个接口会最终会调用到acm_bind
acm_bind
设置输入端点、输出端点、notify 端点, 设置接口描述符.- 调用
gadget->ops
的udc_start
接口, 用于初始化 udc 的硬件, 这个接口必须实现 - 调用
gadget->ops
的pullup
接口, 启用 D+(或D-) 上的上拉电阻,通知主机它已准备好进行连接。当主机检测到这个信号后,它会启动枚举过程, 这个接口不是必须的. - 绑定之后数据结构关系如下所示.
绑定 compiste
和 gadget
的过程就是 usb 设备初始化的过程, 当完成关联结束, 我们的设备就准备好了, 接下来就会触发 set_up
枚举流程. 调用流程如下所示.
usb 串口枚举流程大概如下所示.
USB_DT_DEVICE
USB_DT_DEVICE
USB_DT_CONFIG
USB_DT_STRING
USB_DT_STRING
USB_DT_STRING
USB_DT_DEVICE
USB_DT_CONFIG
USB_DT_CONFIG
USB_DT_CONFIG
USB_REQ_SET_CONFIGURATION
USB_RECIP_INTERFACE
USB_RECIP_INTERFACE
USB_RECIP_INTERFACE
USB_RECIP_INTERFACE
USB_DT_STRING
USB_DT_STRING
USB_DT_STRING
USB_RECIP_INTERFACE
USB_DT_STRING
USB_DT_STRING
USB_DT_STRING
其中 USB_REQ_SET_CONFIGURATION
比较特殊, 用于设置 config
, 在 usb 串口中设置对应的配置.
composite_setup() -->
set_config() -->
f->set_alt(f, tmp, 0) -->
acm_set_alt() -->
gserial_connect() -->
usb_ep_enable() --> // 用于使能 ep
除此之外 USB_RECIP_INTERFACE
这用于枚举设备的接口信息, 会回调 acm_setup
这是对应的 function
的 setup
接口, 用于提供设备的接口类信息. usb 串口中用于获取以及设置串口的属性 8n1
等.
composite_setup() -->
f->setup() -->
acm_setup() -->
七、数据通讯
usb 串口的数据通信比较简单, usb 做设备的通讯是通过 usb_request
来进行, 简称 req. usb 通讯是通过端点进行通信, 为了隔离 function
和 udc 控制器直接的实现细节, 内核封装了 usb_ep_queue
用于 function
进行数据通信. 大致的发送流程.
- 分配 req
- 将要通讯的数据填入到对应的 req
- 调用
usb_ep_queue
填入要通讯的 ep 和 包含数据的 req - 调用 gadget 提供的通用接口
dwc2_hsotg_ep_queue_lock
将数据填充到fifo
或者dma buffer
- 通信的流程都是主机发起的, 前面先把数据填充到
buffer
, 接下来等待主机发起通信请求.
usb 串口中首先通过 gs_open
分配通讯的 req 和对应的 buffer.
发送和接受数据都比较简单, 发送流程如下.
接收的流程如下所示.