<kernel>kernel 6.4 USB-之-hub_port_init()分析
kernel 6.4 USB系列文章如下:
<kernel>kernel 6.4 USB-之-hub_event()分析
<kernel>kernel 6.4 USB-之-port_event()分析
<kernel>kernel 6.4 USB-之-hub_port_connect_change()分析
<kernel>kernel 6.4 USB-之-hub_port_connect()分析
<kernel>kernel 6.4 USB-之-hub_port_init()分析
本文是基于linux kernel 6.4版本内核分析;源码下载路径:linux kernel
一、前言
hub_port_init()函数是Linux内核中的USB驱动程序的一部分,它位于USB核心驱动程序的hub.c文件中。这段代码的主要功能是初始化USB设备并将其连接到USB集线器端口。
具体来说,这段代码的主要步骤包括:
(1) 重置设备并获取设备描述符。
(2) 根据设备的速度(例如,超高速、高速、全速或低速)设置端点0的最大数据包大小。
(3) 尝试多次获取设备描述符,如果失败则重试。
(4) 如果设备是无线USB设备,我们已经在连接确认序列中分配了一个未授权的地址;授权将分配最终地址。
(5) 获取设备描述符,并根据需要设置等待时间。
(6) 如果设备是超高速设备但设备描述符显示它不是,进行热重置。
(7) 检查端点0的最大数据包大小是否有效。
(8) 获取设备描述符,并检测任何特殊的设备特性。
(9) 通知主机控制器驱动程序我们已经连接并寻址了一个设备。
如果在此过程中出现任何错误,代码将禁用集线器端口并返回错误值。
二、hub_port_init()函数
hub_port_init()函数内容如下:
static int
hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
int retry_counter)
{
struct usb_device *hdev = hub->hdev;
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
struct usb_port *port_dev = hub->ports[port1 - 1];
int retries, operations, retval, i;
unsigned delay = HUB_SHORT_RESET_TIME;
enum usb_device_speed oldspeed = udev->speed;
const char *speed;
int devnum = udev->devnum;
const char *driver_name;
bool do_new_scheme;
/* root hub ports have a slightly longer reset period
* (from USB 2.0 spec, section 7.1.7.5)
*/
if (!hdev->parent) {
delay = HUB_ROOT_RESET_TIME;
if (port1 == hdev->bus->otg_port)
hdev->bus->b_hnp_enable = 0;
}
/* Some low speed devices have problems with the quick delay, so */
/* be a bit pessimistic with those devices. RHbug #23670 */
if (oldspeed == USB_SPEED_LOW)
delay = HUB_LONG_RESET_TIME;
/* Reset the device; full speed may morph to high speed */
/* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */
retval = hub_port_reset(hub, port1, udev, delay, false);
if (retval < 0) /* error or disconnect */
goto fail;
/* success, speed is known */
retval = -ENODEV;
/* Don't allow speed changes at reset, except usb 3.0 to faster */
if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed &&
!(oldspeed == USB_SPEED_SUPER && udev->speed > oldspeed)) {
dev_dbg(&udev->dev, "device reset changed speed!\n");
goto fail;
}
oldspeed = udev->speed;
/* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...
* it's fixed size except for full speed devices.
* For Wireless USB devices, ep0 max packet is always 512 (tho
* reported as 0xff in the device descriptor). WUSB1.0[4.8.1].
*/
switch (udev->speed) {
case USB_SPEED_SUPER_PLUS:
case USB_SPEED_SUPER:
case USB_SPEED_WIRELESS: /* fixed at 512 */
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);
break;
case USB_SPEED_HIGH: /* fixed at 64 */
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
break;
case USB_SPEED_FULL: /* 8, 16, 32, or 64 */
/* to determine the ep0 maxpacket size, try to read
* the device descriptor to get bMaxPacketSize0 and
* then correct our initial guess.
*/
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
break;
case USB_SPEED_LOW: /* fixed at 8 */
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(8);
break;
default:
goto fail;
}
if (udev->speed == USB_SPEED_WIRELESS)
speed = "variable speed Wireless";
else
speed = usb_speed_string(udev->speed);
/*
* The controller driver may be NULL if the controller device
* is the middle device between platform device and roothub.
* This middle device may not need a device driver due to
* all hardware control can be at platform device driver, this
* platform device is usually a dual-role USB controller device.
*/
if (udev->bus->controller->driver)
driver_name = udev->bus->controller->driver->name;
else
driver_name = udev->bus->sysdev->driver->name;
if (udev->speed < USB_SPEED_SUPER)
dev_info(&udev->dev,
"%s %s USB device number %d using %s\n",
(udev->config) ? "reset" : "new", speed,
devnum, driver_name);
/* Set up TT records, if needed */
if (hdev->tt) {
udev->tt = hdev->tt;
udev->ttport = hdev->ttport;
} else if (udev->speed != USB_SPEED_HIGH
&& hdev->speed == USB_SPEED_HIGH) {
if (!hub->tt.hub) {
dev_err(&udev->dev, "parent hub has no TT\n");
retval = -EINVAL;
goto fail;
}
udev->tt = &hub->tt;
udev->ttport = port1;
}
/* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way?
* Because device hardware and firmware is sometimes buggy in
* this area, and this is how Linux has done it for ages.
* Change it cautiously.
*
* NOTE: If use_new_scheme() is true we will start by issuing
* a 64-byte GET_DESCRIPTOR request. This is what Windows does,
* so it may help with some non-standards-compliant devices.
* Otherwise we start with SET_ADDRESS and then try to read the
* first 8 bytes of the device descriptor to get the ep0 maxpacket
* value.
*/
do_new_scheme = use_new_scheme(udev, retry_counter, port_dev);
for (retries = 0; retries < GET_DESCRIPTOR_TRIES; (++retries, msleep(100))) {
if (hub_port_stop_enumerate(hub, port1, retries)) {
retval = -ENODEV;
break;
}
if (do_new_scheme) {
struct usb_device_descriptor *buf;
int r = 0;
retval = hub_enable_device(udev);
if (retval < 0) {
dev_err(&udev->dev,
"hub failed to enable device, error %d\n",
retval);
goto fail;
}
#define GET_DESCRIPTOR_BUFSIZE 64
buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);
if (!buf) {
retval = -ENOMEM;
continue;
}
/* Retry on all errors; some devices are flakey.
* 255 is for WUSB devices, we actually need to use
* 512 (WUSB1.0[4.8.1]).
*/
for (operations = 0; operations < GET_MAXPACKET0_TRIES;
++operations) {
buf->bMaxPacketSize0 = 0;
r = 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);
switch (buf->bMaxPacketSize0) {
case 8: case 16: case 32: case 64: case 255:
if (buf->bDescriptorType ==
USB_DT_DEVICE) {
r = 0;
break;
}
fallthrough;
default:
if (r == 0)
r = -EPROTO;
break;
}
/*
* Some devices time out if they are powered on
* when already connected. They need a second
* reset. But only on the first attempt,
* lest we get into a time out/reset loop
*/
if (r == 0 || (r == -ETIMEDOUT &&
retries == 0 &&
udev->speed > USB_SPEED_FULL))
break;
}
udev->descriptor.bMaxPacketSize0 =
buf->bMaxPacketSize0;
kfree(buf);
retval = hub_port_reset(hub, port1, udev, delay, false);
if (retval < 0) /* error or disconnect */
goto fail;
if (oldspeed != udev->speed) {
dev_dbg(&udev->dev,
"device reset changed speed!\n");
retval = -ENODEV;
goto fail;
}
if (r) {
if (r != -ENODEV)
dev_err(&udev->dev, "device descriptor read/64, error %d\n",
r);
retval = -EMSGSIZE;
continue;
}
#undef GET_DESCRIPTOR_BUFSIZE
}
/*
* If device is WUSB, we already assigned an
* unauthorized address in the Connect Ack sequence;
* authorization will assign the final address.
*/
if (udev->wusb == 0) {
for (operations = 0; operations < SET_ADDRESS_TRIES; ++operations) {
retval = hub_set_address(udev, devnum);
if (retval >= 0)
break;
msleep(200);
}
if (retval < 0) {
if (retval != -ENODEV)
dev_err(&udev->dev, "device not accepting address %d, error %d\n",
devnum, retval);
goto fail;
}
if (udev->speed >= USB_SPEED_SUPER) {
devnum = udev->devnum;
dev_info(&udev->dev,
"%s SuperSpeed%s%s USB device number %d using %s\n",
(udev->config) ? "reset" : "new",
(udev->speed == USB_SPEED_SUPER_PLUS) ?
" Plus" : "",
(udev->ssp_rate == USB_SSP_GEN_2x2) ?
" Gen 2x2" :
(udev->ssp_rate == USB_SSP_GEN_2x1) ?
" Gen 2x1" :
(udev->ssp_rate == USB_SSP_GEN_1x2) ?
" Gen 1x2" : "",
devnum, driver_name);
}
/* cope with hardware quirkiness:
* - let SET_ADDRESS settle, some device hardware wants it
* - read ep0 maxpacket even for high and low speed,
*/
msleep(10);
if (do_new_scheme)
break;
}
retval = usb_get_device_descriptor(udev, 8);
if (retval < 8) {
if (retval != -ENODEV)
dev_err(&udev->dev,
"device descriptor read/8, error %d\n",
retval);
if (retval >= 0)
retval = -EMSGSIZE;
} else {
u32 delay;
retval = 0;
delay = udev->parent->hub_delay;
udev->hub_delay = min_t(u32, delay,
USB_TP_TRANSMISSION_DELAY_MAX);
retval = usb_set_isoch_delay(udev);
if (retval) {
dev_dbg(&udev->dev,
"Failed set isoch delay, error %d\n",
retval);
retval = 0;
}
break;
}
}
if (retval)
goto fail;
/*
* Some superspeed devices have finished the link training process
* and attached to a superspeed hub port, but the device descriptor
* got from those devices show they aren't superspeed devices. Warm
* reset the port attached by the devices can fix them.
*/
if ((udev->speed >= USB_SPEED_SUPER) &&
(le16_to_cpu(udev->descriptor.bcdUSB) < 0x0300)) {
dev_err(&udev->dev, "got a wrong device descriptor, "
"warm reset device\n");
hub_port_reset(hub, port1, udev,
HUB_BH_RESET_TIME, true);
retval = -EINVAL;
goto fail;
}
if (udev->descriptor.bMaxPacketSize0 == 0xff ||
udev->speed >= USB_SPEED_SUPER)
i = 512;
else
i = udev->descriptor.bMaxPacketSize0;
if (usb_endpoint_maxp(&udev->ep0.desc) != i) {
if (udev->speed == USB_SPEED_LOW ||
!(i == 8 || i == 16 || i == 32 || i == 64)) {
dev_err(&udev->dev, "Invalid ep0 maxpacket: %d\n", i);
retval = -EMSGSIZE;
goto fail;
}
if (udev->speed == USB_SPEED_FULL)
dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i);
else
dev_warn(&udev->dev, "Using ep0 maxpacket: %d\n", i);
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i);
usb_ep0_reinit(udev);
}
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
if (retval < (signed)sizeof(udev->descriptor)) {
if (retval != -ENODEV)
dev_err(&udev->dev, "device descriptor read/all, error %d\n",
retval);
if (retval >= 0)
retval = -ENOMSG;
goto fail;
}
usb_detect_quirks(udev);
if (udev->wusb == 0 && le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201) {
retval = usb_get_bos_descriptor(udev);
if (!retval) {
udev->lpm_capable = usb_device_supports_lpm(udev);
udev->lpm_disable_count = 1;
usb_set_lpm_parameters(udev);
usb_req_set_sel(udev);
}
}
retval = 0;
/* notify HCD that we have a device connected and addressed */
if (hcd->driver->update_device)
hcd->driver->update_device(hcd, udev);
hub_set_initial_usb2_lpm_policy(udev);
fail:
if (retval) {
hub_port_disable(hub, port1, 0);
update_devnum(udev, devnum); /* for disconnect processing */
}
return retval;
}
下面就对hub_port_init()函数内容详细分析:
2.1 第5-14行:声明和初始化变量
第5-14行:声明和初始化一些变量,这些变量将在后续的USB集线器操作中使用。
具体来说:
struct usb_device *hdev = hub->hdev;:这行代码声明了一个指向usb_device结构的指针hdev,并将其初始化为指向hub结构体中的hdev成员。hdev代表的是USB集线器设备。
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);:这行代码声明了一个指向usb_hcd结构的指针hcd,并通过调用bus_to_hcd函数将其初始化为指向hdev所在总线对应的主机控制器驱动(Host Controller Driver)。
struct usb_port *port_dev = hub->ports[port1 - 1];:这行代码声明了一个指向usb_port结构的指针port_dev,并将其初始化为指向hub中的某个端口。
int retries, operations, retval, i;:这行代码声明了四个整型变量,分别是retries(重试次数)、operations(操作次数)、retval(返回值)和i(通常用作循环计数器)。
unsigned delay = HUB_SHORT_RESET_TIME;:这行代码声明了一个无符号整型变量delay,并将其初始化为HUB_SHORT_RESET_TIME,这通常是一个定义在头文件中的常量,表示USB集线器短复位时间。
enum usb_device_speed oldspeed = udev->speed;:这行代码声明了一个usb_device_speed枚举类型的变量oldspeed,并将其初始化为udev的速度。
const char *speed;:这行代码声明了一个指向常量字符串的指针speed。
int devnum = udev->devnum;:这行代码声明了一个整型变量devnum,并将其初始化为udev的设备号。
const char *driver_name;:这行代码声明了一个指向常量字符串的指针driver_name。
bool do_new_scheme;:这行代码声明了一个布尔类型的变量do_new_scheme。
2.2 第19-23行:处理根集线器(root hub)端口的复位时间
第19-23行:位于Linux内核的USB驱动程序中,特别是在处理USB集线器(hub)的部分。它主要处理根集线器(root hub)端口的复位时间。
具体来说:
if (!hdev->parent) {:这行代码检查USB设备(由hdev指针表示)是否有父设备。如果没有,那么这个设备就是根集线器(root hub)。
delay = HUB_ROOT_RESET_TIME;:这行代码将复位延迟时间设置为根集线器的复位时间。这是因为根集线器的复位时间略长一些,这一点在USB 2.0规范的第7.1.7.5节中有所规定。
if (port1 == hdev->bus->otg_port):这行代码检查正在处理的端口(port1)是否是OTG(USB On-The-Go)端口。OTG是一种允许USB设备直接进行通信的规范,而无需通过主机。
hdev->bus->b_hnp_enable = 0;:如果上述条件成立,即正在处理的端口是OTG端口,那么这行代码就会禁用HNP(Host Negotiation Protocol,主机协商协议)。HNP是OTG规范的一部分,允许一个USB设备向另一个USB设备请求成为主机。在这里,我们将其禁用,可能是因为在复位期间,我们不希望进行主机协商。
总的来说,这段代码的作用是处理根集线器的复位时间,并在特定条件下禁用OTG端口的主机协商。
2.3 第27-28行:处理低速USB设备的复位时间
第27-28行:位于Linux内核的USB驱动程序中,特别是在处理USB集线器(hub)的部分。这段代码的作用是处理低速USB设备的复位时间。
具体来说:
if (oldspeed == USB_SPEED_LOW):这行代码检查USB设备的速度是否为低速(USB_SPEED_LOW)。USB设备的速度可以是低速、全速、高速或超高速,这是由设备的硬件和固件决定的。
delay = HUB_LONG_RESET_TIME;:如果上述条件成立,即USB设备的速度为低速,那么这行代码就会将复位延迟时间设置为长复位时间(HUB_LONG_RESET_TIME)。这是因为一些低速设备在复位时可能会有问题,需要更长的时间来完成复位。
这段代码的注释也解释了这个问题。它提到,“一些低速设备在快速延迟下有问题,所以对这些设备要悲观一些”。这是一个对实际问题的应对策略,即在处理低速设备时使用更长的复位时间,以避免可能出现的问题。
2.4 第31-34行:重置USB设备的端口
第31-34行:位于Linux内核的USB驱动程序中,特别是在处理USB集线器(hub)的部分。这段代码的主要作用是重置USB设备的端口并检查其返回值。
具体来说:
retval = hub_port_reset(hub, port1, udev, delay, false);:这行代码调用hub_port_reset函数来重置指定的USB设备端口。这个函数的参数包括集线器、端口号、USB设备、延迟时间和一个布尔值,表示是否强制重置。函数的返回值存储在retval变量中。
if (retval < 0) goto fail;:这行代码检查hub_port_reset函数的返回值。如果返回值小于0,说明函数执行失败,可能是由于错误或设备已断开连接。在这种情况下,代码会跳转到fail标签处执行,fail标签通常位于这段代码下方,包含了错误处理或资源清理的代码。
注释中的内容提到,全速设备在重置后可能会变为高速设备,USB 2.0设备在重置后可能会变为超高速设备。这是因为设备在重置后可能会改变其操作速度。
2.5 第40-45行:检查设备的速度是否改变
第40-45行:主要作用是在设备复位后检查设备的速度是否发生了改变。
具体来说:
if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed && !(oldspeed == USB_SPEED_SUPER && udev->speed > oldspeed)):这个条件判断语句检查设备复位前后的速度是否发生了改变。如果速度从已知的某个值改变,且新的速度不是“超高速”到更快的速度,那么就满足这个条件。
dev_dbg(&udev->dev, “device reset changed speed!\n”);:如果满足上述条件,那么这行代码就会向调试日志输出一条消息,表示设备复位后速度发生了改变。
goto fail;:然后,代码会跳转到fail标签处执行。fail标签通常位于这段代码下方,包含了错误处理或资源清理的代码。
oldspeed = udev->speed;:最后,这行代码将设备当前的速度赋值给oldspeed变量,以便后续的操作可以知道设备的新速度。
总的来说,这段代码的作用是检查设备复位后速度是否发生了改变,如果发生了非法的改变(即除了从“超高速”变为更快的速度之外的其他改变),那么就进行错误处理。
2.6 第52-73行:设置其端点0(ep0)的最大数据包
第52-73行:主要作用是根据USB设备的速度设置其端点0(ep0)的最大数据包大小。
具体来说:
switch (udev->speed) {:这行代码开始一个switch语句,根据USB设备的速度(由udev->speed表示)来决定执行哪个case。
case USB_SPEED_SUPER_PLUS:、case USB_SPEED_SUPER:和case USB_SPEED_WIRELESS::这些case表示设备的速度为超超高速(SuperSpeedPlus)、超高速(SuperSpeed)或无线USB。这些设备的端点0的最大数据包大小固定为512字节。
case USB_SPEED_HIGH::这个case表示设备的速度为高速USB。这些设备的端点0的最大数据包大小固定为64字节。
case USB_SPEED_FULL::这个case表示设备的速度为全速USB。这些设备的端点0的最大数据包大小可以是8、16、32或64字节。在这里,我们首先假设最大数据包大小为64字节,然后尝试读取设备描述符来获取实际的最大数据包大小,并根据需要进行修正。
case USB_SPEED_LOW::这个case表示设备的速度为低速USB。这些备的端点0的最大数据包大小固定为8字节。
default::这个case用于处理未知的设备速度。在这种情况下,代码会跳转到fail标签处执行,fail标签通常位于这段代码下方,包含了错误处理或资源清理的代码。
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);这样的语句用于设置端点0的最大数据包大小。cpu_to_le16是一个宏,用于将CPU的主机字节序转换为小端字节序。
总的来说,这段代码的作用是根据USB设备的速度设置其端点0的最大数据包大小。
2.7 第75-78行:
第75-78行:主要作用是根据USB设备的速度设置一个字符串,描述设备的速度。
具体来说:
if (udev->speed == USB_SPEED_WIRELESS):这行代码检查USB设备的速度是否为无线(USB_SPEED_WIRELESS)。USB设备的速度可以是低速、全速、高速、超高速、无线等,这是由设备的硬件和固件决定的。
speed = “variable speed Wireless”;:如果上述条件成立,即USB设备的速度为无线,那么这行代码就会将变量speed设置为字符串"variable speed Wireless"。
else:这是上述条件不成立时执行的代码,即USB设备的速度不为无线。
speed = usb_speed_string(udev->speed);:这行代码调用usb_speed_string函数,传入USB设备的速度作为参数,返回一个描述设备速度的字符串,并将这个字符串赋值给speed变量。这个函数根据设备的速度返回相应的字符串,例如,对于低速设备,返回"Low";对于全速设备,返回"Full";对于高速设备,返回"High";对于超高速设备,返回"Super"等。
总的来说,这段代码的作用是根据USB设备的速度设置一个描述设备速度的字符串。这个字符串可以用于日志记录、错误报告、用户界面显示等。
2.8 第87-96行:获取USB设备的控制器驱动程序的名称
第87-96行:主要作用是获取USB设备的控制器驱动程序的名称。
具体来说:
if (udev->bus->controller->driver):这行代码检查USB设备所在的总线的控制器是否有驱动程序。如果有,那么就进入if语句。
driver_name = udev->bus->controller->driver->name;:这行代码获取控制器驱动程序的名称,并将其赋值给driver_name变量。
else:这个部分处理控制器没有驱驱动程序的情况。在这种情况下,代码会获取系统设备(sysdev)的驱动程序的名称。
driver_name = udev->bus->sysdev->driver->name;:这行代码获取系统设备的驱动程序的名称,并将其赋值给driver_name变量。
这段代码的注释解释了为什么控制器可能没有驱动程序。它提到,控制器设备可能是平台设备和根集线器(roothub)之间的中间设备。这个中间设备可能不需要设备驱动程序,因为所有的硬件控制都可以在平台设备驱动程序中完成。这个平台设备通常是一个双角色USB控制器设备。
总的来说,这段代码的作用是获取USB设备的控制器驱动程序的名称,如果控制器没有驱动程序,那么就获取系统设备的驱动程序的名称。
2.9 第99-111行:设置USB设备的事务翻译
第99-111行:主要作用是设置USB设备的事务翻译(Transaction Translator,简称TT)记录。
USB 2.0规范引入了事务翻译器的概念,它允许高速(480Mbps)USB主机控制器和低速(1.5Mbps)或全速(12Mbps)的USB设备进行通信。每个USB 2.0的全速或低速设备都会连接到一个事务翻译器,而这个事务翻译器是连接到高速USB集线器的。
具体来说:
if (hdev->tt) {:这行代码检查父设备(即集线器)是否有事务翻译器。如果有,那么就进入if语句。
udev->tt = hdev->tt;和udev->ttport = hdev->ttport;:这两行代码将父设备的事务翻译器和事务翻译器的端口号分别赋值给设备的tt和ttport字段。
else if (udev->speed != USB_SPEED_HIGH && hdev->speed == USB_SPEED_HIGH) {:这个条件判断语句检查设备是否是低速或全速设备,并且父设备是否是高速设备。如果是,那么就进入else if语句。
if (!hub->tt.hub) {:这行代码检查集线器是否有事务翻译器。如果没有,那么就进入if语句。
dev_err(&udev->dev, “parent hub has no TT\n”);:这行代码输出一条错误信息,表示父集线器没有事务翻译器。
retval = -EINVAL;和goto fail;:这两行代码设置返回值为错误值,并跳转到fail标签处执行错误处理代码。
udev->tt = &hub->tt;和udev->ttport = port1;:这两行代码将集线器的事务翻译器和端口号分别赋值给设备的tt和ttport字段。
总的来说,这段代码的作用是设置USB设备的事务翻译器记录,以支持高速USB主机控制器和低速或全速USB设备之间的通信。
2.10 第125行:决定初始化USB设备方案
第125行:主要作用是决定初始化USB设备时,是使用新的方案(首先发送64字节的GET_DESCRIPTOR请求),还是旧的方案(首先发送SET_ADDRESS请求,然后尝试读取设备描述符的前8个字节以获取ep0的最大包值)。
具体来说:
注释部分解释了为什么要以这种方式交错GET_DESCRIPTOR和SET_ADDRESS。原因是有些设备的硬件和固件在这方面可能存在问题,而这种方式是Linux长期以来的做法,因此要谨慎改变。另外,如果use_new_scheme()函数返回真,那么将首先发送一个64字节的GET_DESCRIPTOR请求。这是Windows的做法,因此可能有助于处理一些不符合标准的设备。否则,将首先发送SET_ADDRESS请求,然后尝试读取设备描述符的前8个字节以获取ep0的最大包值。
do_new_scheme = use_new_scheme(udev, retry_counter, port_dev);:这行代码调用use_new_scheme()函数,决定是使用新的方案还是旧的方案。use_new_scheme()函数的参数包括设备udev,重试计数器retry_counter和端口设备port_dev。
总的来说,这段代码的作用是决定初始化USB设备时,是使用新的方案还是旧的方案。
2.11 第127行:循环
第127行:主要作用是尝试重复执行某个操作一定的次数(在这个例子中是GET_DESCRIPTOR_TRIES次),并在每次尝试之间暂停100毫秒。
具体来说:
for (retries = 0; retries < GET_DESCRIPTOR_TRIES;:这部分代码初始化一个名为retries的计数器为0,并设置循环的结束条件为retries小于GET_DESCRIPTOR_TRIES。GET_DESCRIPTOR_TRIES是一个预定义的常量,表示最大的尝试次数,其值为2 ( #define GET_DESCRIPTOR_TRIES 2) 。
(++retries, msleep(100))) {:这部分代码在每次循环迭代时执行两个操作。首先,它增加retries计数器的值(即,每次循环迭代后,retries都会加1)。然后,它调用msleep(100)函数,使当前的进程暂停100毫秒(即,每次循环迭代后,都会暂停100毫秒)。
这段代码通常用在需要多次尝试某个可能会失败的操作的情况下。在每次尝试之间暂停一段时间,可以给系统一些时间来恢复或者调整状态,以便下次尝试能够成功。
2.12 第128-131行:尝试停止对指定USB端口的枚举过程
第128-131行:主要作用是尝试停止对指定USB端口的枚举过程,并在失败时返回一个错误值。
具体来说:
if (hub_port_stop_enumerate(hub, port1, retries)) {:这行代码调用hub_port_stop_enumerate()函数,尝试停止对指定的USB端口(由hub和port1参数指定)的枚举过程。retries参数表示尝试的次数。如果hub_port_stop_enumerate()函数返回非零值,那么就进入if语句。
retval = -ENODEV;:这行代码设置返回值为-ENODEV,表示没有找到设备。这是一个错误值,用于表示在停止端口枚举过程时发生了错误。
break;:这行代码退出当前的循环或者switch语句。
总的来说,这段代码的作用是尝试停止对指定USB端口的枚举过程,并在失败时返回一个错误值。
2.13 第133-135行:进入初始化新方案部分处理
第133-135行:如果前面确认使用新的初始化USB设备方案,则进入这个if中。并定义两个变量。
2.14 第137-143行:启用一个USB设备
第137-143行:主要作用是尝试启用一个USB设备,并在失败时输出错误信息并跳转到错误处理部分。
具体来说:
retval = hub_enable_device(udev);:这行代码调用hub_enable_device()函数,尝试启用一个USB设备(由udev参数指定)。hub_enable_device()函数的返回值被赋值给retval。如果该函数成功执行,将返回0;否则,将返回一个负的错误代码。
if (retval < 0) {:这行代码检查retval的值,如果它小于0,表示hub_enable_device()函数执行失败,因此进入if语句。
dev_err(&udev->dev, “hub failed to enable device, error %d\n”, retval);:这行代码调用dev_err()函数,输出一条错误信息。这条信息包含了"hub failed to enable device, error %d\n"这样的格式字符串和retval的值。
goto fail;:这行代码执行一个goto语句,跳转到标签为fail的代码位置。这通常是一段错误处理代码,用于清理资源、恢复状态等。
总的来说,这段代码的作用是尝试启用一个USB设备,并在失败时输出错误信息并跳转到错误处理部分。
2.15 第145-150行:分配一个大小为64字节的内存缓冲区
第145-150行:主要作用是尝试分配一个大小为64字节的内存缓冲区,并在内存分配失败时设置错误值并跳过当前循环迭代。
具体来说:
#define GET_DESCRIPTOR_BUFSIZE 64:这行代码定义了一个名为GET_DESCRIPTOR_BUFSIZE的宏,值为64。这个宏代表了要分配的内存缓冲区的大小。
buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);:这行代码调用kmalloc()函数,尝试分配一个大小为GET_DESCRIPTOR_BUFSIZE(即64)字节的内存缓冲区。kmalloc()函数的返回值是分配的内存缓冲区的指针,这个指针被赋值给buf。如果内存分配失败,kmalloc()函数将返回NULL。
if (!buf) {:这行代码检查buf的值,如果它是NULL,表示kmalloc()函数执行失败,因此进入if语句。
retval = -ENOMEM;:这行代码设置返回值为-ENOMEM,表示内存不足。这是一个错误值,用于表示内存分配失败。
continue;:这行代码执行一个continue语句,跳过当前的循环迭代,直接开始下一次迭代。
总的来说,这段代码的作用是尝试分配一个大小为64字节的内存缓冲区,并在内存分配失败时设置错误值并跳过当前循环迭代。
2.16 第156-187行:获取USB设备的描述符
第156-187行:主要功能是尝试获取USB设备的描述符,特别是设备的最大数据包大小(bMaxPacketSize0)。这是通过发送一个控制消息(usb_control_msg)到USB设备来实现的。
代码的主体是一个for循环,最多尝试GET_MAXPACKET0_TRIES次获取设备描述符。在每次尝试中,它首先将bMaxPacketSize0设置为0,然后发送一个获取描述符的控制消息。
接收到的描述符被存储在buf中。然后根据buf中的bMaxPacketSize0的值进行判断。如果它的值是8、16、32、64或255,并且描述符类型是设备描述符(USB_DT_DEVICE),那么就认为获取描述符成功,将r设置为0并退出循环。
如果bMaxPacketSize0的值不是这些值之一,或者描述符类型不是设备描述符,那么就将r设置为-EPROTO,表示协议错误。
在循环的最后,有一个条件判断。如果r等于0,或者r等于-ETIMEDOUT(表示超时)并且这是第一次尝试,并且设备的速度大于USB_SPEED_FULL,那么就退出循环。
这段代码的主要作用是获取USB设备的描述符,并通过对获取结果的判断,处理一些可能的错误情况。
2.17 第188-190行:设置USB设备描述符的最大包大小
第188-190行:用于设置USB设备描述符的最大包大小。
udev->descriptor.bMaxPacketSize0 = buf->bMaxPacketSize0;
这行代码将buf(一个USB设备描述符的缓冲区)中的bMaxPacketSize0字段的值赋给udev(一个代表USB设备的数据结构)的descriptor.bMaxPacketSize0字段。bMaxPacketSize0字段表示设备的最大控制传输数据包大小。这是USB设备和主机之间进行通信时,单个数据包的最大长度。
kfree(buf);
这行代码是释放之前分配给buf的内存。在Linux内核中,动态内存的分配和释放需要通过专门的函数来完成,这里的kfree就是用于释放内存的函数。在这段代码中,buf的内存被释放,表示其生命周期结束,不再使用。
总的来说,这段代码的作用是从一个临时的USB设备描述符缓冲区中获取设备的最大数据包大小,并设置到设备的描述符中,然后释放临时缓冲区的内存。
2.18 第192-209行:重置USB设备并读取设备描述符
第192-209行:用于重置USB设备并读取设备描述符。
首先,hub_port_reset(hub, port1, udev, delay, false);这行代码尝试重置连接到特定USB端口的设备。如果重置失败或者设备在重置过程中断开连接,那么它会跳转到fail标签处。
然后,它检查设备的速度是否在重置过程中发生了变化。如果发生了变化,那么它会将retval设置为-ENODEV,表示没有这样的设备,然后跳转到fail标签处。
接下来,如果读取设备描述符的过程中发生了错误(由变量r表示),那么它会打印一条错误消息,并将retval设置为-EMSGSIZE,表示消息太长,然后继续执行下一轮的循环。
这段代码的主要作用是重置USB设备并读取设备描述符,同时处理可能出现的一些错误情况
2.19 第216-252行:USB设备的地址设置
第216-252行:用于处理USB设备的地址设置和设备信息打印的部分。
首先,它会检查USB设备是否是无线USB(WUSB)。如果不是,它将尝试为设备设置地址。这个过程可能会尝试多次,每次尝试失败后会暂停200毫秒再重试。如果所有尝试都失败,它将打印一个错误消息并跳转到错误处理部分。
如果设备的速度等于或超过USB 3.0(SuperSpeed),它将打印设备的信息,包括设备是新的还是被重置的,设备的速度,设备的USB 3.1 Gen的版本,设备的编号,以及驱动的名称。
在设置地址后,代码会暂停10毫秒以等待SET_ADDRESS命令被设备硬件接收。然后,如果采用新的方案,它将退出循环。
总的来说,这段代码的主要作用是为USB设备设置地址,并打印设备的一些信息。
2.20 第254-278行:读取设备描述符
第254-278行:主要是从USB设备中读取设备描述符,并设置USB设备的等待延迟。
首先,使用usb_get_device_descriptor函数试图从USB设备(udev)中获取8字节的设备描述符,该函数的返回值(retval)表示实际读取的字节数。
如果读取的字节数小于8,那么会检查错误类型。如果错误类型不是-ENODEV(表示设备不存在),则打印错误消息。如果返回的字节数大于等于0,那么将错误类型设置为-EMSGSIZE(表示消息太长)。
如果读取的字节数等于8,表示成功获取了设备描述符。然后,获取父设备的等待延迟(hub_delay),并设置当前设备的等待延迟为父设备的延迟和USB_TP_TRANSMISSION_DELAY_MAX中的较小值。
使用usb_set_isoch_delay函数设置USB设备的等待延迟。如果设置失败,打印错误消息,但并不改变函数的返回值(retval),即函数的执行结果仍然被视为成功。
总的来说,这段代码的作用是获取USB设备的描述符,并根据其父设备的延迟设置其等待延迟。
2.20.1 usb_get_device_descriptor()
路径:drivers\usb\core\message.c
int usb_get_device_descriptor(struct usb_device *dev, unsigned int size)
{
struct usb_device_descriptor *desc;
int ret;
if (size > sizeof(*desc))
return -EINVAL;
desc = kmalloc(sizeof(*desc), GFP_NOIO);
if (!desc)
return -ENOMEM;
ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, size);
if (ret >= 0)
memcpy(&dev->descriptor, desc, size);
kfree(desc);
return ret;
}
这个函数的作用是读取USB设备的设备描述符,并将其存储在设备结构体的相应位置。
2.20.2 usb_get_descriptor()
路径:drivers\usb\core\message.c
int usb_get_descriptor(struct usb_device *dev, unsigned char type,
unsigned char index, void *buf, int size)
{
int i;
int result;
if (size <= 0) /* No point in asking for no data */
return -EINVAL;
memset(buf, 0, size); /* Make sure we parse really received data */
for (i = 0; i < 3; ++i) {
/* retry on length 0 or error; some devices are flakey */
result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
(type << 8) + index, 0, buf, size,
USB_CTRL_GET_TIMEOUT);
if (result <= 0 && result != -ETIMEDOUT)
continue;
if (result > 1 && ((u8 *)buf)[1] != type) {
result = -ENODATA;
continue;
}
break;
}
return result;
}
EXPORT_SYMBOL_GPL(usb_get_descriptor);
发出一个通用的GET_DESCRIPTOR请求以获取USB设备的描述符。并将获取USB设备的指定类型和编号的描述符存储在输入的缓冲区中。
2.21 第289-297行:检测并修复超速USB设备的设备描述符错误
第289-297行:主要处理了一个特殊情况,即某些超速(Superspeed)USB设备已经完成了链路训练过程并连接到了超速USB集线器端口,但从这些设备获取的设备描述符表明它们并非超速设备。为了修复这种情况,代码对连接这些设备的端口进行了热复位(Warm reset)。
首先,通过判断udev->speed >= USB_SPEED_SUPER和le16_to_cpu(udev->descriptor.bcdUSB) < 0x0300,检查当前设备是否为上述的特殊情况。其中,udev->speed >= USB_SPEED_SUPER判断设备是否为超速设备,le16_to_cpu(udev->descriptor.bcdUSB) < 0x0300判断设备描述符的USB版本是否低于3.0。
如果满足上述条件,即设备速度为超速但设备描述符的USB版本低于3.0,那么打印错误信息,表明获取到了错误的设备描述符。
然后,调用hub_port_reset函数对连接设备的端口进行热复位。其中,hub是设备连接的集线器,port1是设备连接的端口,udev是设备,HUB_BH_RESET_TIME是复位的时间,true表示执行热复位。
最后,将函数的返回值retval设置为-EINVAL,表示获取到了无效的参数,然后跳转到fail标签处。
总的来说,这段代码的作用是检测并修复超速USB设备的设备描述符错误。
2.22 第299-303行:设置USB设备的最大包大小
第299-303行:主要是在设置USB设备的最大包大小。
首先,检查设备描述符中的bMaxPacketSize0字段是否等于0xff,或者设备的速度是否大于或等于超速(USB_SPEED_SUPER)。bMaxPacketSize0字段指定了设备的默认控制端点的最大包大小,0xff通常是一个特殊值,表示最大包大小为64字节。USB_SPEED_SUPER是一个枚举值,表示设备的速度为超速。
如果上述条件满足,那么将变量i设置为512。这是因为超速设备的最大包大小通常为512字节。
如果上述条件不满足,那么将变量i设置为bMaxPacketSize0字段的值。这是因为对于非超速设备,最大包大小由设备描述符中的bMaxPacketSize0字段指定。
总的来说,这段代码的作用是根据设备的类型和速度,设置设备的最大包大小
2.23 第304-317行:USB设备的端点0(EP0)的最大包大小检查和设置
第304-317行:主要是在Linux的USB驱动中,对USB设备的端点0(EP0)的最大包大小进行检查和设置。
usb_endpoint_maxp(&udev->ep0.desc) != i:这是检查当前设备端点0的最大包大小是否与预期的值i相等。如果不相等,进入以下处理流程。
首先,检查设备的速度是否为低速(USB_SPEED_LOW),或者预期的包大小i是否不等于8,16,32或64。这是因为USB规定,低速设备的端点0的最大包大小必须为8字节,全速设备的端点0的最大包大小可以为8,16,32或64字节。如果不满足这些条件,那么打印一条错误信息,并将返回值retval设为-EMSGSIZE(表示消息太大),然后跳转到fail标签。
如果设备的速度为全速(USB_SPEED_FULL),则打印一条调试信息,显示端点0的最大包大小。如果设备的速度不是全速,那么打印一条警告信息,显示正在使用的端点0的最大包大小。
将设备端点0的最大包大小设置为预期的值i,并重新初始化端点0。
总的来说,这段代码的作用是检查和设置USB设备端点0的最大包大小,以确保符合USB规定。如果不符合规定,那么会打印错误信息,并返回错误代码。
2.24 第319-327行:EP0复位后重新读取USB设备的设备描述符
第319-327行:前面经过EP0复位后需要重新读取USB设备的设备描述符。
usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE):这是调用一个函数来读取USB设备的设备描述符。设备描述符是一个数据结构,包含了关于USB设备的一些基本信息,如设备的类别,制造商和产品ID等。USB_DT_DEVICE_SIZE是设备描述符的大小。函数的返回值retval表示实际读取的字节数,如果读取失败,那么返回值将是一个负的错误代码。
if (retval < (signed)sizeof(udev->descriptor)):这是检查实际读取的字节数是否小于设备描述符的大小。如果小于,那么表示读取设备描述符失败,进入以下处理流程。
if (retval != -ENODEV):这是检查返回值是否为-ENODEV。-ENODEV是一个错误代码,表示设备不存在。如果返回值不等于-ENODEV,那么打印一条错误信息,显示读取设备描述符失败,并显示错误代码。
if (retval >= 0):这是检查返回值是否大于或等于0。如果大于或等于0,那么表示实际读取的字节数为0,也就是说没有读取到任何数据。在这种情况下,将返回值retval设置为-ENOMSG(表示没有消息),然后跳转到fail标签。
总的来说,这段代码的作用是读取USB设备的设备描述符,如果读取失败,那么打印错误信息,并返回错误代码。
2.25 第329-339行:检测USB设备的特性并进行设置
第329-339行:检测USB设备的一些特性并进行相应的设置。
usb_detect_quirks(udev);:这是调用一个函数来检测USB设备的特性,如设备是否支持某些特性或有某些特殊的行为。这些特性和行为被称为"quirks",在驱动程序中,需要对这些特性和行为进行特殊处理。
if (udev->wusb == 0 && le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201):这是检查设备是否符合某些条件。udev->wusb表示设备是否是无线USB设备,0表示不是。le16_to_cpu(udev->descriptor.bcdUSB)是设备的USB协议版本号,0x0201表示USB 2.1。所以,这个条件表示设备是有线的,并且支持USB 2.1或更高版本的协议。
retval = usb_get_bos_descriptor(udev);:这是调用一个函数来获取设备的BOS(Binary Object Store)描述符。BOS描述符是USB 3.0引入的,用于描述设备支持的一些新特性。
if (!retval):这是检查获取BOS描述符是否成功。如果成功,进入以下处理流程。
udev->lpm_capable = usb_device_supports_lpm(udev);:这是调用一个函数来检查设备是否支持LPM(Link Power Management)。LPM是USB 2.0引入的一种功耗管理机制,可以让设备在不活动时进入低功耗状态。
udev->lpm_disable_count = 1;:这是设置设备的LPM禁用计数为1。这个计数用于跟踪设备是否应该进入LPM状态。
usb_set_lpm_parameters(udev);:这是调用一个函数来设置设备的LPM参数。
usb_req_set_sel(udev);:这是调用一个函数来设置设备的SEL(System Exit Latency)。SEL是USB 3.0引入的一种机制,用于控制设备从低功耗状态恢复到正常状态的最大延迟。
总的来说,这段代码的作用是检测USB设备的一些特性,并根据这些特性进行相应的设置,如获取BOS描述符,检查和设置LPM支持,设置SEL等。
2.25.1 usb_detect_quirks()函数
路径:drivers\usb\core\quirks.c
void usb_detect_quirks(struct usb_device *udev)
{
udev->quirks = usb_detect_static_quirks(udev, usb_quirk_list);
/*
* Pixart-based mice would trigger remote wakeup issue on AMD
* Yangtze chipset, so set them as RESET_RESUME flag.
*/
if (usb_amd_resume_quirk(udev))
udev->quirks |= usb_detect_static_quirks(udev,
usb_amd_resume_quirk_list);
udev->quirks ^= usb_detect_dynamic_quirks(udev);
if (udev->quirks)
dev_dbg(&udev->dev, "USB quirks for this device: %x\n",
udev->quirks);
#ifdef CONFIG_USB_DEFAULT_PERSIST
if (!(udev->quirks & USB_QUIRK_RESET))
udev->persist_enabled = 1;
#else
/* Hubs are automatically enabled for USB-PERSIST */
if (udev->descriptor.bDeviceClass == USB_CLASS_HUB)
udev->persist_enabled = 1;
#endif /* CONFIG_USB_DEFAULT_PERSIST */
}
主要用于检测USB设备的特定行为或特性(被称为"quirks"),并根据这些特性进行相应的设置。
udev->quirks = usb_detect_static_quirks(udev, usb_quirk_list);:这行代码会调用usb_detect_static_quirks函数,传入的参数是一个USB设备对象udev和一个quirk列表usb_quirk_list。这个函数的目的是检测设备是否有在usb_quirk_list列表中定义的quirks。如果有,这些quirks的标志位会被设置到设备对象的quirks字段中。
接下来的几行代码是专门针对Pixart-based鼠标在AMD Yangtze芯片组上可能触发的远程唤醒问题。如果usb_amd_resume_quirk(udev)函数判断设备有这个问题,那么会调用usb_detect_static_quirks函数,检测设备是否有在usb_amd_resume_quirk_list列表中定义的quirks,并将这些quirks的标志位设置到设备对象的quirks字段中。
udev->quirks ^= usb_detect_dynamic_quirks(udev);:这行代码会调用usb_detect_dynamic_quirks函数,检测设备是否有动态quirks。这些quirks的标志位会被异或到设备对象的quirks字段中。
如果设备有任何quirks,那么会打印一条调试信息,显示设备的quirks。
最后几行代码是关于USB设备的持久性设置。如果设备没有USB_QUIRK_RESET这个quirk,那么会启用设备的持久性。如果设备是一个USB hub,那么也会启用设备的持久性。持久性是指在USB设备断电后,设备的状态可以被保存并在重新上电后恢复。
总的来说,这段代码的作用是检测USB设备的quirks,并根据这些quirks进行相应的设置。
2.25.2 usb_device_supports_lpm()函数
int usb_device_supports_lpm(struct usb_device *udev)
{
/* Some devices have trouble with LPM */
if (udev->quirks & USB_QUIRK_NO_LPM)
return 0;
/* USB 2.1 (and greater) devices indicate LPM support through
* their USB 2.0 Extended Capabilities BOS descriptor.
*/
if (udev->speed == USB_SPEED_HIGH || udev->speed == USB_SPEED_FULL) {
if (udev->bos->ext_cap &&
(USB_LPM_SUPPORT &
le32_to_cpu(udev->bos->ext_cap->bmAttributes)))
return 1;
return 0;
}
/*
* According to the USB 3.0 spec, all USB 3.0 devices must support LPM.
* However, there are some that don't, and they set the U1/U2 exit
* latencies to zero.
*/
if (!udev->bos->ss_cap) {
dev_info(&udev->dev, "No LPM exit latency info found, disabling LPM.\n");
return 0;
}
if (udev->bos->ss_cap->bU1devExitLat == 0 &&
udev->bos->ss_cap->bU2DevExitLat == 0) {
if (udev->parent)
dev_info(&udev->dev, "LPM exit latency is zeroed, disabling LPM.\n");
else
dev_info(&udev->dev, "We don't know the algorithms for LPM for this host, disabling LPM.\n");
return 0;
}
if (!udev->parent || udev->parent->lpm_capable)
return 1;
return 0;
}
用于检测USB设备是否支持Link Power Management(LPM)。
LPM是USB 2.0和USB 3.0设备的一个特性,可以让设备在空闲时进入低功耗状态,以节省电力。
首先,如果设备有USB_QUIRK_NO_LPM这个quirk,表示设备有问题不能支持LPM,函数就直接返回0,表示设备不支持LPM。
如果设备是USB 2.1及以上版本,并且速度是高速或全速,那么会检查设备的BOS描述符的扩展功能字段ext_cap,如果这个字段的USB_LPM_SUPPORT位被设置,那么函数返回1,表示设备支持LPM。否则返回0,表示设备不支持LPM。
如果设备是USB 3.0,那么会检查设备的BOS描述符的超速功能字段ss_cap。如果这个字段不存在,或者设备的U1和U2退出延迟都被设置为0,那么函数返回0,表示设备不支持LPM,并打印一条信息。
如果设备是USB hub或者设备的父设备支持LPM,那么函数返回1,表示设备支持LPM。否则返回0,表示设备不支持LPM。
总的来说,这个函数的作用是检测USB设备是否支持LPM
2.25.3 usb_req_set_sel()函数
static int usb_req_set_sel(struct usb_device *udev)
{
struct usb_set_sel_req *sel_values;
unsigned long long u1_sel;
unsigned long long u1_pel;
unsigned long long u2_sel;
unsigned long long u2_pel;
int ret;
if (!udev->parent || udev->speed < USB_SPEED_SUPER || !udev->lpm_capable)
return 0;
/* Convert SEL and PEL stored in ns to us */
u1_sel = DIV_ROUND_UP(udev->u1_params.sel, 1000);
u1_pel = DIV_ROUND_UP(udev->u1_params.pel, 1000);
u2_sel = DIV_ROUND_UP(udev->u2_params.sel, 1000);
u2_pel = DIV_ROUND_UP(udev->u2_params.pel, 1000);
/*
* Make sure that the calculated SEL and PEL values for the link
* state we're enabling aren't bigger than the max SEL/PEL
* value that will fit in the SET SEL control transfer.
* Otherwise the device would get an incorrect idea of the exit
* latency for the link state, and could start a device-initiated
* U1/U2 when the exit latencies are too high.
*/
if (u1_sel > USB3_LPM_MAX_U1_SEL_PEL ||
u1_pel > USB3_LPM_MAX_U1_SEL_PEL ||
u2_sel > USB3_LPM_MAX_U2_SEL_PEL ||
u2_pel > USB3_LPM_MAX_U2_SEL_PEL) {
dev_dbg(&udev->dev, "Device-initiated U1/U2 disabled due to long SEL or PEL\n");
return -EINVAL;
}
/*
* usb_enable_lpm() can be called as part of a failed device reset,
* which may be initiated by an error path of a mass storage driver.
* Therefore, use GFP_NOIO.
*/
sel_values = kmalloc(sizeof *(sel_values), GFP_NOIO);
if (!sel_values)
return -ENOMEM;
sel_values->u1_sel = u1_sel;
sel_values->u1_pel = u1_pel;
sel_values->u2_sel = cpu_to_le16(u2_sel);
sel_values->u2_pel = cpu_to_le16(u2_pel);
ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
USB_REQ_SET_SEL,
USB_RECIP_DEVICE,
0, 0,
sel_values, sizeof *(sel_values),
USB_CTRL_SET_TIMEOUT);
kfree(sel_values);
if (ret > 0)
udev->lpm_devinit_allow = 1;
return ret;
}
用于发送一个Set SEL控制传输到USB设备,以便在启用设备发起的U1或U2状态之前,让设备知道从设备启动U1或U2退出到接收到主机数据包的延迟时间。
SEL(System Exit Latency)和PEL(Device Exit Latency)是USB 3.0设备的两个参数,分别表示系统退出延迟和设备退出延迟。
以下是这段代码的详细过程:
首先,检查设备是否满足发送Set SEL控制传输的条件,如果设备没有父设备,或者设备的速度不是超速,或者设备不支持低功耗模式(LPM),则直接返回0。
然后,将设备的SEL和PEL值(以纳秒为单位)转换为微秒。使用的是四舍五入的方式进行转换。
接下来,确保计算出的SEL和PEL值不大于SET SEL控制传输可以容纳的最大SEL/PEL值。否则,设备会对链接状态的退出延迟产生错误的理解,并可能在退出延迟过高时启动设备发起的U1/U2状态,这时函数返回-EINVAL错误。
然后,分配一个usb_set_sel_req结构的内存空间,用于存放SEL和PEL值。
将SEL和PEL值填充到分配的内存空间中,注意U2的SEL和PEL值需要转换为小端格式。
使用usb_control_msg函数发送一个Set SEL控制传输到设备。传输的数据是刚刚填充的SEL和PEL值。
发送完毕后,释放分配的内存空间。
如果控制传输成功,那么设置设备的lpm_devinit_allow字段为1,表示允许设备发起的低功耗模式。
最后,函数返回控制传输的结果。
所以,这个函数的主要作用是发送一个Set SEL控制传输到USB设备,以便在启用设备发起的U1或U2状态之前,让设备知道从设备启动U1或U2退出到接收到主机数据包的延迟时间。
2.26 第341-345行:通知主机控制器驱动(HCD)更新设备状态
第341-345行:主要用于在USB设备被成功连接和地址分配后,通知主机控制器驱动(HCD)更新设备状态,并设置设备的初始USB2 Link Power Management(LPM)策略。
以下是这段代码的详细过程:
retval = 0;:这行代码初始化返回值为0,表示操作成功。
if (hcd->driver->update_device) hcd->driver->update_device(hcd, udev);:这两行代码检查HCD驱动是否提供了update_device接口。如果提供了,那么就调用这个接口,将设备udev传入,通知HCD驱动更新设备状态。这通常在设备被成功连接和地址分配后进行。
hub_set_initial_usb2_lpm_policy(udev);:这行代码调用hub_set_initial_usb2_lpm_policy函数,设置设备的初始USB2 LPM策略。USB2 LPM是一种可以让设备在空闲时进入低功耗状态的技术,可以帮助节省电力。设置初始LPM策略是设备上电后的一项重要操作。
总的来说,这段代码的作用是在USB设备被成功连接和地址分配后,通知HCD驱动更新设备状态,并设置设备的初始USB2 LPM策略。
2.26.1 hub_set_initial_usb2_lpm_policy()函数
static void hub_set_initial_usb2_lpm_policy(struct usb_device *udev)
{
struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
int connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN;
if (!udev->usb2_hw_lpm_capable || !udev->bos)
return;
if (hub)
connect_type = hub->ports[udev->portnum - 1]->connect_type;
if ((udev->bos->ext_cap->bmAttributes & cpu_to_le32(USB_BESL_SUPPORT)) ||
connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) {
udev->usb2_hw_lpm_allowed = 1;
usb_enable_usb2_hardware_lpm(udev);
}
}
作用是设置USB设备的初始USB 2.0 Link Power Management(LPM)策略。LPM是一种可以让设备在空闲时进入低功耗状态的技术,可以帮助节省电力。
以下是这段代码的详细过程:
首先,检查设备是否支持硬件LPM(udev->usb2_hw_lpm_capable)以及设备是否有BOS(Binary Object Store)描述符(udev->bos)。如果设备不支持硬件LPM或者没有BOS描述符,那么函数直接返回。
然后,如果设备有父设备(也就是设备是连接到一个USB hub上),那么获取设备连接的类型(connect_type)。
接下来,检查设备的BOS描述符的扩展功能字段(ext_cap->bmAttributes)是否设置了BESL支持位(BESL是Bulk Endpoint Service Latency的缩写,表示批量端点服务延迟)。或者设备的连接类型是硬连线(USB_PORT_CONNECT_TYPE_HARD_WIRED)。
如果满足以上任一条件,那么设置设备允许硬件LPM(udev->usb2_hw_lpm_allowed = 1;),并启用设备的硬件LPM(usb_enable_usb2_hardware_lpm(udev);)。
总的来说,这个函数的作用是设置USB设备的初始USB 2.0 LPM策略。只有当设备支持BESL或者设备是硬连线时,才会启用设备的硬件LPM。
2.26.2 hub_set_initial_usb2_lpm_policy()函数
路径:drivers\usb\core\driver.c
static int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable)
{
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
int ret = -EPERM;
if (hcd->driver->set_usb2_hw_lpm) {
ret = hcd->driver->set_usb2_hw_lpm(hcd, udev, enable);
if (!ret)
udev->usb2_hw_lpm_enabled = enable;
}
return ret;
}
int usb_enable_usb2_hardware_lpm(struct usb_device *udev)
{
if (!udev->usb2_hw_lpm_capable ||
!udev->usb2_hw_lpm_allowed ||
udev->usb2_hw_lpm_enabled)
return 0;
return usb_set_usb2_hardware_lpm(udev, 1);
}
目的是设置和启用USB设备的硬件Link Power Management(LPM)。
以下是这段代码的详细过程:
usb_set_usb2_hardware_lpm函数:
首先,从USB设备的总线字段获取到主机控制器驱动(Host Controller Driver,HCD)。
然后,检查HCD驱动是否提供了set_usb2_hw_lpm接口。如果提供了,那么就调用这个接口,将HCD、设备和启用标志传入,设置设备的硬件LPM。
如果设置成功,那么更新设备的usb2_hw_lpm_enabled字段,表示硬件LPM已经被启用。
usb_enable_usb2_hardware_lpm函数:
首先,检查设备是否支持硬件LPM(usb2_hw_lpm_capable)、是否允许硬件LPM(usb2_hw_lpm_allowed)以及硬件LPM是否已经被启用(usb2_hw_lpm_enabled)。如果任一条件不满足,那么函数直接返回0。
如果满足所有条件,那么调用usb_set_usb2_hardware_lpm函数,启用设备的硬件LPM。
总的来说,这段代码的作用是设置和启用USB设备的硬件LPM。硬件LPM是一种可以让设备在空闲时进入低功耗状态的技术,可以帮助节省电力。
路径:drivers\usb\host\xhci.c
static const struct hc_driver xhci_hc_driver = {
.description = "xhci-hcd",
.product_desc = "xHCI Host Controller",
...........
.update_device = xhci_update_device,
};
static int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
struct usb_device *udev, int enable)
{
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct xhci_port **ports;
__le32 __iomem *pm_addr, *hlpm_addr;
u32 pm_val, hlpm_val, field;
unsigned int port_num;
unsigned long flags;
int hird, exit_latency;
int ret;
if (xhci->quirks & XHCI_HW_LPM_DISABLE)
return -EPERM;
if (hcd->speed >= HCD_USB3 || !xhci->hw_lpm_support ||
!udev->lpm_capable)
return -EPERM;
if (!udev->parent || udev->parent->parent ||
udev->descriptor.bDeviceClass == USB_CLASS_HUB)
return -EPERM;
if (udev->usb2_hw_lpm_capable != 1)
return -EPERM;
spin_lock_irqsave(&xhci->lock, flags);
ports = xhci->usb2_rhub.ports;
port_num = udev->portnum - 1;
pm_addr = ports[port_num]->addr + PORTPMSC;
pm_val = readl(pm_addr);
hlpm_addr = ports[port_num]->addr + PORTHLPMC;
xhci_dbg(xhci, "%s port %d USB2 hardware LPM\n",
enable ? "enable" : "disable", port_num + 1);
if (enable) {
/* Host supports BESL timeout instead of HIRD */
if (udev->usb2_hw_lpm_besl_capable) {
/* if device doesn't have a preferred BESL value use a
* default one which works with mixed HIRD and BESL
* systems. See XHCI_DEFAULT_BESL definition in xhci.h
*/
field = le32_to_cpu(udev->bos->ext_cap->bmAttributes);
if ((field & USB_BESL_SUPPORT) &&
(field & USB_BESL_BASELINE_VALID))
hird = USB_GET_BESL_BASELINE(field);
else
hird = udev->l1_params.besl;
exit_latency = xhci_besl_encoding[hird];
spin_unlock_irqrestore(&xhci->lock, flags);
ret = xhci_change_max_exit_latency(xhci, udev,
exit_latency);
if (ret < 0)
return ret;
spin_lock_irqsave(&xhci->lock, flags);
hlpm_val = xhci_calculate_usb2_hw_lpm_params(udev);
writel(hlpm_val, hlpm_addr);
/* flush write */
readl(hlpm_addr);
} else {
hird = xhci_calculate_hird_besl(xhci, udev);
}
pm_val &= ~PORT_HIRD_MASK;
pm_val |= PORT_HIRD(hird) | PORT_RWE | PORT_L1DS(udev->slot_id);
writel(pm_val, pm_addr);
pm_val = readl(pm_addr);
pm_val |= PORT_HLE;
writel(pm_val, pm_addr);
/* flush write */
readl(pm_addr);
} else {
pm_val &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK | PORT_L1DS_MASK);
writel(pm_val, pm_addr);
/* flush write */
readl(pm_addr);
if (udev->usb2_hw_lpm_besl_capable) {
spin_unlock_irqrestore(&xhci->lock, flags);
xhci_change_max_exit_latency(xhci, udev, 0);
readl_poll_timeout(ports[port_num]->addr, pm_val,
(pm_val & PORT_PLS_MASK) == XDEV_U0,
100, 10000);
return 0;
}
}
spin_unlock_irqrestore(&xhci->lock, flags);
return 0;
}
代码位于Linux内核的xHCI(USB 3.0主机控制器接口)驱动中,定义了一个函数xhci_set_usb2_hardware_lpm,用于设置和启用USB 2.0设备的硬件Link Power Management(LPM)。
以下是这段代码的详细过程:
首先,函数检查了一系列的条件,包括xHCI是否支持硬件LPM,设备是否支持LPM,设备是否是USB 2.0设备,设备是否是直接连接到根集线器的设备,以及设备是否支持硬件LPM。如果任一条件不满足,函数返回-EPERM错误。
接下来,函数获取设备连接的端口的地址和端口号,然后锁定xHCI的锁,防止并发访问。
如果启用硬件LPM,函数会检查设备是否支持BESL(Bulk Endpoint Service Latency,批量端点服务延迟)。如果支持,函数会计算出设备的退出延迟,并调用xhci_change_max_exit_latency函数设置设备的最大退出延迟。然后,函数会计算设备的LPM参数,并写入到端口的PORTHLPMC寄存器中。
如果设备不支持BESL,函数会计算设备的HIRD参数。
然后,函数会设置端口的PORTPMSC寄存器,启用硬件LPM。
如果禁用硬件LPM,函数会清除端口的PORTPMSC寄存器的相关位,禁用硬件LPM。如果设备支持BESL,函数会调用xhci_change_max_exit_latency函数设置设备的最大退出延迟为0。
最后,函数解锁xHCI的锁,返回0表示操作成功。
总的来说,这个函数的作用是设置和启用USB 2.0设备的硬件LPM。硬件LPM是一种可以让设备在空闲时进入低功耗状态的技术,可以帮助节省电力。
2.27 第346-351行:错误处理部分
第346-351行:是一个错误处理部分,标记为fail:,当某些操作失败时,会跳转到这里进行错误处理。
以下是这段代码的详细过程:
if (retval) {…}:这行代码检查retval变量的值。retval通常用于存储函数的返回值,如果它不为0,表示之前的某个操作失败。
hub_port_disable(hub, port1, 0);:这行代码调用hub_port_disable函数,禁用指定的USB端口。这是因为如果之前的操作失败,可能会导致USB设备处于不稳定状态,为了防止对系统造成影响,需要禁用这个端口。
update_devnum(udev, devnum);:这行代码调用update_devnum函数,更新USB设备的设备号。这是为了在断开设备时能正确处理设备。
return retval;:这行代码将错误码返回给调用者,告知调用者之前的操作失败,并提供失败的原因。
总的来说,这段代码的作用是当某些操作失败时,进行错误处理,包括禁用出问题的USB端口和更新USB设备的设备号。
三、总结
hub_port_init()主要是在 配置 端口的参数,重置设备并获取设备描述符、根据设备的速度(例如,超高速、高速、全速或低速)设置端点0的最大数据包大小、检查端点0的最大数据包大小是否有效、获取设备描述符,并检测任何特殊的设备特性、通知主机控制器驱动程序我们已经连接并寻址了一个设备等操作;
以上是对hub_port_init()的分析。后面将继续分析 usb_new_device()函数,则是在USB设备枚举后创建一个具体的USB设备 并添加到设备管理中。后面文章继续分析。