<kernel>kernel 6.4 USB-之-hub_event()分析

<kernel>kernel 6.4 USB-之-hub_event()分析

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()分析
<kernel>kernel 6.4 USB-之-usb_new_device()分析

本文是基于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设备,并允许设备自动挂起。

二、 hub_event()函数

下面我们来看下源码,

路径: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)收集。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

waterfxw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值