Linux USB设备结构中的配置、接口、设置、端点
内核版本:linux-3.4.2
## 1.描述符数据结构 ##
描述符存储在USB设备结构体中,USB设备结构中与描述符相关的成员如下:
struct usb_device {
struct usb_host_endpoint ep0;
struct usb_device_descriptor descriptor; /* SPEC中定义的设备描述符 */
struct usb_host_bos *bos;
struct usb_host_config *config; /* 配置描述符 */
struct usb_host_config *actconfig;
struct usb_host_endpoint *ep_in[16];
struct usb_host_endpoint *ep_out[16];
char **rawdescriptors;
};
从上面的成员的类型可以看出,struct usb_device_descriptor
对应的是USB规范中的设备描述符,而struct usb_host_config
并没有对应USB规范中的配置描述符,一般名字为usb_xxx_descriptor
的结构体对应的是USB规范中的描述符,而名字为usb_host_xxx
的结构体则代表主机侧的一个描述符,而具体的USB规范中的描述符则是一个usb_host_xxx
结构体中的成员。
可以预见的是:
struct usb_config_descriptor
对应SPEC中的配置描述符
struct usb_interface_descriptor
对应SPEC中的接口描述符
struct usb_endpoint_descriptor
对应SPEC中的端点描述符
struct usb_host_config
对应主机侧的一个配置
struct usb_host_interface
对应主机侧的一个接口
struct usb_host_endpoint
对应主机侧的一个端点
那么这些描述符是怎么存储的,config和actconfig有什么区别,这得看从设备枚举的时候读取设备描述符开始到USB接口设备匹配到接口驱动为止的代码来看struct usb_device
结构体中成员的变化。
## 2. 读取设备描述符,设置设备 ##
读取设备描述符是在hub_port_connect_change
函数中,大概过程如下:
static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange)
{
for (i = 0; i < SET_CONFIG_TRIES; i++) {
udev = usb_alloc_dev(hdev, hdev->bus, port1); /* 分配一个usb_device,此时设备的状态是 USB_STATE_ATTACHED */
...
choose_devnum(udev); /* 选择设备编号 */
hub_port_init(hub, udev, port1, i); /* hub端口的初始化 */
...
usb_new_device(udev); /* 向系统添加新的USB设备 */
}
}
### 2.1 分配初始化USB设备对象 ###
首先看设备是怎么分配的:
parent:这个参数指向了设备插入的hub的设备结构体
bus:这个参数指向了物理上的一个usb总线
port1:这个参数指设备插入的hub的端口号
struct usb_device *usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
{
dev = kzalloc(sizeof(*dev), GFP_KERNEL); /* 这里分配了一个usb_device结构体,代表了一个USB设备 */
...
dev->state = USB_STATE_ATTACHED; /* 设备的状态为ATTACHED */
...
dev->ep0.desc.bLength = USB_DT_ENDPOINT_SIZE; /* 设置端点0的描述符的长度 */
dev->ep0.desc.bDescriptorType = USB_DT_ENDPOINT; /* 设置端点0的描述符的类型 */
}
此时的usb_device
里的成员是这样的情况:
图1
### 2.2 读取设备描述符 ###
分配了usb_device
之后,就会进入hub_port_init
函数读取设备描述符:
static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, int retry_counter)
{
int devnum = udev->devnum;
for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100))) { //如果第一次就成功了,那就退出循环了
if (USE_NEW_SCHEME(retry_counter) && !(hcd->driver->flags & HCD_USB3)) {
#define GET_DESCRIPTOR_BUFSIZE 64
buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO); /* 首先分配64字节的空间 */
buf->bMaxPacketSize0 = 0;
usb_control_msg(udev, usb_rcvaddr0pipe(),
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
USB_DT_DEVICE << 8, 0,
buf, GET_DESCRIPTOR_BUFSIZE,
initial_descriptor_timeout); /* 发送一个控制传输请求,请求64字节的数据 */
udev->descriptor.bMaxPacketSize0 = buf->bMaxPacketSize0; /* 记录端点0最大包的值 */
kfree(buf);
hub_port_reset(hub, port1, udev, delay, false); /* reset端口和设备 */
}
if (udev->wusb == 0) {
hub_set_address(udev, devnum); /* 设置设备地址 */
}
retval = usb_get_device_descriptor(udev, 8); /* 如果执行到这里说明使用的是旧策略,读取8字节数据到udev.descriptor里,就包含了端点0最大包大小 */
}
...
i = udev->descriptor.bMaxPacketSize0;
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i); /* 设置端点0最大包的值 */
usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); /* 得到了端点0最大包的大小后,使用端点0读取设备描述符 */
usb_ep0_reinit(udev);
usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); /* 得到了端点0最大包的大小后,使用端点0读取设备描述符 */
}
此时设置了设备结构中的ep_in[0]
和ep_out[0]
指针,指向了设备结构中的ep0,并且设备描述符读取完成,此时设备结构体里各成员状态如图:
图2
### 2.3 初始化设备里的配置 ###
当读取了设备描述符后,就可以向总线添加设备了,但在之前需要将设备中的基本的描述符都读出来并保存在设备的结构体里,包括配置描述符、接口描述符、端点描述符等,也可能会有一些额外的描述符。
向总线添加USB设备的接口如下:
int usb_new_device(struct usb_device *udev)
{
usb_enumerate_device(udev); /* 读取所有的描述符 */
...
device_add(&udev->dev); /* 想总线添加设备 */
}
首先看怎么读取描述符:
static int usb_enumerate_device(struct usb_device *udev)
{
if (udev->config == NULL) { /* 还没有读过配置 */
usb_get_configuration(udev); /* 读取配置 */
}
}
实际的读取是usb_get_configuration()
函数:
int usb_get_configuration(struct usb_device *dev)
{
int ncfg = dev->descriptor.bNumConfigurations; /* 从设备描述符中得到配置的个数 */
length = ncfg * sizeof(struct usb_host_config); /* 计算所有配置描述符需要的空间大小 */
dev->config = kzalloc(length, GFP_KERNEL); /* 为保存配置描述符分配空间 */
...
length = ncfg * sizeof(char *);
dev->rawdescriptors = kzalloc(length, GFP_KERNEL); /* 这个空间用来保存读描述符时读到的所有的原始数据 */
desc = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL); /* 这块空间用来临时保存配置描述符 */
for (; cfgno < ncfg; cfgno++) { /* 循环处理所有的配置下的所有描述符 */
usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, desc, USB_DT_CONFIG_SIZE); /* 读取配置描述符保存在临时空间 */
length = max((int) le16_to_cpu(desc->wTotalLength), USB_DT_CONFIG_SIZE); /* 得到这配置下所有描述符的大小,包括配置描述符 */
bigbuffer = kmalloc(length, GFP_KERNEL); /* 分配空间保存配置下的所有描述符 */
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, bigbuffer, length); /* 读取配置下的所有描述符 */
dev->rawdescriptors[cfgno] = bigbuffer; /* 这里设置设备结构体里的指针 */
usb_parse_configuration(dev, cfgno, &dev->config[cfgno], bigbuffer, length); /* 这个函数中会解析配置下的描述符 */
}
kfree(desc); /* 释放临时空间 */
dev->descriptor.bNumConfigurations = cfgno; /* 修正接口数 */
}
大概过程如图所示:
图3
假设这个设备下有三个配置,当这个函数执行完了之后,设备结构体中和描述符相关的内容大概是这样:
图4
### 2.4 解析配置 ###
解析配置下的描述符使用usb_parse_configuration
函数,先看每个参数的意思
int usb_parse_configuration(struct usb_device *dev, int cfgidx, struct usb_host_config *config, unsigned char *buffer, int size)
dev:USB设备结构体
cfgidx:配置的索引,从0开始
config:指向设备结构体中config[cfgidx]的指针
buffer:指向了该配置的原始数据,也就是设备结构体中的rawdescriptors[cfgno]指针指向的地方
size:这个配置下原始数据的长度,也就是buffer指针指向的数据长度
int usb_parse_configuration(struct usb_device *dev, int cfgidx, struct usb_host_config *config, unsigned char *buffer, int size)
{
memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE); /* 将配置描述符复制到设备结构体的config指向的结构体的成员中 */
buffer += config->desc.bLength; /* 将指针移动到配置描述符之后的位置 */
size -= config->desc.bLength;
nintf = nintf_orig = config->desc.bNumInterfaces; /* 得到这个配置下的接口的个数 */
for ((buffer2 = buffer, size2 = size);
size2 > 0;
(buffer2 += header->bLength, size2 -= header->bLength)) {
header = (struct usb_descriptor_header *) buffer2; /* 指向了一个描述符的头 */
if (header->bDescriptorType == USB_DT_INTERFACE) {
d = (struct usb_interface_descriptor *) header; /* 指向这个接口描述符首地址 */
inum = d->bInterfaceNumber; /* 得到这个接口的编号 */
...
for (i = 0; i < n; ++i) { /* n表示统计过的接口的 */
if (inums[i] == inum) /* 如果相等,那么就要去设置对应数组里的设置的数量 */
break;
}
if (i < n) {
if (nalts[i] < 255)
++nalts[i]; /* 对应的设置数加一 */
} else if (n < USB_MAXINTERFACES) {
inums[n] = inum; /* 新统计的接口,记录接口编号 */
nalts[n] = 1; /* 接口的设置数量是1 */
++n; /* 增加已经统计过的接口索引值 */
}
}else if (header->bDescriptorType == USB_DT_INTERFACE_ASSOCIATION) {
config->intf_assoc[iad_num] = (struct usb_interface_assoc_descriptor *)header;
iad_num++;
}else if (header->bDescriptorType == USB_DT_DEVICE || header->bDescriptorType == USB_DT_CONFIG)
dev_warn(ddev, "config %d contains an unexpected "
"descriptor of type 0x%X, skipping\n",
cfgno, header->bDescriptorType); /* 打印警告 */
}
size = buffer2 - buffer; /* 接口描述符处理完成后调整剩余数据的大小 */
config->desc.wTotalLength = cpu_to_le16(buffer2 - buffer0); /* 调整一下配置下数据的大小 */
config->desc.bNumInterfaces = nintf = n; /* n是上面循环解析的接口数量,调整一下接口数量 */
for (i = 0; i < nintf; ++i) {
j = nalts[i]; /* j是一个设置的数量,对应的设置号是inums[i] */
len = sizeof(*intfc) + sizeof(struct usb_host_interface) * j; /* 计算分配一个struct usb_interface_cache结构体的大小,因为该结构体里有可变数组 */
config->intf_cache[i] = intfc = kzalloc(len, GFP_KERNEL); /* 分配一个struct usb_interface_cache结构体 */
}
config->extra = buffer; /* 额外描述符的指针先指向buffer,也就是跳过最前面配置描述符的地址 */
i = find_next_descriptor(buffer, size, USB_DT_INTERFACE, USB_DT_INTERFACE, &n); /* 从buffer开始,到buffer2,也就是从配置描述符之后开始找接口描述符,返回值i返回从buffer开始到下个接口描述符的偏移量,传出参数n表示跳过了几个不是接口描述符的描述符 */
config->extralen = i; /* 如果i不等于0表示有额外描述符,如果等于0表示没有额外描述符 */
buffer += i; /* 将buffer指针跳过额外描述符,指向了第一个接口描述符 */
size -= i; /* 调整剩余数据的大小 */
while (size > 0) { /* 这个循环用来解析刚才统计的和找到的接口描述符 */
retval = usb_parse_interface(ddev, cfgno, config, buffer, size, inums, nalts); /* 这个函数解析接口描述符*/
if (retval < 0)
return retval;
buffer += retval; /* buffer指向下一个接口描述符 */
size -= retval; /* 调整剩余数据大小 */
}
}
这个函数只操作了图4中config指针指向的某一项,如果完成配置的设置,则设备结构体中成员的情况是这样:
图5
这个函数中有两个数组inums
nalts
,分别来记录一个接口编号和每个接口编号下有几个设置,假设这个配置下有三个接口描述符,其中第一二个接口描述符的接口编号是0,第三个接口描述符的接口编号是1,那么inums
nalts
这两个数组中数据是图5下面所示,特别要注意的是,编号的接口不一定是按照顺序排列的,编号可能是乱序。
### 2.5 解析接口 ###
上面的过程中已经将配置描述符拷贝到了USB设备结构体中,也统计好了接口的编号和对应的设置数量,并且为接口分配好了空间,并且循环调用了usb_parse_interface
函数设置接口描述符。
解析设置接口描述符的函数是:
int usb_parse_interface(struct device *ddev, int cfgno, struct usb_host_config *config, unsigned char *buffer, int size, u8 inums[], u8 nalts[])
ddev:USB设备结构体中内嵌的dev成员
cfgno:配置号,配置描述符中的bConfigurationValue成员的值
config:接口附属的配置的指针
buffer:指向原始数据区域的指针,此时指向了一个接口描述符首地址
size:原始数据剩余的数据长度
inums[]:解析接口描述符时的数组,保存了接口编号
nalts[]:解析接口描述符时的数组,保存了每个接口下有几个设置,对应inums数组的接口编号
int usb_parse_interface(struct device *ddev, int cfgno, struct usb_host_config *config, unsigned char *buffer, int size, u8 inums[], u8 nalts[])
{
d = (struct usb_interface_descriptor *) buffer; /* 指向一个接口描述符 */
buffer += d->bLength; /* 指向接口描述符结束的下一个字节地址 */
size -= d->bLength; /* 调整未解析的数据长度 */
inum = d->bInterfaceNumber; /* 得到接口编号 */
for (i = 0; i < config->desc.bNumInterfaces; ++i) {
if (inums[i] == inum) { /* 遍历数组,取得该接口在数组的索引值i */
intfc = config->intf_cache[i]; /* 临时变量指向对应设备编号索引的地方 */
break;
}
}
asnum = d->bAlternateSetting; /* 取得接口的设置号 */
for ((i = 0, alt = &intfc->altsetting[0]); i < intfc->num_altsetting; (++i, ++alt)) {
if (alt->desc.bAlternateSetting == asnum) { /* 如果这个设置已经统计过就跳到下一个描述符 */
goto skip_to_next_interface_descriptor;
}
}
++intfc->num_altsetting; /* 接口的设置数量加1 */
memcpy(&alt->desc, d, USB_DT_INTERFACE_SIZE); /* 复制接口描述符 */
alt->extra = buffer; /* 额外描述符 */
i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT, USB_DT_INTERFACE, &n); /* 找到下一个端点描述符或者接口描述符 */
alt->extralen = i; /* 额外描述符的大小 */
buffer += i; /* 调整指针指向下一个接口描述符或者端点描述符 */
size -= i; /* 调整剩余没解析数据的长度 */
num_ep = num_ep_orig = alt->desc.bNumEndpoints; /* 得到这个接口下的端点数 */
len = sizeof(struct usb_host_endpoint) * num_ep; /* 计算所有端点需要的空间大小 */
alt->endpoint = kzalloc(len, GFP_KERNEL); /* 为这些端点分配空间 */
while (size > 0) {
if (((struct usb_descriptor_header *) buffer)->bDescriptorType
== USB_DT_INTERFACE) /* 如果此时buffer没有指向一个接口描述符,那么指向的就是端口描述符 */
break;
retval = usb_parse_endpoint(ddev, cfgno, inum, asnum, alt, num_ep, buffer, size); /* 解析这个接口下的端点描述符 */
buffer += retval; /* 调整指针位置 */
size -= retval; /* 调整剩余没解析数据长度 */
}
return buffer - buffer0; /* 返回这个函数处理的数据的大小 */
skip_to_next_interface_descriptor:
i = find_next_descriptor(buffer, size, USB_DT_INTERFACE, USB_DT_INTERFACE, NULL); /* 找到下个接口描述符 */
return buffer - buffer0 + i; /* 计算从传入的地址到下个接口描述符的地址的偏移量 */
}
这个函数结束之后,这个配置下的接口情况如图所示(调整了接口的顺序):
图6
当在解析配置的函数中循环调用完解析接口的函数后,intf_cache[]中的某个指针指向usb_interface_cache
结构体被设置,这个结构体中的altsetting成员就代表了一个接口,同一个usb_interface_cache
结构中的接口的接口编号是相同的,该接口编号有几个接口设置,那么altsetting数组就是多长。altsetting数组的顺序与接口的设置号也没有关系,与原始数据中的顺序有关
### 2.6 解析端点 ###
上面解析设置了接口描述符,并为端点描述符分配了空间,并且循环调用了usb_parse_endpoint
函数解析设置端点,假设每个接口下有一个端点。
解析设置端点描述符的接口:
int usb_parse_endpoint(struct device *ddev, int cfgno, int inum, int asnum, struct usb_host_interface *ifp, int num_ep, unsigned char *buffer, int size)
ddev:USB设备结构体中的dev成员
cfgno:配置号,配置描述符中的bConfigurationValue成员的值
inum:接口的编号
asnum:接口的设置号
ifp:指向一个接口的指针
num_ep:该接口的端点数,是接口描述符中的bNumEndpoints成员的值
buffer:未解析的数据首地址,此时应该是指向了一个端点描述符
size:未解析数据的长度
该函数处理过程如下:
int usb_parse_endpoint(struct device *ddev, int cfgno, int inum, int asnum, struct usb_host_interface *ifp, int num_ep, unsigned char *buffer, int size)
{
d = (struct usb_endpoint_descriptor *) buffer; /* 指向了一个端点描述符 */
buffer += d->bLength;
size -= d->bLength; /* 调整指针和大小 */
endpoint = &ifp->endpoint[ifp->desc.bNumEndpoints]; /* 临时指针指向一个端点 */
++ifp->desc.bNumEndpoints; /* 为端点数计数 */
...
memcpy(&endpoint->desc, d, n); /* 复制端点描述符 */
}
由于这个函数只操作了usb_host_interface
结构体, 所以函数执行完的数据状态以一个usb_host_interface
为例,如下图所示:
图7
### 2.7 设置配置和注册接口 ###
当上述过程结束后,USB设备结构体中的描述符都被填充,但是会发现有几个指针成员没有设置,例如USB设备结构体中的struct usb_host_config *actconfig;
指针,此时还没有设置,还有struct usb_host_config
结构体中的struct usb_interface *interface[USB_MAXINTERFACES];
并没有被设置。这两个成员是在配置和注册接口时被设置的。
当hub线程探测到一个设备插入,向设备添加了一个设备,也就是上面的usb_new_device()
函数执行的时候,向总线添加一个新的USB设备,就会匹配到USB设备驱动usb_generic_driver
,然后调用驱动的probe函数:
int generic_probe(struct usb_device *udev)
{
c = usb_choose_configuration(udev); /* 选择一个配置 */
err = usb_set_configuration(udev, c); /* 配置设备 */
}
int usb_set_configuration(struct usb_device *dev, int configuration)
{
...
cp = &dev->config[i]; /* 根据配置号找到对应的结构体 */
...
dev->actconfig = cp; /* 设备结构中的actconfig成员指向了当前的配置 */
}
实际上actconfig指向了设备结构体中的config的一项,这项就是当前的配置
在看interface指针数组是怎么设置的
int usb_set_configuration(struct usb_device *dev, int configuration)
{
...
cp = &dev->config[i]; /* 根据配置号找到对应的结构体 */
...
nintf = cp->desc.bNumInterfaces; /* 得到配置下的接口数 */
new_interfaces = kmalloc(nintf * sizeof(*new_interfaces), GFP_NOIO); /* 分配指针数组 */
for (; n < nintf; ++n) {
new_interfaces[n] = kzalloc( sizeof(struct usb_interface), GFP_NOIO); /* 为每个指针数组的指针分配空间 */
}
for (i = 0; i < nintf; ++i) {
cp->interface[i] = intf = new_interfaces[i]; /* 在这里设置interface指针数组 */
intfc = cp->intf_cache[i]; /* 指向一个intf_cache */
intf->altsetting = intfc->altsetting; /* 指向接口数组的第一项 */
intf->num_altsetting = intfc->num_altsetting;
alt = usb_altnum_to_altsetting(intf, 0); /* 获得这个接口下设置号是0号的设置 */
...
intf->cur_altsetting = alt; /* 指向当前使用的接口 */
...
intf->dev.bus = &usb_bus_type; /* 设置接口设备的总线 */
intf->dev.type = &usb_if_device_type; /* 设置成接口设备类型 */
}
kfree(new_interfaces);
for (i = 0; i < nintf; ++i) {
device_add(&intf->dev); /* 将这个接口设备加入到总线 */
}
}
配置完成后配置结构里的指针如下图所示:
图8
实际上系统是把USB设备结构体中的当前配置下的interface[]数组中的有效的struct usb_interface
对象作为一个USB接口设备添加到了USB总线上,根据struct usb_interface
结构中的altsetting成员和num_altsetting成员能得到这个接口编号里所有的设置接口,而cur_altsetting成员则指向了当前接口的设置接口。