linux usb 驱动 - hdc 框架

    usb 是什么, usb 只是一种数据传输的方式, usb 的作用就是数据传输, 我们接受到了数据时候做什么, 是我们自己决定, 在usb 中则通过描述符来确定这些数据要来干什么, 设备描述符用于描述当前是什么 usb 设备, 以及这个设备支持什么样的功能, 这个支持的功能(串口, uvc等)则由 function 来描述. 设备的描述符以及功能等由 usb_composite_dev 进行统一的管理维护.

    configfs 则将这种管理进行动态化, 使得我们能够动态的去配置我们的 usb 为特定的 usb 设备. 当然我们也可以不用 configfs 自己去写死 usb 支持的功能, 甚至驱动不做处理仅仅导出 libusb 库, 应用程序自己实现 usb 设备支持的功能的各种描述符. 因为 usb 只是数据传输, 具体的功能实现可以是驱动也可以应用, 取决与开发的需求.本文将以 configfs以及串口设备为例分析 usbdevice 的驱动框架.

阅读这篇文章之前请先阅读我博客里面的 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 的分配和初始化流程如下图所示.

    注册流程就是对核心的数据结构进行初始化, 核心调用接口如下.

1
2
3
4
5
6
7
8
9
10
11
12
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_buffep0_buff 用来做端点分配控制传输的 buffer
  • 注册 gadget 中断, 为控制传输分配 ctrl_req
  • dwc2_hsotg_initep 为每一个端点分配内存, 即分配 eps_in[epnum] 数组. 有个小细节, ep0 并没有链接到 gadget->ep_list.
  • 分配 usb_udc 将其链接到 udc_list 链表, 注册对应设备到设备模型
  • 更新 udc 电源状态, 设置 udc->gadget = gadgetadget->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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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、创建配置根目录

1
mkdir /sys/kernel/config/usb_gadget/serial_demo

    这个操作是创建一个 configfs 文件系根目录, 由 configfs 文件系统的特性可以知道会回调到 gadgets_make 接口. 调用流程如下.

1
2
3
4
5
6
7
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 是一个关键接口, 我们看看他的实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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_driverusb_composite_dev 作为其成员变量同时被分配
  • 设置 functionsconfigsstringsos_desc 等默认的子目录配置项.即创建子目录与子目录的属性文件.
  • 简单初始化设备描述符 cdev.desc 的基本信息
  • 初始化 composite 并且填充 gadget_driver

2、配置设备描述符

1
2
3
4
5
6
7
8
9
# 配置设备描述符
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 是变量,通常由主机配置

我们在内核中找到对应的属性接口的定义.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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_devusb_device_descriptor 设备描述符结构体的成员变量.

3、配置字符串描述符

1
2
3
4
5
6
# 配置字符串描述符(用于标识设备的字符串,如制造商名、产品名、序列号等)
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 这头文件里面.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// 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 的机制来看, 直接看它的使用实例.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 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 总结

    1. configs 中的 strings 子目录下创建的子目录默认支持 manufacturer, manufacturer, manufacturer这三个属性文件.
    1. 这三个属性文件对应的属性gadget_strings 这个数据结构中, 它们链接到了gadget_info->string_list 链表.可以通过这个链表找到它.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
};
    1. strings 目录的子目录的目录名用于表示支持的语言, 必须符合指定的格式, 他会通过 check_user_usb_string 函数进行检查. 表格给出了一些常见的国家.
语言 语言标识符(Language ID)
英语(美国) 0x0409
中文(简体) 0x0804
法语 0x040c
德语 0x0407
西班牙语 0x0c0a

注: 这里说的 strings 目录是 /sys/kernel/config/usb_gadget/xxx/strings 目录, 不是 configs/c.1/strings/ 目录.

strings 目录配置完之后的 composite 数据结构增加了 gadget_strings.

4、配置配置描述符

1
2
3
4
5
6
7
# 配置配置描述符(表示 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 这个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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)可以随意更改, 只要确保每个配置编号唯一即可.
  • 创建默认的属性文件 MaxPowerbmAttributes
  • 创建子目录 strings, 以及其属性文件

ls 查看生成的目录

1
2
3
4
# pwd
/sys/kernel/config/usb_gadget/serial_demo/configs/c.1
# ls
MaxPower acm.0 bmAttributes strings

