<kernel>kernel 6.4 USB-之-port_event()分析
kernel 6.4 USB系列文章如下:
<kernel>kernel 6.4 USB-之-hub_event()分析
<kernel>kernel 6.4 USB-之-port_event()分析
本文是基于linux kernel 6.4版本内核分析;源码下载路径:linux kernel
一、前言
在前一篇文章分析了hub_event()函数,之后hub_event()函数会进一步调用 port_event函数;
这个函数port_event的主要作用是处理USB集线器(Hub)上某个特定端口的事件。
具体来看,代码的主要功能如下:
首先,它会检查端口的变化位(change bits)。如果端口的连接状态(connection)或使能状态(enable)发生变化,它会清除相应的端口特性(port feature)。如果端口被禁用并且没有连接变化,它会尝试重新启用端口并将连接更改标志设置为1。
然后,它会检查是否发生了过流(overcurrent)变化。如果过流变化发生,它会清除过流特性,并尝试打开集线器的电源。如果存在过流条件,它会打印一条错误信息。
接着,它会检查端口是否发生了复位(reset)变化。如果复位变化发生,它会清除复位特性。
然后,它会检查端口是否需要进行热复位(warm reset)。如果需要,它会进行一系列的操作来进行热复位,包括尝试复位端口、禁用端口、或者复位设备。
最后,如果连接状态发生变化,它会调用hub_port_connect_change函数来处理连接状态的变化。
所以,这个函数的主要作用是处理USB集线器上某个特定端口的事件,包括连接变化、使能变化、过流变化、复位变化等,以及对这些变化进行相应的处理。
二、port_event()函数
port_event()函数内容如下:
路径:drivers\usb\core\hub.c
static void port_event(struct usb_hub *hub, int port1)
__must_hold(&port_dev->status_lock)
{
int connect_change;
struct usb_port *port_dev = hub->ports[port1 - 1];
struct usb_device *udev = port_dev->child;
struct usb_device *hdev = hub->hdev;
u16 portstatus, portchange;
int i = 0;
connect_change = test_bit(port1, hub->change_bits);
clear_bit(port1, hub->event_bits);
clear_bit(port1, hub->wakeup_bits);
if (usb_hub_port_status(hub, port1, &portstatus, &portchange) < 0)
return;
if (portchange & USB_PORT_STAT_C_CONNECTION) {
usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION);
connect_change = 1;
}
if (portchange & USB_PORT_STAT_C_ENABLE) {
if (!connect_change)
dev_dbg(&port_dev->dev, "enable change, status %08x\n",
portstatus);
usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
/*
* EM interference sometimes causes badly shielded USB devices
* to be shutdown by the hub, this hack enables them again.
* Works at least with mouse driver.
*/
if (!(portstatus & USB_PORT_STAT_ENABLE)
&& !connect_change && udev) {
dev_err(&port_dev->dev, "disabled by hub (EMI?), re-enabling...\n");
connect_change = 1;
}
}
if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
u16 status = 0, unused;
port_dev->over_current_count++;
port_over_current_notify(port_dev);
dev_dbg(&port_dev->dev, "over-current change #%u\n",
port_dev->over_current_count);
usb_clear_port_feature(hdev, port1,
USB_PORT_FEAT_C_OVER_CURRENT);
msleep(100); /* Cool down */
hub_power_on(hub, true);
usb_hub_port_status(hub, port1, &status, &unused);
if (status & USB_PORT_STAT_OVERCURRENT)
dev_err(&port_dev->dev, "over-current condition\n");
}
if (portchange & USB_PORT_STAT_C_RESET) {
dev_dbg(&port_dev->dev, "reset change\n");
usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_RESET);
}
if ((portchange & USB_PORT_STAT_C_BH_RESET)
&& hub_is_superspeed(hdev)) {
dev_dbg(&port_dev->dev, "warm reset change\n");
usb_clear_port_feature(hdev, port1,
USB_PORT_FEAT_C_BH_PORT_RESET);
}
if (portchange & USB_PORT_STAT_C_LINK_STATE) {
dev_dbg(&port_dev->dev, "link state change\n");
usb_clear_port_feature(hdev, port1,
USB_PORT_FEAT_C_PORT_LINK_STATE);
}
if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) {
dev_warn(&port_dev->dev, "config error\n");
usb_clear_port_feature(hdev, port1,
USB_PORT_FEAT_C_PORT_CONFIG_ERROR);
}
/* skip port actions that require the port to be powered on */
if (!pm_runtime_active(&port_dev->dev))
return;
/* skip port actions if ignore_event and early_stop are true */
if (port_dev->ignore_event && port_dev->early_stop)
return;
if (hub_handle_remote_wakeup(hub, port1, portstatus, portchange))
connect_change = 1;
/*
* Avoid trying to recover a USB3 SS.Inactive port with a warm reset if
* the device was disconnected. A 12ms disconnect detect timer in
* SS.Inactive state transitions the port to RxDetect automatically.
* SS.Inactive link error state is common during device disconnect.
*/
while (hub_port_warm_reset_required(hub, port1, portstatus)) {
if ((i++ < DETECT_DISCONNECT_TRIES) && udev) {
u16 unused;
msleep(20);
usb_hub_port_status(hub, port1, &portstatus, &unused);
dev_dbg(&port_dev->dev, "Wait for inactive link disconnect detect\n");
continue;
} else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)
|| udev->state == USB_STATE_NOTATTACHED) {
dev_dbg(&port_dev->dev, "do warm reset, port only\n");
if (hub_port_reset(hub, port1, NULL,
HUB_BH_RESET_TIME, true) < 0)
hub_port_disable(hub, port1, 1);
} else {
dev_dbg(&port_dev->dev, "do warm reset, full device\n");
usb_unlock_port(port_dev);
usb_lock_device(udev);
usb_reset_device(udev);
usb_unlock_device(udev);
usb_lock_port(port_dev);
connect_change = 0;
}
break;
}
if (connect_change)
hub_port_connect_change(hub, port1, portstatus, portchange);
}
下面就对port_event()函数内容详细分析:
2.1 第6行
第6行:从集线器的ports数组中获取第port1个端口的struct usb_port结构体的指针,并将这个指针赋值给port_dev变量。这里的port1是端口的编号,编号从1开始,所以在数组中的索引需要减1。struct usb_port结构体代表了USB集线器上的一个端口,它包含了端口的状态、端口上连接的设备等信息。通过获取struct usb_port结构体的指针,我们可以方便地操作和管理集线器上的端口。
第7行:从port_dev结构体的child成员中获取连接在这个端口上的USB设备的struct usb_device结构体的指针,并将这个指针赋值给udev变量。struct usb_device结构体代表了一个USB设备,它包含了设备的状态、设备的配置等信息。通过获取struct usb_device结构体的指针,我们可以方便地操作和管理USB设备。
2.2 第12-14行:检查USB集线器(Hub)上的特定端口是否发生了连接变化
第12-14行:主要作用是检查USB集线器(Hub)上的特定端口是否发生了连接变化,并清除该端口的事件位和唤醒位。
具体来看,代码的作用如下:
(1)connect_change = test_bit(port1, hub->change_bits);:这行代码检查集线器的变化位中第port1个位是否被设置。如果被设置,那么表示该端口发生了连接变化。这个结果被赋值给connect_change变量。
(2)clear_bit(port1, hub->event_bits);:这行代码清除集线器的事件位中第port1个位。这通常表示已经处理了该端口的事件。
(3)clear_bit(port1, hub->wakeup_bits);:这行代码清除集线器的唤醒位中第port1个位。这通常表示已经处理了该端口的唤醒事件。
所以,这段代码的主要作用是检查USB集线器上的特定端口是否发生了连接变化,并清除该端口的事件位和唤醒位。
2.3 第16-17行:获取USB集线器(Hub)上特定端口的状态和状态变化
第16-17行:主要作用是获取USB集线器(Hub)上特定端口的状态和状态变化。
(1)if (usb_hub_port_status(hub, port1, &portstatus, &portchange) < 0) {…}:这个if语句调用usb_hub_port_status函数来获取集线器上第port1个端口的状态和状态变化。portstatus和portchange是输出参数,用于保存端口的状态和状态变化。如果usb_hub_port_status函数返回值小于0,表示获取状态失败,那么就直接返回,不进行后续的操作。
(2)usb_hub_port_status函数通常用于读取集线器上的端口状态寄存器,获取端口的当前状态和状态变化。端口的状态包括连接状态、使能状态、复位状态等;状态变化包括连接变化、使能变化、复位变化等。
所以,这段代码的主要作用是获取USB集线器上特定端口的状态和状态变化,如果获取状态失败,那么就直接返回,不进行后续的操作。
2.3.1 usb_hub_port_status()函数
usb_hub_port_status()函数内容如下:
static int hub_ext_port_status(struct usb_hub *hub, int port1, int type,
u16 *status, u16 *change, u32 *ext_status)
{
int ret;
int len = 4;
if (type != HUB_PORT_STATUS)
len = 8;
mutex_lock(&hub->status_mutex);
ret = get_port_status(hub->hdev, port1, &hub->status->port, type, len);
if (ret < len) {
if (ret != -ENODEV)
dev_err(hub->intfdev,
"%s failed (err = %d)\n", __func__, ret);
if (ret >= 0)
ret = -EIO;
} else {
*status = le16_to_cpu(hub->status->port.wPortStatus);
*change = le16_to_cpu(hub->status->port.wPortChange);
if (type != HUB_PORT_STATUS && ext_status)
*ext_status = le32_to_cpu(
hub->status->port.dwExtPortStatus);
ret = 0;
}
mutex_unlock(&hub->status_mutex);
return ret;
}
int usb_hub_port_status(struct usb_hub *hub, int port1,
u16 *status, u16 *change)
{
return hub_ext_port_status(hub, port1, HUB_PORT_STATUS,
status, change, NULL);
}
2.4 第19-22行:检查USB集线器(Hub)上特定端口是否发生了连接变化
第19-22行:主要作用是检查USB集线器(Hub)上特定端口是否发生了连接变化,并清除连接变化标志。
具体来看,代码的作用如下:
(1) if (portchange & USB_PORT_STAT_C_CONNECTION) {…}:这个if语句检查portchange中的连接变化位是否被设置。USB_PORT_STAT_C_CONNECTION是一个位掩码,用于在portchange中选出连接变化位。如果连接变化位被设置,那么表示该端口的连接状态发生了变化,就执行后面的代码块。
(2) usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION);:这行代码清除集线器上第port1个端口的连接变化特性。这通常表示已经处理了该端口的连接变化。
2.4.1 usb_clear_port_feature()
/* USB 2.0规范第11.24.2.2节 */
int usb_clear_port_feature(struct usb_device *hdev, int port1, int feature)
{
return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port1,
NULL, 0, 1000);
}
(3) connect_change = 1;:这行代码将connect_change变量设置为1,表示该端口的连接状态发生了变化。
所以,这段代码的主要作用是检查USB集线器上特定端口是否发生了连接变化,并清除连接变化标志。
2.5 第24-40行:处理USB端口的状态变化
第24-40行:主要处理USB端口的状态变化,特别是当USB端口的使能状态发生变化时的情况。
(1) 首先,通过检查portchange & USB_PORT_STAT_C_ENABLE来判断USB端口的使能状态是否发生变化。如果变化了,那么进入该if语句块。
(2) 然后,如果connect_change为假(表示没有连接变化),则打印一条调试信息,显示当前端口的状态。
(3) 接着,调用usb_clear_port_feature函数来清除端口的使能状态变化特性。
在注释中提到,由于电磁干扰,一些屏蔽不良的USB设备可能会被集线器关闭。因此,这段代码的下一部分就是处理这种情况的。如果端口没有被使能,并且没有连接变化,且udev不为空(表示有设备连接),那么打印一条错误信息,并将connect_change设置为1,表示需要重新使能设备。
2.6 第42-56行:处理USB端口的过流状态
第42-56行:主要处理USB端口的过流(over-current)状态变化。
(1) 首先,通过检查portchange & USB_PORT_STAT_C_OVERCURRENT来判断USB端口的过流状态是否发生变化。如果变化了,那么进入该if语句块。
(2) 然后,port_dev->over_current_count++用于累计过流次数。
(3) port_over_current_notify(port_dev)函数用来通知相关设备或系统USB端口过流。
2.6.1 port_over_current_notify(port_dev)函数
/* Handle notifying userspace about hub over-current events */
static void port_over_current_notify(struct usb_port *port_dev)
{
char *envp[3] = { NULL, NULL, NULL };
struct device *hub_dev;
char *port_dev_path;
sysfs_notify(&port_dev->dev.kobj, NULL, "over_current_count");
hub_dev = port_dev->dev.parent;
if (!hub_dev)
return;
port_dev_path = kobject_get_path(&port_dev->dev.kobj, GFP_KERNEL);
if (!port_dev_path)
return;
envp[0] = kasprintf(GFP_KERNEL, "OVER_CURRENT_PORT=%s", port_dev_path);
if (!envp[0])
goto exit;
envp[1] = kasprintf(GFP_KERNEL, "OVER_CURRENT_COUNT=%u",
port_dev->over_current_count);
if (!envp[1])
goto exit;
kobject_uevent_env(&hub_dev->kobj, KOBJ_CHANGE, envp);
exit:
kfree(envp[1]);
kfree(envp[0]);
kfree(port_dev_path);
}
首先,sysfs_notify(&port_dev->dev.kobj, NULL, "over_current_count")用于通知用户空间,USB端口的过流计数已经发生变化。
然后,获取USB端口所在的集线器设备。如果集线器设备不存在,则直接返回。
接着,获取USB端口设备在sysfs文件系统中的路径。如果获取失败,则直接返回。
然后,构造两个环境变量,一个是表示过流的USB端口的路径,一个是表示过流次数。这两个环境变量将会被发送到用户空间。
kobject_uevent_env(&hub_dev->kobj, KOBJ_CHANGE, envp)用于发送一个kobject事件,通知用户空间USB端口过流。
最后,释放之前分配的内存。
总结起来,这段代码的主要作用就是当USB端口过流时,通知用户空间进行相应的处理。
(4) 接着,打印一条调试信息,显示当前过流变化的次数。
(5) usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_OVER_CURRENT)函数用来清除端口的过流状态变化特性。
(6) msleep(100)函数让当前线程暂停100毫秒,这是为了让USB端口"冷却"下来。
(7) hub_power_on(hub, true)函数用来打开集线器的电源。
2.6.2 hub_power_on(hub, true)函数
static void hub_power_on(struct usb_hub *hub, bool do_delay)
{
int port1;
/*启用每个端口的电源。一些集线器的描述符中保留了LPSM(>2)值,
即使它们是USB 2.0集线器。有些集线器不实现端口电源切换,
而只是模拟它。在所有情况下,除非我们将这些消息发送到集线器,
否则端口都不会工作*/
if (hub_is_port_power_switchable(hub))
dev_dbg(hub->intfdev, "enabling power on all ports\n");
else
dev_dbg(hub->intfdev, "trying to enable port power on "
"non-switchable hub\n");
for (port1 = 1; port1 <= hub->hdev->maxchild; port1++)
if (test_bit(port1, hub->power_bits))
set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER);
else
usb_clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_POWER);
if (do_delay)
msleep(hub_power_on_good_delay(hub));
}
首先,通过hub_is_port_power_switchable(hub)函数判断集线器的端口是否支持电源切换。如果支持,就打印一条调试信息;如果不支持,也打印一条调试信息,但是信息内容不同。
然后,遍历集线器的所有端口。对于每个端口,如果power_bits中对应的位被设置,那么就调用set_port_feature函数打开端口的电源;否则,调用usb_clear_port_feature函数关闭端口的电源。
最后,如果do_delay参数为真,那么就调用msleep函数暂停一段时间。暂停的时间由hub_power_on_good_delay(hub)函数计算得出。
总的来说,这段代码的作用就是打开USB集线器的所有端口的电源。如果集线器的端口不支持电源切换,那么尝试打开电源可能会失败,但是这段代码仍然会尝试打开电源。
(8) usb_hub_port_status(hub, port1, &status, &unused)函数用来获取端口的状态。
最后,如果端口仍然处于过流状态,那么打印一条错误信息。整体就是判断端口过流,通知系统,并清理端口状态重启端口的供电,然后重新获取状态,仍然过流则输出错误log。
2.6.3 usb_hub_port_status(hub, port1, &status, &unused)函数
int usb_hub_port_status(struct usb_hub *hub, int port1,u16 *status, u16 *change)
-->>hub_ext_port_status(hub, port1, HUB_PORT_STATUS,status, change, NULL);
------->>get_port_status(hub->hdev, port1, &hub->status->port, type, len);
/*
*USB 2.0规范第11.24.2.7节
*USB 3.1考虑使用wValue和wLength字段,规范第10.16.2.6节
*/
static int get_port_status(struct usb_device *hdev, int port1,
void *data, u16 value, u16 length)
{
int i, status = -ETIMEDOUT;
for (i = 0; i < USB_STS_RETRIES &&
(status == -ETIMEDOUT || status == -EPIPE); i++) {
status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, value,
port1, data, length, USB_STS_TIMEOUT);
}
return status;
}
//就是从设备中获取状态信息。
2.7 第58-61行:处理USB端口复位状态
第58-61行:作用是处理USB端口复位状态的变化。
首先,通过检查portchange & USB_PORT_STAT_C_RESET来判断USB端口的复位状态是否发生变化。如果变化了,那么进入该if语句块。
然后,打印一条调试信息,显示"reset change"。
最后,调用usb_clear_port_feature函数来清除端口的复位状态变化特性。
总的来说,这段代码的作用就是当USB端口的复位状态发生变化时,清除该状态变化,并打印一条调试信息。
2.8 第62-67行:处理超高速(superspeed)USB端口的热复位
第62-67行:主要作用是处理超高速(superspeed)USB端口的热复位(warm reset)状态变化。
(1) 首先,通过portchange & USB_PORT_STAT_C_BH_RESET判断USB端口的热复位状态是否发生变化,并通过hub_is_superspeed(hdev)判断该USB设备是否支持超高速模式。如果这两个条件都满足,那么进入if语句块。
static inline int hub_is_superspeed(struct usb_device *hdev)
{
return hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS;
}
(2) 然后,打印一条调试信息,显示"warm reset change"。
(3) 最后,调用usb_clear_port_feature函数来清除端口的热复位状态变化特性。
总的来说,这段代码的作用就是当支持超高速模式的USB端口的热复位状态发生变化时,清除该状态变化,并打印一条调试信息。。
2.9 第68-72行: 处理USB端口链路状态(Link State)的变化
第68-72行:主要作用是处理USB端口链路状态(Link State)的变化。
首先,通过portchange & USB_PORT_STAT_C_LINK_STATE判断USB端口的链路状态是否发生变化。如果发生变化,那么进入if语句块。
然后,打印一条调试信息,显示"link state change"。
最后,调用usb_clear_port_feature函数来清除端口的链路状态变化特性。
总的来说,这段代码的作用就是当USB端口的链路状态发生变化时,清除该状态变化,并打印一条调试信息。
2.10 第73-77行:处理USB端口配置错误状态的变化
第73-77行:主要作用是处理USB端口配置错误状态的变化。
首先,通过portchange & USB_PORT_STAT_C_CONFIG_ERROR判断USB端口的配置错误状态是否发生变化。如果发生变化,那么进入if语句块。
然后,打印一条警告信息,显示"config error"。
最后,调用usb_clear_port_feature函数来清除端口的配置错误状态变化特性。
总的来说,这段代码的作用就是当USB端口的配置错误状态发生变化时,清除该状态变化,并打印一条警告信息。
2.11 第80-81行:检查USB端口设备的运行时电源管理状态
第80-81行:作用是检查USB端口设备的运行时电源管理状态。
pm_runtime_active函数会返回设备的运行时电源管理(Runtime Power Management,简称RPM)是否处于活动状态。如果处于活动状态,函数会返回true;否则,返回false。
在这段代码中,如果USB端口设备的RPM不处于活动状态,那么函数将立即返回,不会执行后续的代码。这样可以避免在设备处于低功耗状态时执行可能会唤醒设备的操作,从而节省能源。
2.11.1 pm_runtime_active函数
路径:include\linux\pm_runtime.h
/**
* pm_runtime_active - Check whether or not a device is runtime-active.
* @dev: Target device.
*
* Return %true if runtime PM is disabled for @dev or its runtime PM status is
* %RPM_ACTIVE, or %false otherwise.
*
* Note that the return value of this function can only be trusted if it is
* called under the runtime PM lock of @dev or under conditions in which
* runtime PM cannot be either disabled or enabled for @dev and its runtime PM
* status cannot change.
*/
static inline bool pm_runtime_active(struct device *dev)
{
return dev->power.runtime_status == RPM_ACTIVE
|| dev->power.disable_depth;
}
static inline bool pm_runtime_active(struct device *dev) { return true; }
定义了一个名为pm_runtime_active的内联函数,用于检查一个设备是否处于运行时电源管理(Runtime Power Management,简称RPM)的活动状态。
dev:是一个指向设备结构体的指针,表示要检查的目标设备。
dev->power.runtime_status == RPM_ACTIVE:检查设备的运行时电源管理状态是否为RPM_ACTIVE,即设备是否处于活动状态。
dev->power.disable_depth:检查设备的运行时电源管理是否被禁用。如果disable_depth的值大于0,表示运行时电源管理被禁用。
如果设备的运行时电源管理状态为RPM_ACTIVE或者设备的运行时电源管理被禁用,那么函数返回true,表示设备处于活动状态。否则,返回false,表示设备不处于活动状态。
需要注意的是,这个函数的返回值只有在持有设备的运行时电源管理锁,或者在设备的运行时电源管理无法被禁用或启用,且其状态无法改变的情况下,才能被信任。
2.12 第84-85行
第84-85行:作用是判断是否应该忽略对USB端口事件的处理。
port_dev->ignore_event:如果这个值为真,表示应该忽略对USB端口事件的处理。
port_dev->early_stop:如果这个值为真,表示应该提前停止对USB端口事件的处理。
如果这两个条件都满足,那么函数立即返回,不会执行后续的代码。
总的来说,这段代码的作用就是提供一种机制,允许在满足某些条件时跳过对USB端口事件的处理。这可以用于优化性能,或者处理特殊情况。
2.13 第87-88行
第87-88行:主要作用是处理USB集线器的远程唤醒(Remote Wakeup)事件。
hub_handle_remote_wakeup函数会检查是否有远程唤醒事件发生。如果有,那么函数会处理这个事件,并返回1;否则,返回0。
如果hub_handle_remote_wakeup函数返回1,那么就将connect_change设置为1。这表示USB设备的连接状态发生了变化,需要重新枚举设备。
总的来说,这段代码的作用就是当USB设备通过远程唤醒事件改变了其连接状态时,设置connect_change为1,以便后续的代码可以处理这个变化。
2.13.1 hub_handle_remote_wakeup函数
/* Returns 1 if there was a remote wakeup and a connect status change. */
static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
u16 portstatus, u16 portchange)
__must_hold(&port_dev->status_lock)
{
struct usb_port *port_dev = hub->ports[port - 1];
struct usb_device *hdev;
struct usb_device *udev;
int connect_change = 0;
u16 link_state;
int ret;
hdev = hub->hdev;
udev = port_dev->child;
if (!hub_is_superspeed(hdev)) {
if (!(portchange & USB_PORT_STAT_C_SUSPEND))
return 0;
usb_clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND);
} else {
link_state = portstatus & USB_PORT_STAT_LINK_STATE;
if (!udev || udev->state != USB_STATE_SUSPENDED ||
(link_state != USB_SS_PORT_LS_U0 &&
link_state != USB_SS_PORT_LS_U1 &&
link_state != USB_SS_PORT_LS_U2))
return 0;
}
if (udev) {
/* TRSMRCY = 10 msec */
msleep(10);
usb_unlock_port(port_dev);
ret = usb_remote_wakeup(udev);
usb_lock_port(port_dev);
if (ret < 0)
connect_change = 1;
} else {
ret = -ENODEV;
hub_port_disable(hub, port, 1);
}
dev_dbg(&port_dev->dev, "resume, status %d\n", ret);
return connect_change;
}
主要作用是处理USB集线器的远程唤醒事件。
首先,判断USB设备是否支持超高速模式。如果不支持超高速模式,那么检查portchange & USB_PORT_STAT_C_SUSPEND,如果该值为0,表示没有远程唤醒事件,函数返回0。如果该值不为0,表示有远程唤醒事件,使用usb_clear_port_feature函数清除该状态。
如果USB设备支持超高速模式,那么检查USB设备的状态是否为挂起状态,并且链路状态是否为U0、U1或U2。如果不满足这些条件,表示没有远程唤醒事件,函数返回0。
然后,如果USB端口上有设备连接,那么先等待10毫秒,然后尝试唤醒设备。如果唤醒失败,那么将connect_change设置为1。
如果USB端口上没有设备连接,那么禁用该端口,并将返回值设置为-ENODEV。
最后,打印一条调试信息,并返回connect_change。
总的来说,这个函数的作用就是处理USB集线器的远程唤醒事件。如果有远程唤醒事件,并且唤醒设备失败,那么函数会返回1,表示设备的连接状态发生了变化。
2.14 第96-120行:处理USB3超速(Superspeed)端口热复位
第96-120行:主要作用是处理USB3超速(Superspeed)端口在SS.Inactive状态下需要进行热复位(warm reset)的情况。
(1) 首先,通过hub_port_warm_reset_required函数判断是否需要进行热复位。如果需要,就进入while循环。
(2) 在循环中,首先检查是否已经进行了多次重试(由DETECT_DISCONNECT_TRIES定义)。如果没有达到重试次数,并且udev(表示USB设备)不为空,那么就暂停20毫秒,然后重新获取端口状态,并打印一条调试信息,然后继续下一次循环。
(3) 如果达到了重试次数,或者udev为空,或者端口没有连接,或者设备处于未连接状态,那么就只对端口进行热复位,并打印一条调试信息。如果热复位失败,那么就禁用该端口。
(4) 如果以上条件都不满足,那么就对整个设备进行复位,并打印一条调试信息。复位设备需要先解锁端口,然后锁定设备,然后复位设备,然后解锁设备,最后再锁定端口。并且将connect_change设置为0,表示连接状态没有变化。
总的来说,这段代码的作用就是处理USB3超速端口在SS.Inactive状态下需要进行热复位的情况。如果设备仍然连接在端口上,那么会尝试复位整个设备;否则,只复位端口。
2.14.1 hub_port_warm_reset_required()
static bool hub_port_warm_reset_required(struct usb_hub *hub, int port1,
u16 portstatus)
{
u16 link_state;
if (!hub_is_superspeed(hub->hdev))
return false;
if (test_bit(port1, hub->warm_reset_bits))
return true;
link_state = portstatus & USB_PORT_STAT_LINK_STATE;
return link_state == USB_SS_PORT_LS_SS_INACTIVE
|| link_state == USB_SS_PORT_LS_COMP_MOD;
}
其主要作用是判断一个USB 3.0端口是否处于Inactive或Compliance Mode状态,如果是,那么需要进行热复位(warm reset)恢复。
首先,通过hub_is_superspeed(hub->hdev)判断集线器设备是否为超速(Superspeed)设备。如果不是,那么直接返回false,表示不需要热复位。
然后,通过test_bit(port1, hub->warm_reset_bits)判断指定端口是否已经标记为需要热复位。如果已经标记,那么返回true,表示需要热复位。
接着,通过link_state = portstatus & USB_PORT_STAT_LINK_STATE获取端口的链接状态。
最后,判断链接状态是否为USB_SS_PORT_LS_SS_INACTIVE或USB_SS_PORT_LS_COMP_MOD。如果是,那么返回true,表示需要热复位。
总的来说,这个函数的作用就是判断一个USB 3.0端口是否需要进行热复位。如果端口处于Inactive或Compliance Mode状态,或者已经被标记为需要热复位,那么就需要进行热复位。
2.14.2 hub_port_reset()
/* Handle port reset and port warm(BH) reset (for USB3 protocol ports) */
static int hub_port_reset(struct usb_hub *hub, int port1,
struct usb_device *udev, unsigned int delay, bool warm)
{
int i, status;
u16 portchange, portstatus;
struct usb_port *port_dev = hub->ports[port1 - 1];
int reset_recovery_time;
if (!hub_is_superspeed(hub->hdev)) {
if (warm) {
dev_err(hub->intfdev, "only USB3 hub support "
"warm reset\n");
return -EINVAL;
}
/* Block EHCI CF initialization during the port reset.
* Some companion controllers don't like it when they mix.
*/
down_read(&ehci_cf_port_reset_rwsem);
} else if (!warm) {
/*
* If the caller hasn't explicitly requested a warm reset,
* double check and see if one is needed.
*/
if (usb_hub_port_status(hub, port1, &portstatus,
&portchange) == 0)
if (hub_port_warm_reset_required(hub, port1,
portstatus))
warm = true;
}
clear_bit(port1, hub->warm_reset_bits);
/* Reset the port */
for (i = 0; i < PORT_RESET_TRIES; i++) {
status = set_port_feature(hub->hdev, port1, (warm ?
USB_PORT_FEAT_BH_PORT_RESET :
USB_PORT_FEAT_RESET));
if (status == -ENODEV) {
; /* The hub is gone */
} else if (status) {
dev_err(&port_dev->dev,
"cannot %sreset (err = %d)\n",
warm ? "warm " : "", status);
} else {
status = hub_port_wait_reset(hub, port1, udev, delay,
warm);
if (status && status != -ENOTCONN && status != -ENODEV)
dev_dbg(hub->intfdev,
"port_wait_reset: err = %d\n",
status);
}
/*
* Check for disconnect or reset, and bail out after several
* reset attempts to avoid warm reset loop.
*/
if (status == 0 || status == -ENOTCONN || status == -ENODEV ||
(status == -EBUSY && i == PORT_RESET_TRIES - 1)) {
usb_clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_RESET);
if (!hub_is_superspeed(hub->hdev))
goto done;
usb_clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_BH_PORT_RESET);
usb_clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_PORT_LINK_STATE);
if (udev)
usb_clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_CONNECTION);
/*
* If a USB 3.0 device migrates from reset to an error
* state, re-issue the warm reset.
*/
if (usb_hub_port_status(hub, port1,
&portstatus, &portchange) < 0)
goto done;
if (!hub_port_warm_reset_required(hub, port1,
portstatus))
goto done;
/*
* If the port is in SS.Inactive or Compliance Mode, the
* hot or warm reset failed. Try another warm reset.
*/
if (!warm) {
dev_dbg(&port_dev->dev,
"hot reset failed, warm reset\n");
warm = true;
}
}
dev_dbg(&port_dev->dev,
"not enabled, trying %sreset again...\n",
warm ? "warm " : "");
delay = HUB_LONG_RESET_TIME;
}
dev_err(&port_dev->dev, "Cannot enable. Maybe the USB cable is bad?\n");
done:
if (status == 0) {
if (port_dev->quirks & USB_PORT_QUIRK_FAST_ENUM)
usleep_range(10000, 12000);
else {
/* TRSTRCY = 10 ms; plus some extra */
reset_recovery_time = 10 + 40;
/* Hub needs extra delay after resetting its port. */
if (hub->hdev->quirks & USB_QUIRK_HUB_SLOW_RESET)
reset_recovery_time += 100;
msleep(reset_recovery_time);
}
if (udev) {
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
update_devnum(udev, 0);
/* The xHC may think the device is already reset,
* so ignore the status.
*/
if (hcd->driver->reset_device)
hcd->driver->reset_device(hcd, udev);
usb_set_device_state(udev, USB_STATE_DEFAULT);
}
} else {
if (udev)
usb_set_device_state(udev, USB_STATE_NOTATTACHED);
}
if (!hub_is_superspeed(hub->hdev))
up_read(&ehci_cf_port_reset_rwsem);
return status;
}
该函数用于对USB集线器的端口进行复位操作。复位操作包括常规复位和热复位两种,热复位主要针对USB3.0协议的端口。复位操作主要用于设备枚举和错误恢复。
以下是这段代码的详细过程和作用:
首先,判断是否为超速设备。如果不是,并且进行的是热复位,打印错误信息并返回。如果是超速设备,但没有明确要求进行热复位,那么检查端口状态,如果需要,设定进行热复位。
清除端口的热复位标记。
进行端口复位操作,复位操作可能需要多次尝试。在每次尝试中,首先设置端口复位特性,然后等待端口复位完成。
检查复位操作的结果。如果复位成功,或者端口已经断开连接,或者端口已经禁用,或者复位操作繁忙并且已经达到最大尝试次数,那么清除端口的复位状态变化特性,并且如果是超速设备,还需要清除端口的热复位状态变化特性和链路状态变化特性。如果端口在复位后进入了错误状态,那么需要再次进行热复位。
如果复位操作失败,那么打印错误信息并返回。
如果复位操作成功,那么等待一段时间以便设备恢复,然后更新设备的设备号,并将设备状态设置为默认状态。
如果复位操作失败,那么将设备状态设置为未连接状态。
总的来说,这段代码的作用就是对USB集线器的端口进行复位操作,以便设备枚举和错误恢复。
2.14.2.1 update_devnum()
static void update_devnum(struct usb_device *udev, int devnum)
{
/* The address for a WUSB device is managed by wusbcore. */
if (!udev->wusb)
udev->devnum = devnum;
if (!udev->devaddr)
udev->devaddr = (u8)devnum;
}
主要作用是更新USB设备的设备编号(device number)。
以下是这段代码的详细过程和作用:
首先检查udev->wusb的值。如果udev->wusb为0,表示这不是一个无线USB(WUSB)设备,那么就将输入参数devnum赋值给udev->devnum,作为设备的新编号。
然后检查udev->devaddr的值。如果udev->devaddr为0,表示设备的地址还没有被设置,那么就将输入参数devnum的值(转换为u8类型)赋值给udev->devaddr,作为设备的新地址。
总的来说,这个函数的作用就是更新USB设备的设备编号和设备地址。对于无线USB设备,其设备编号是由无线USB核心管理的,所以这个函数不会更新无线USB设备的设备编号。
2.14.2.2 usb_set_device_state()
void usb_set_device_state(struct usb_device *udev,
enum usb_device_state new_state)
{
unsigned long flags;
int wakeup = -1;
spin_lock_irqsave(&device_state_lock, flags);
if (udev->state == USB_STATE_NOTATTACHED)
; /* do nothing */
else if (new_state != USB_STATE_NOTATTACHED) {
/* root hub wakeup capabilities are managed out-of-band
* and may involve silicon errata ... ignore them here.
*/
if (udev->parent) {
if (udev->state == USB_STATE_SUSPENDED
|| new_state == USB_STATE_SUSPENDED)
; /* No change to wakeup settings */
else if (new_state == USB_STATE_CONFIGURED)
wakeup = (udev->quirks &
USB_QUIRK_IGNORE_REMOTE_WAKEUP) ? 0 :
udev->actconfig->desc.bmAttributes &
USB_CONFIG_ATT_WAKEUP;
else
wakeup = 0;
}
if (udev->state == USB_STATE_SUSPENDED &&
new_state != USB_STATE_SUSPENDED)
udev->active_duration -= jiffies;
else if (new_state == USB_STATE_SUSPENDED &&
udev->state != USB_STATE_SUSPENDED)
udev->active_duration += jiffies;
udev->state = new_state;
} else
recursively_mark_NOTATTACHED(udev);
spin_unlock_irqrestore(&device_state_lock, flags);
if (wakeup >= 0)
device_set_wakeup_capable(&udev->dev, wakeup);
}
EXPORT_SYMBOL_GPL(usb_set_device_state);
主要用于改变USB设备的状态。
以下是这段代码的详细过程和作用:
函数首先获取device_state_lock自旋锁,以保护设备状态的修改操作。
然后检查设备当前的状态是否为USB_STATE_NOTATTACHED。如果是,函数不做任何操作。
如果新的状态new_state不为USB_STATE_NOTATTACHED,则进一步检查设备的状态。如果设备不是根集线器,并且设备当前状态或新的状态为USB_STATE_SUSPENDED,则不改变设备的唤醒设置。如果新的状态为USB_STATE_CONFIGURED,则根据设备是否支持远程唤醒(USB_QUIRK_IGNORE_REMOTE_WAKEUP)和配置描述符的唤醒属性(USB_CONFIG_ATT_WAKEUP)来设置唤醒标志wakeup。否则,将唤醒标志wakeup设置为0。
接着,如果设备当前状态为USB_STATE_SUSPENDED并且新的状态不为USB_STATE_SUSPENDED,则从设备的活动持续时间中减去当前时间。如果新的状态为USB_STATE_SUSPENDED并且设备当前状态不为USB_STATE_SUSPENDED,则向设备的活动持续时间中添加当前时间。
最后,将设备的状态设置为新的状态。
如果新的状态为USB_STATE_NOTATTACHED,则调用recursively_mark_NOTATTACHED函数将设备及其所有子设备的状态都设置为USB_STATE_NOTATTACHED。
释放device_state_lock自旋锁。
如果唤醒标志wakeup大于等于0,调用device_set_wakeup_capable函数设置设备的唤醒能力。
总的来说,这个函数的作用就是改变USB设备的状态,并在需要时更新设备的唤醒设置和活动持续时间。
2.14.3 hub_port_disable()
static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
{
struct usb_port *port_dev = hub->ports[port1 - 1];
struct usb_device *hdev = hub->hdev;
int ret = 0;
if (!hub->error) {
if (hub_is_superspeed(hub->hdev)) {
hub_usb3_port_prepare_disable(hub, port_dev);
ret = hub_set_port_link_state(hub, port_dev->portnum,
USB_SS_PORT_LS_U3);
} else {
ret = usb_clear_port_feature(hdev, port1,
USB_PORT_FEAT_ENABLE);
}
}
if (port_dev->child && set_state)
usb_set_device_state(port_dev->child, USB_STATE_NOTATTACHED);
if (ret && ret != -ENODEV)
dev_err(&port_dev->dev, "cannot disable (err = %d)\n", ret);
return ret;
}
主要用于关闭指定的USB端口。以下是这段代码的详细过程和作用:
函数首先获取指定端口的设备和集线器对象。
然后,检查集线器是否有错误。如果没有错误,那么就关闭端口。关闭端口的方式取决于集线器是否支持超速(Superspeed)模式。如果支持超速模式,那么就调用hub_usb3_port_prepare_disable函数和hub_set_port_link_state函数将端口的链接状态设置为U3(U3是USB3.0协议中的一种电源管理状态,表示端口被关闭)。如果不支持超速模式,那么就调用usb_clear_port_feature函数清除端口的使能特性,以关闭端口。
接着,如果端口上有设备连接,并且set_state参数为真,那么就将设备的状态设置为USB_STATE_NOTATTACHED,表示设备已经从端口上断开。
最后,如果关闭端口失败,并且错误码不是-ENODEV(表示设备不存在),那么就打印一条错误信息。
总的来说,这个函数的作用就是关闭指定的USB端口。如果端口上有设备连接,那么还会将设备的状态设置为已断开。
2.14.3.1 hub_usb3_port_prepare_disable()
static void hub_usb3_port_prepare_disable(struct usb_hub *hub,
struct usb_port *port_dev)
{
struct usb_device *udev = port_dev->child;
int ret;
if (udev && udev->port_is_suspended && udev->do_remote_wakeup) {
ret = hub_set_port_link_state(hub, port_dev->portnum,
USB_SS_PORT_LS_U0);
if (!ret) {
msleep(USB_RESUME_TIMEOUT);
ret = usb_disable_remote_wakeup(udev);
}
if (ret)
dev_warn(&udev->dev,
"Port disable: can't disable remote wake\n");
udev->do_remote_wakeup = 0;
}
}
主要用于为关闭USB3.0端口做准备工作,特别是确保关闭端口的远程唤醒功能。
以下是这段代码的详细过程和作用:
函数首先检查端口上是否有设备连接,并且设备是否处于挂起状态,以及设备的远程唤醒功能是否已经打开。如果这三个条件都满足,那么就进入if语句块。
在if语句块中,首先调用hub_set_port_link_state函数将端口的链接状态设置为U0(U0是USB3.0协议中的一种链接状态,表示端口处于活动状态)。然后等待一段时间(由USB_RESUME_TIMEOUT定义),以便设备从挂起状态恢复。
接着,调用usb_disable_remote_wakeup函数关闭设备的远程唤醒功能。如果关闭远程唤醒功能失败,那么就打印一条警告信息。
最后,无论关闭远程唤醒功能是否成功,都将设备的do_remote_wakeup标志设置为0,表示设备的远程唤醒功能已经关闭。
总的来说,这个函数的作用就是为关闭USB3.0端口做准备工作,特别是确保关闭端口的远程唤醒功能。这是因为在USB3.0协议中,端口的远程唤醒功能需要在端口被关闭之前关闭。
2.14.3.2 hub_set_port_link_state()
static int hub_set_port_link_state(struct usb_hub *hub, int port1,
unsigned int link_status)
{
return set_port_feature(hub->hdev,
port1 | (link_status << 3),
USB_PORT_FEAT_LINK_STATE);
}
主要用于设置USB集线器端口的链路状态。
以下是这段代码的详细过程和作用:
函数接收三个参数:一个指向usb_hub结构体的指针hub,表示要操作的USB集线器;一个整数port1,表示要操作的端口号;一个无符号整数link_status,表示要设置的链路状态。
在函数中,首先通过移位操作(link_status << 3)将link_status的值左移3位。然后,将移位后的link_status的值和port1的值进行按位或操作。这样可以将port1的值和link_status的值合并到一个16位的整数中。
最后,调用set_port_feature函数,将合并后的值和USB_PORT_FEAT_LINK_STATE作为参数传递给set_port_feature函数。set_port_feature函数会将USB_PORT_FEAT_LINK_STATE特性设置到指定的端口上,从而改变端口的链路状态。
总的来说,这个函数的作用就是设置USB集线器端口的链路状态。这是通过调用set_port_feature函数实现的,该函数会将指定的特性设置到端口上。
2.14.4 usb_reset_device()
int usb_reset_device(struct usb_device *udev)
{
int ret;
int i;
unsigned int noio_flag;
struct usb_port *port_dev;
struct usb_host_config *config = udev->actconfig;
struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
if (udev->state == USB_STATE_NOTATTACHED) {
dev_dbg(&udev->dev, "device reset not allowed in state %d\n",
udev->state);
return -EINVAL;
}
if (!udev->parent) {
/* this requires hcd-specific logic; see ohci_restart() */
dev_dbg(&udev->dev, "%s for root hub!\n", __func__);
return -EISDIR;
}
if (udev->reset_in_progress)
return -EINPROGRESS;
udev->reset_in_progress = 1;
port_dev = hub->ports[udev->portnum - 1];
/*
* Don't allocate memory with GFP_KERNEL in current
* context to avoid possible deadlock if usb mass
* storage interface or usbnet interface(iSCSI case)
* is included in current configuration. The easist
* approach is to do it for every device reset,
* because the device 'memalloc_noio' flag may have
* not been set before reseting the usb device.
*/
noio_flag = memalloc_noio_save();
/* Prevent autosuspend during the reset */
usb_autoresume_device(udev);
if (config) {
for (i = 0; i < config->desc.bNumInterfaces; ++i) {
struct usb_interface *cintf = config->interface[i];
struct usb_driver *drv;
int unbind = 0;
if (cintf->dev.driver) {
drv = to_usb_driver(cintf->dev.driver);
if (drv->pre_reset && drv->post_reset)
unbind = (drv->pre_reset)(cintf);
else if (cintf->condition ==
USB_INTERFACE_BOUND)
unbind = 1;
if (unbind)
usb_forced_unbind_intf(cintf);
}
}
}
usb_lock_port(port_dev);
ret = usb_reset_and_verify_device(udev);
usb_unlock_port(port_dev);
if (config) {
for (i = config->desc.bNumInterfaces - 1; i >= 0; --i) {
struct usb_interface *cintf = config->interface[i];
struct usb_driver *drv;
int rebind = cintf->needs_binding;
if (!rebind && cintf->dev.driver) {
drv = to_usb_driver(cintf->dev.driver);
if (drv->post_reset)
rebind = (drv->post_reset)(cintf);
else if (cintf->condition ==
USB_INTERFACE_BOUND)
rebind = 1;
if (rebind)
cintf->needs_binding = 1;
}
}
/* If the reset failed, hub_wq will unbind drivers later */
if (ret == 0)
usb_unbind_and_rebind_marked_interfaces(udev);
}
usb_autosuspend_device(udev);
memalloc_noio_restore(noio_flag);
udev->reset_in_progress = 0;
return ret;
}
EXPORT_SYMBOL_GPL(usb_reset_device);
主要用于对USB设备进行复位操作。
以下是这段代码的详细过程和作用:
函数首先检查设备是否处于USB_STATE_NOTATTACHED状态。如果是,那么打印一条调试信息并返回错误。
接着,如果设备是根集线器,那么打印一条调试信息并返回错误。
如果设备已经在进行复位操作,那么返回-EINPROGRESS,表示复位操作正在进行中。
然后,获取设备所连接的端口和集线器。
为了避免在当前上下文中分配内存可能导致的死锁,保存并改变内存分配的标志。
调用usb_autoresume_device函数,防止设备在复位操作期间自动挂起。
如果设备有活动的配置,那么遍历配置中的所有接口,对每个接口进行以下操作:
如果接口已经绑定到驱动,并且驱动提供了pre_reset和post_reset回调,那么调用pre_reset回调,并根据回调的返回值决定是否需要解绑接口;
如果接口已经绑定到驱动,但驱动没有提供pre_reset和post_reset回调,那么解绑接口;
如果接口没有绑定到驱动,那么不做任何操作。
对设备进行复位和验证操作。
如果设备有活动的配置,那么再次遍历配置中的所有接口,对每个接口进行以下操作:
如果接口需要绑定,并且接口已经绑定到驱动,并且驱动提供了post_reset回调,那么调用post_reset回调,并根据回调的返回值决定是否需要重新绑定接口;
如果接口需要绑定,并且接口已经绑定到驱动,但驱动没有提供post_reset回调,那么重新绑定接口;
如果接口不需要绑定,那么不做任何操作。
如果复位操作成功,那么对所有需要重新绑定的接口进行解绑和绑定操作。
调用usb_autosuspend_device函数,允许设备在空闲时自动挂起。
恢复内存分配的标志。
清除设备的复位标志。
总的来说,这个函数的作用就是对USB设备进行复位操作。在复位操作开始和结束时,会通知所有绑定到设备的驱动。如果驱动没有提供相应的回调,那么会解绑驱动并在复位操作结束后重新绑定驱动。
2.15 第122-123行:处理USB集线器端口的连接状态变化
第122-123行:主要作用是处理USB集线器端口的连接状态变化。
connect_change:这是一个布尔变量,如果其值为真,表示USB设备的连接状态发生了变化。
hub_port_connect_change:这是一个函数,用于处理USB设备的连接状态变化。它接收四个参数:一个指向usb_hub结构体的指针hub,表示要操作的USB集线器;一个整数port1,表示要操作的端口号;一个整数portstatus,表示端口的当前状态;一个整数portchange,表示端口状态的变化。
如果connect_change的值为真,那么就调用hub_port_connect_change函数,处理USB设备的连接状态变化。这可能包括设备的连接、断开连接、重新枚举等操作。
三、总结
以上是对USB port event的处理过程。后面将继续分析,如果USB device connect 发生变化的处理过程,例如USB设备插入、USB设备拔出等链接发生变化情况的处理。 由hub_port_connect_change()函数处理。请看篇分析。