<kernel>kernel 6.4 USB-之-hub_event()分析
本文是基于linux kernel 6.4版本内核分析;源码下载路径:linux kernel
本文主要分析hub_event()函数的内容;hub_event()函数是Linux内核USB子系统中的一个函数,名为hub_event。这个函数的主要作用是处理USB集线器(Hub)的事件。这些事件可能包括USB设备的连接和断开,以及USB端口状态的改变等。
这个函数首先会对USB设备进行锁定,然后检查设备的状态。如果设备已经断开连接,或者设备的状态为未连接,那么它就会进行清理工作。
如果设备处于活动状态,那么它会尝试自动恢复USB接口,然后处理端口状态的改变。对于每个端口,如果它的状态发生了改变,那么就会调用port_event函数来处理端口事件。
最后,如果集线器的状态发生了改变,那么它会获取集线器的状态,然后根据状态的改变来进行相应的操作。例如,如果发生了电源改变,它会清除电源特性,并根据电源状态来设置limited_power标志。如果发生了过流改变,那么它会清除过流特性,然后尝试打开集线器的电源,并检查是否存在过流条件。
这个函数的最后,会解锁USB设备,并允许设备自动挂起。
下面我们来看下源码,
路径:drivers\usb\core\hub.c
static void hub_event(struct work_struct *work)
{
struct usb_device *hdev;
struct usb_interface *intf;
struct usb_hub *hub;
struct device *hub_dev;
u16 hubstatus;
u16 hubchange;
int i, ret;
hub = container_of(work, struct usb_hub, events);
hdev = hub->hdev;
hub_dev = hub->intfdev;
intf = to_usb_interface(hub_dev);
kcov_remote_start_usb((u64)hdev->bus->busnum);
dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
hdev->state, hdev->maxchild,
/* NOTE: expects max 15 ports... */
(u16) hub->change_bits[0],
(u16) hub->event_bits[0]);
/* Lock the device, then check to see if we were
* disconnected while waiting for the lock to succeed. */
usb_lock_device(hdev);
if (unlikely(hub->disconnected))
goto out_hdev_lock;
/* If the hub has died, clean up after it */
if (hdev->state == USB_STATE_NOTATTACHED) {
hub->error = -ENODEV;
hub_quiesce(hub, HUB_DISCONNECT);
goto out_hdev_lock;
}
/* Autoresume */
ret = usb_autopm_get_interface(intf);
if (ret) {
dev_dbg(hub_dev, "Can't autoresume: %d\n", ret);
goto out_hdev_lock;
}
/* If this is an inactive hub, do nothing */
if (hub->quiescing)
goto out_autopm;
if (hub->error) {
dev_dbg(hub_dev, "resetting for error %d\n", hub->error);
ret = usb_reset_device(hdev);
if (ret) {
dev_dbg(hub_dev, "error resetting hub: %d\n", ret);
goto out_autopm;
}
hub->nerrors = 0;
hub->error = 0;
}
/* deal with port status changes */
for (i = 1; i <= hdev->maxchild; i++) {
struct usb_port *port_dev = hub->ports[i - 1];
if (test_bit(i, hub->event_bits)
|| test_bit(i, hub->change_bits)
|| test_bit(i, hub->wakeup_bits)) {
/*
* The get_noresume and barrier ensure that if
* the port was in the process of resuming, we
* flush that work and keep the port active for
* the duration of the port_event(). However,
* if the port is runtime pm suspended
* (powered-off), we leave it in that state, run
* an abbreviated port_event(), and move on.
*/
pm_runtime_get_noresume(&port_dev->dev);
pm_runtime_barrier(&port_dev->dev);
usb_lock_port(port_dev);
port_event(hub, i);
usb_unlock_port(port_dev);
pm_runtime_put_sync(&port_dev->dev);
}
}
/* deal with hub status changes */
if (test_and_clear_bit(0, hub->event_bits) == 0)
; /* do nothing */
else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0)
dev_err(hub_dev, "get_hub_status failed\n");
else {
if (hubchange & HUB_CHANGE_LOCAL_POWER) {
dev_dbg(hub_dev, "power change\n");
clear_hub_feature(hdev, C_HUB_LOCAL_POWER);
if (hubstatus & HUB_STATUS_LOCAL_POWER)
/* FIXME: Is this always true? */
hub->limited_power = 1;
else
hub->limited_power = 0;
}
if (hubchange & HUB_CHANGE_OVERCURRENT) {
u16 status = 0;
u16 unused;
dev_dbg(hub_dev, "over-current change\n");
clear_hub_feature(hdev, C_HUB_OVER_CURRENT);
msleep(500); /* Cool down */
hub_power_on(hub, true);
hub_hub_status(hub, &status, &unused);
if (status & HUB_STATUS_OVERCURRENT)
dev_err(hub_dev, "over-current condition\n");
}
}
out_autopm:
/* Balance the usb_autopm_get_interface() above */
usb_autopm_put_interface_no_suspend(intf);
out_hdev_lock:
usb_unlock_device(hdev);
/* Balance the stuff in kick_hub_wq() and allow autosuspend */
usb_autopm_put_interface(intf);
kref_put(&hub->kref, hub_release);
kcov_remote_stop();
}
下面就对hub_event()函数内容详细分析:
第12行:intf = to_usb_interface(hub_dev);container_of 是一个在Linux内核中广泛使用的宏,它用于获取包含特定成员的结构体的指针。container_of宏的作用是获取包含work成员的struct usb_hub类型的结构体的指针。这里的work是结构体struct usb_hub中的一个成员,而events是struct usb_hub中的另一个成员。这行代码的含义是,根据work成员的指针来获取包含这个成员的struct usb_hub结构体的指针。
一般来说,container_of宏用于在知道结构体中某个成员的指针的情况下,获取包含这个成员的整个结构体的指针。这在内核编程中非常有用,因为我们经常需要从结构体的一个成员出发,来操作整个结构体。
路径:include\linux\container_of.h
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
static_assert(__same_type(*(ptr), ((type *)0)->member) || \
__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
第15行:intf = to_usb_interface(hub_dev); to_usb_interface是一个宏,用于将一个类型为struct device *的指针转换为struct usb_interface *类型的指针。这个宏通常在需要操作USB接口时使用。 这里它的作用是将hub_dev转换为一个USB接口指针,并赋值给intf。
路径:include\linux\usb.h
#define to_usb_interface(__dev) container_of_const(__dev, struct usb_interface, dev)
路径:include\linux\container_of.h
#define container_of_const(ptr, type, member) \
_Generic(ptr, \
const typeof(*(ptr)) *: ((const type *)container_of(ptr, type, member)),\
default: ((type *)container_of(ptr, type, member)) \
)
第17行:kcov_remote_start_usb((u64)hdev->bus->busnum); kcov_remote_start_usb函数是用于启动USB设备的代码覆盖率(Code Coverage)收集的。这个函数通常在USB设备的初始化过程中被调用,用于启动对USB设备的代码覆盖率收集。
第27行:usb_lock_device(hdev);usb_lock_device函数是用于锁定USB设备的,以防止在操作设备时发生并发问题。这个函数通常在需要对USB设备进行保护的操作之前调用,例如在修改设备状态或者读写设备数据等操作之前。
路径:include\linux\usb.h
#define usb_lock_device(udev) device_lock(&(udev)->dev)
#define usb_unlock_device(udev) device_unlock(&(udev)->dev)
路径:include\linux\device.h
static inline void device_lock(struct device *dev)
{
mutex_lock(&dev->mutex);
}
static inline void device_unlock(struct device *dev)
{
mutex_unlock(&dev->mutex);
}
第28行:unlikely(hub->disconnected);unlikely是Linux内核中的一个宏,用于向编译器提供代码执行频率的提示。unlikely(expr)表示表达式expr的结果预期为假(或者说,expr预期不经常发生)。unlikely宏用于告诉编译器,hub->disconnected这个条件预期不会经常为真。也就是说,我们预期USB集线器不会经常断开连接。
这个宏的主要作用是优化代码的执行效率。通过这个宏,编译器可以将不经常执行的代码放到程序的冷路径(Cold Path)中,从而优化CPU的指令缓存使用。unlikely宏的定义在Linux内核源码的include/linux/compiler.h文件中。具体的宏定义如下:
路径:tools\include\linux\compiler.h
#ifndef likely
# define likely(x) __builtin_expect(!!(x), 1)
#endif
#ifndef unlikely
# define unlikely(x) __builtin_expect(!!(x), 0)
#endif
第32-36行:这段代码是在处理USB集线器的事件时,检查集线器的状态,如果集线器已经断开连接(即设备状态为USB_STATE_NOTATTACHED),则进行一些清理工作。将集线器的错误状态设置为-ENODEV,表示设备不存在。这是一个标准的Linux错误码,通常用于表示设备已经断开连接或者设备不存在。hub_quiesce函数的作用是停止集线器的所有活动,并释放所有与集线器相关的资源。
第39-43行:调用usb_autopm_get_interface函数尝试自动恢复USB接口intf。这个函数的返回值保存在ret变量中。如果ret为0,表示自动恢复成功;否则表示自动恢复失败,ret的值为错误码。
第46-47行:检查USB集线器(hub)是否处于静默(quiescing)状态。如果集线器处于静默状态,那么它就不会进行任何操作,并立即跳转到标签out_autopm处执行后续的代码。
第49-60行:主要作用是检查USB集线器(hub)是否有错误,如果有错误则尝试重置集线器,并清除错误状态。
ret = usb_reset_device(hdev);:这行代码调用usb_reset_device函数尝试重置集线器。这个函数的返回值保存在ret变量中。如果ret为0,表示重置成功;否则表示重置失败,ret的值为错误码。
第63-85行:主要作用是处理USB集线器(Hub)上的端口状态变化。对于每一个端口,如果它的事件位、变化位或唤醒位被设置,那么就会对这个端口执行一次端口事件处理。
(1)for (i = 1; i <= hdev->maxchild; i++) {…}:这个for循环遍历集线器上的所有端口。hdev->maxchild表示集线器上的端口数量。
(2)struct usb_port *port_dev = hub->ports[i - 1];:这行代码获取第i个端口的struct usb_port结构体的指针。
(3)if (test_bit(i, hub->event_bits) || test_bit(i, hub->change_bits) || test_bit(i, hub->wakeup_bits)) {…}:这个if语句检查端口的事件位、变化位和唤醒位是否被设置。如果任一位被设置,那么就执行后面的代码块。
(4)pm_runtime_get_noresume(&port_dev->dev);和pm_runtime_barrier(&port_dev->dev);:这两行代码的作用是防止端口在处理事件期间被挂起。如果端口正在恢复过程中,这两个函数会保持端口的活动状态;如果端口处于运行时电源管理挂起状态,这两个函数会使端口保持在这个状态。
(5)usb_lock_port(port_dev);和usb_unlock_port(port_dev);:这两行代码使用互斥锁来保护端口,防止在处理事件期间发生并发问题。
(6)port_event(hub, i);:这行代码调用port_event函数来处理端口事件。
(7)pm_runtime_put_sync(&port_dev->dev);:这行代码减少端口的运行时PM引用计数,并可能触发端口的挂起。
第88-114行:主要作用是处理USB集线器(Hub)状态的变化。当集线器的状态发生变化时,它会获取集线器的状态,并根据状态的变化来进行相应的操作。
(1)if (test_and_clear_bit(0, hub->event_bits) == 0) {…}:这个if语句检查集线器的事件位是否被设置。如果事件位没有被设置,那么就不进行任何操作。
(2)else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0) {…}:这个else if语句尝试获取集线器的状态。如果获取状态失败,那么就打印一条错误信息。
(3)if (hubchange & HUB_CHANGE_LOCAL_POWER) {…}:这个if语句检查集线器的本地电源状态是否发生变化。如果发生了变化,那么就清除电源特性,并根据电源状态来设置limited_power标志。
(4)if (hubchange & HUB_CHANGE_OVERCURRENT) {…}:这个if语句检查集线器是否发生了过流变化。如果发生了过流变化,那么就清除过流特性,然后尝试打开集线器的电源,并检查是否存在过流条件。
所以,这段代码的主要作用是处理USB集线器状态的变化。当集线器的状态发生变化时,它会获取集线器的状态,并根据状态的变化来进行相应的操作。
第116-126:这段代码主要负责在处理USB集线器事件结束后进行一些清理工作,包括释放获取的资源、解锁设备以及停止代码覆盖率(Code Coverage)收集。
代码的作用如下:
(1)usb_autopm_put_interface_no_suspend(intf);:这行代码减少intf的运行时PM引用计数,但不会触发设备的自动挂起。这个函数与之前的usb_autopm_get_interface函数对应,用于平衡引用计数。
(2)usb_unlock_device(hdev);:这行代码解锁USB设备hdev。这个函数与之前的usb_lock_device函数对应,用于释放之前获取的设备锁。
(3)usb_autopm_put_interface(intf);:这行代码减少intf的运行时PM引用计数,并可能触发设备的自动挂起。这个函数与之前的usb_autopm_get_interface函数对应,用于平衡引用计数。
(4)kref_put(&hub->kref, hub_release);:这行代码减少集线器hub的引用计数。如果引用计数变为0,那么就会调用hub_release函数来释放集线器。这个函数与之前的kref_get函数对应,用于平衡引用计数。
(5)kcov_remote_stop();:这行代码停止代码覆盖率(Code Coverage)收集。
所以,这段代码的主要作用是在处理USB集线器事件结束后进行一些清理工作,包括释放获取的资源、解锁设备以及停止代码覆盖率(Code Coverage)收集。