然后在 strings 目录下创建 0x409 目录表示配置支持英文.

1
2
mkdir configs/c.1/strings/0x409  # 为配置创建语言描述符
echo "INGENIC" > configs/c.1/strings/0x409/configuration # 设置配置的描述符

它对应的内核代码如下.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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 配置功能描述符.

1
2
3
4
5
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 先尝试使用已有的驱动, 在 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 中查找 nameacm 对应的 usb_function_driver, 调用 nameacm 对应的 usb_function_driver->fd->alloc_inst() 创建一个 fi.

    从这里我们就可以知道, 我们需要提前往 func_list 中注册一个 usb_function_driver 用于分配我们需要的 usb_function_instance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 创建一个 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_installoc_func 回调函数, 然后将它注册到 func_list 链表中. 在 usb 串口驱动 f_acm.c 中注册.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 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

1
ln -s functions/acm.0 configs/c.1

这个配置将 function 绑定到对应的配置描述符, 调用流程如下

1
2
3
4
// ln -s functions/acm.0 configs/c.1
configfs_symlink() -->
type->ct_item_ops->allow_link() -->
config_usb_cfg_link() -->

最终调用到 config_usb_cfg_link 接口.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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_connectacm_disconnectacm_send_break 回调函数
  • 初始化 usb_function 成员变量的各个回调函数等
  • 设置 acm->port_num 这个是 ports[] 数组的索引
  • usb_function 链接到 config_usb_cfg
  • 关联 function 和 configs 之后数据结构如下.

六、关联 compiste 和 gadget

1
echo `ls /sys/class/udc/` > UDC  # 这个步骤将设备连接到 USB 设备控制器(UDC)并启动 USB 外设

这个操作会调用到 gadget_dev_desc_UDC_store 这个属性文件操作接口, 调用流程如下.

这个过程主要作了这些事情.

  • 绑定 udc->driverusb_gadget_driver
  • 调用 gadget->opsdc_set_speed 接口设置 usb 设备支持的速度, 这个不是必须的
  • 调用 gadget->opsbind 接口, 这个接口必须实现, 这个接口会最终会调用到 acm_bind
  • acm_bind 设置输入端点输出端点notify 端点, 设置接口描述符.
  • 调用 gadget->opsudc_start 接口, 用于初始化 udc 的硬件, 这个接口必须实现
  • 调用 gadget->opspullup 接口, 启用 D+(或D-) 上的上拉电阻,通知主机它已准备好进行连接。当主机检测到这个信号后,它会启动枚举过程, 这个接口不是必须的.
  • 绑定之后数据结构关系如下所示.

    绑定 compistegadget 的过程就是 usb 设备初始化的过程, 当完成关联结束, 我们的设备就准备好了, 接下来就会触发 set_up 枚举流程. 调用流程如下所示.

usb 串口枚举流程大概如下所示.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 串口中设置对应的配置.

1
2
3
4
5
6
composite_setup() -->
set_config() -->
f->set_alt(f, tmp, 0) -->
acm_set_alt() -->
gserial_connect() -->
usb_ep_enable() --> // 用于使能 ep

    除此之外 USB_RECIP_INTERFACE 这用于枚举设备的接口信息, 会回调 acm_setup 这是对应的 functionsetup 接口, 用于提供设备的接口类信息. usb 串口中用于获取以及设置串口的属性 8n1 等.

1
2
3
composite_setup() -->
f->setup() -->
acm_setup() -->

七、数据通讯

usb 串口的数据通信比较简单, usb 做设备的通讯是通过 usb_request 来进行, 简称 req. usb 通讯是通过端点进行通信, 为了隔离 function 和 udc 控制器之间的实现细节, 内核封装了 usb_ep_queue 用于 function 进行数据通信. 大致的发送流程.

  1. 分配 req
  2. 将要通讯的数据填入到对应的 req
  3. 调用 usb_ep_queue 填入要通讯的 ep 和 包含数据的 req
  4. 调用 gadget 提供的通用接口 dwc2_hsotg_ep_queue_lock 将数据填充到 fifo 或者 dma buffer
  5. 通信的流程都是主机发起的, 前面先把数据填充到 buffer, 接下来等待主机发起通信请求.

usb 串口中首先通过 gs_open 分配通讯的 req 和对应的 buffer.

发送和接受数据都比较简单, 发送流程如下.

接收的流程如下所示.