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

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

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_port_connect_change()函数主要用于处理USB集线器端口的连接状态变化。这个函数在以下情况下被调用:
当端口的连接状态发生变化;
当端口的使能状态发生变化(通常由电磁干扰引起);
当usb_reset_and_verify_device函数遇到改变的描述符(比如固件下载后)。

以下是这段代码的详细过程和作用:

函数首先获取指定端口的设备和集线器对象,并打印一条关于端口状态和变化的调试信息。

如果集线器有LED指示器,那么就将指定端口的LED设置为自动模式。

对于OTG设备,如果当前是B主机,那么不会重复去消抖。

如果端口上有设备连接,并且设备的状态不是USB_STATE_NOTATTACHED,那么就尝试复活设备。复活设备的方式取决于端口和设备的状态。如果端口已经使能,并且设备的描述符没有改变,那么就不做任何操作。如果端口没有使能,但设备处于挂起状态,并且设备支持持久连接,那么就尝试唤醒设备。否则,不会复活设备。

清除指定端口的状态变化标志。

如果成功复活设备,那么就直接返回。

如果没有成功复活设备,那么就调用hub_port_connect函数,处理设备的连接状态变化。

总的来说,这个函数的作用就是处理USB集线器端口的连接状态变化。这可能包括设备的连接、断开连接、重新枚举等操作。

二、hub_port_connect_change()函数

hub_port_connect_change()函数内容如下:

static void hub_port_connect_change(struct usb_hub *hub, int port1,
					u16 portstatus, u16 portchange)
		__must_hold(&port_dev->status_lock)
{
	struct usb_port *port_dev = hub->ports[port1 - 1];
	struct usb_device *udev = port_dev->child;
	struct usb_device_descriptor descriptor;
	int status = -ENODEV;
	int retval;

	dev_dbg(&port_dev->dev, "status %04x, change %04x, %s\n", portstatus,
			portchange, portspeed(hub, portstatus));

	if (hub->has_indicators) {
		set_port_led(hub, port1, HUB_LED_AUTO);
		hub->indicator[port1-1] = INDICATOR_AUTO;
	}

#ifdef	CONFIG_USB_OTG
	/* during HNP, don't repeat the debounce */
	if (hub->hdev->bus->is_b_host)
		portchange &= ~(USB_PORT_STAT_C_CONNECTION |
				USB_PORT_STAT_C_ENABLE);
#endif

	/* Try to resuscitate an existing device */
	if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
			udev->state != USB_STATE_NOTATTACHED) {
		if (portstatus & USB_PORT_STAT_ENABLE) {
			/*
			 * USB-3 connections are initialized automatically by
			 * the hostcontroller hardware. Therefore check for
			 * changed device descriptors before resuscitating the
			 * device.
			 */
			descriptor = udev->descriptor;
			retval = usb_get_device_descriptor(udev,
					sizeof(udev->descriptor));
			if (retval < 0) {
				dev_dbg(&udev->dev,
						"can't read device descriptor %d\n",
						retval);
			} else {
				if (descriptors_changed(udev, &descriptor,
						udev->bos)) {
					dev_dbg(&udev->dev,
							"device descriptor has changed\n");
					/* for disconnect() calls */
					udev->descriptor = descriptor;
				} else {
					status = 0; /* Nothing to do */
				}
			}
#ifdef CONFIG_PM
		} else if (udev->state == USB_STATE_SUSPENDED &&
				udev->persist_enabled) {
			/* For a suspended device, treat this as a
			 * remote wakeup event.
			 */
			usb_unlock_port(port_dev);
			status = usb_remote_wakeup(udev);
			usb_lock_port(port_dev);
#endif
		} else {
			/* Don't resuscitate */;
		}
	}
	clear_bit(port1, hub->change_bits);

	/* successfully revalidated the connection */
	if (status == 0)
		return;

	usb_unlock_port(port_dev);
	hub_port_connect(hub, port1, portstatus, portchange);
	usb_lock_port(port_dev);
}

下面就对hub_port_connect_change()函数内容详细分析:

2.1 第5-6行:获取USB集线器的指定端口及该端口上连接的设备

第5-6行:主要作用是获取USB集线器的指定端口及该端口上连接的设备。
以下是这段代码的详细过程和作用:
struct usb_port *port_dev = hub->ports[port1 - 1];:这行代码从hub->ports数组中获取指定端口的usb_port结构体对象。hub->ports是一个指向usb_port结构体对象的指针数组,每个元素对应集线器的一个端口。注意,端口编号port1从1开始,所以在获取端口对象时需要减1。

struct usb_device *udev = port_dev->child;:这行代码获取端口上连接的设备。port_dev->child是一个指向usb_device结构体对象的指针,表示端口上连接的设备。如果端口上没有设备连接,那么port_dev->child的值为NULL。

总的来说,这段代码的作用就是获取USB集线器的指定端口及该端口上连接的设备。

2.2 第14-17行:设置USB集线器端口的LED指示器状态

第14-17行:主要作用是设置USB集线器端口的LED指示器状态。
以下是这段代码的详细过程和作用:

if (hub->has_indicators) { … }:这个if语句块在集线器有LED指示器时执行。

set_port_led(hub, port1, HUB_LED_AUTO);:这行代码调用set_port_led函数,将指定端口的LED指示器设置为自动模式。HUB_LED_AUTO是一个宏,表示LED指示器的自动模式。

hub->indicator[port1-1] = INDICATOR_AUTO;:这行代码将指定端口的LED指示器状态保存在hub->indicator数组中。INDICATOR_AUTO是一个宏,表示LED指示器的自动模式。

总的来说,这段代码的作用就是设置USB集线器端口的LED指示器状态。如果集线器有LED指示器,那么就将指定端口的LED指示器设置为自动模式,并将这个状态保存在hub->indicator数组中。

2.2.1 set_port_led()

/*USB 2.0规范第11.24.2.7.1.10节和表11-7中有关使用端口指示器的信息*/
static void set_port_led(struct usb_hub *hub, int port1, int selector)
{
	struct usb_port *port_dev = hub->ports[port1 - 1];
	int status;

	status = set_port_feature(hub->hdev, (selector << 8) | port1,
			USB_PORT_FEAT_INDICATOR);
	dev_dbg(&port_dev->dev, "indicator %s status %d\n",
		to_led_name(selector), status);
}

主要作用是设置USB集线器端口的LED指示器状态。

以下是这段代码的详细过程和作用:

if (hub->has_indicators) { … }:这个if语句块在集线器有LED指示器时执行。

set_port_led(hub, port1, HUB_LED_AUTO);:这行代码调用set_port_led函数,将指定端口的LED指示器设置为自动模式。HUB_LED_AUTO是一个宏,表示LED指示器的自动模式。

hub->indicator[port1-1] = INDICATOR_AUTO;:这行代码将指定端口的LED指示器状态保存在hub->indicator数组中。INDICATOR_AUTO是一个宏,表示LED指示器的自动模式。

总的来说,这段代码的作用就是设置USB集线器端口的LED指示器状态。如果集线器有LED指示器,那么就将指定端口的LED指示器设置为自动模式,并将这个状态保存在hub->indicator数组中。

2.3 第19-24行:USB设备进行HNP过程中不对端口的连接和使能状态进行消抖

第19-24行:主要作用是在USB设备进行Host Negotiation Protocol (HNP)过程中,不对端口的连接和使能状态进行消抖。
以下是这段代码的详细过程和作用:

#ifdef CONFIG_USB_OTG:这是一个预处理指令,用于检查CONFIG_USB_OTG是否被定义。如果CONFIG_USB_OTG被定义,那么就编译和执行后面的代码。CONFIG_USB_OTG通常在配置内核时被定义,表示内核支持USB On-The-Go (OTG)功能。

if (hub->hdev->bus->is_b_host) { … }:这个if语句块在当前设备是B主机时执行。在USB OTG中,设备可以是A主机或B主机。A主机是提供电源的设备,B主机是接收电源的设备。在进行HNP过程时,B主机可以变成A主机,A主机可以变成B主机。

portchange &= ~(USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE);:这行代码清除portchange的连接状态变化标志和使能状态变化标志。USB_PORT_STAT_C_CONNECTION和USB_PORT_STAT_C_ENABLE是两个宏,分别表示端口的连接状态变化标志和使能状态变化标志。~操作符将它们的值取反,|操作符将它们的值进行按位或操作,&=操作符将portchange的值和它们的值进行按位与操作。

总的来说,这段代码的作用就是在USB设备进行HNP过程中,不对端口的连接和使能状态进行消抖。这是通过清除portchange的连接状态变化标志和使能状态变化标志实现的。

2.4 第26-76行: 处理USB设备复位和唤醒的逻辑

第26-76行:
处理USB设备复位和唤醒的逻辑。

以下是这段代码的详细过程和作用:
代码首先检查指定USB端口是否有连接的设备(portstatus & USB_PORT_STAT_CONNECTION),并且这个设备的状态不是USB_STATE_NOTATTACHED。如果这两个条件满足,那么进入if语句块。

在if语句块中,首先检查端口是否已经使能(portstatus & USB_PORT_STAT_ENABLE)。如果端口已经使能,那么就读取设备的描述符,并检查设备的描述符是否已经改变。如果设备的描述符已经改变,那么就更新设备的描述符。否则,就将status设置为0,表示没有需要做的事情。

如果端口没有使能,但设备处于挂起状态,并且设备的持久连接功能已经打开(只有在配置了电源管理(CONFIG_PM)的情况下才会检查),那么就尝试唤醒设备。这是通过调用usb_remote_wakeup函数实现的。

如果上述两个条件都不满足,那么就不对设备进行复位或唤醒操作。

总的来说,这段代码的作用就是处理USB设备的复位和唤醒操作。在设备已经连接并且端口已经使能的情况下,会检查设备的描述符是否已经改变,如果已经改变,那么就更新设备的描述符。在设备已经连接但端口没有使能的情况下,如果设备处于挂起状态并且设备的持久连接功能已经打开,那么就尝试唤醒设备。

2.4.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设备的设备描述符。

以下是这段代码的详细过程和作用:

函数接收两个参数:一个指向usb_device结构体的指针dev,表示要操作的USB设备;一个无符号整数size,表示要读取的设备描述符的大小。

在函数中,首先检查size是否大于设备描述符的大小。如果size大于设备描述符的大小,那么返回-EINVAL,表示参数无效。

然后,调用kmalloc函数,分配一个usb_device_descriptor结构体的内存空间,并将返回的指针赋值给desc。如果kmalloc函数返回NULL,那么返回-ENOMEM,表示内存不足。

接着,调用usb_get_descriptor函数,读取设备的设备描述符。usb_get_descriptor函数会将读取到的设备描述符存储在desc指向的内存空间中。

如果usb_get_descriptor函数成功执行,那么就将desc指向的设备描述符复制到dev->descriptor中。

最后,调用kfree函数,释放desc指向的内存空间,并返回usb_get_descriptor函数的返回值。

总的来说,这个函数的作用就是读取或重新读取USB设备的设备描述符。这是通过调用usb_get_descriptor函数实现的,该函数会将读取到的设备描述符存储在一个临时的内存空间中,然后再将这个设备描述符复制到设备结构体中。

2.4.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_device结构体的指针dev,表示要操作的USB设备;一个无符号字符type,表示要获取的描述符的类型;一个无符号字符index,表示要获取的描述符的编号;一个指针buf,表示存放描述符的缓冲区;一个整数size,表示缓冲区的大小。

在函数中,首先检查size是否小于等于0。如果size小于等于0,那么返回-EINVAL,表示参数无效。

然后,使用memset函数将缓冲区的内容全部设置为0。这样可以确保解析的是真正接收到的数据。

接着,进行最多3次尝试,发起GET_DESCRIPTOR请求。在每次尝试中,首先调用usb_control_msg函数,发起GET_DESCRIPTOR请求。如果usb_control_msg函数返回的结果小于等于0并且结果不是-ETIMEDOUT,那么就继续下一次尝试。如果结果大于1并且缓冲区的第二个字节(描述符的类型)不等于type,那么就将结果设置为-ENODATA,并继续下一次尝试。如果以上两个条件都不满足,那么就跳出循环。

最后,返回usb_control_msg函数的返回值。

总的来说,这个函数的作用就是发起一个标准的GET_DESCRIPTOR请求,以获取USB设备的描述符。这是通过调用usb_control_msg函数实现的,该函数会将接收到的描述符存储在提供的缓冲区中。

2.4.3 descriptors_changed()

static int descriptors_changed(struct usb_device *udev,
		struct usb_device_descriptor *old_device_descriptor,
		struct usb_host_bos *old_bos)
{
	int		changed = 0;
	unsigned	index;
	unsigned	serial_len = 0;
	unsigned	len;
	unsigned	old_length;
	int		length;
	char		*buf;

	if (memcmp(&udev->descriptor, old_device_descriptor,
			sizeof(*old_device_descriptor)) != 0)
		return 1;

	if ((old_bos && !udev->bos) || (!old_bos && udev->bos))
		return 1;
	if (udev->bos) {
		len = le16_to_cpu(udev->bos->desc->wTotalLength);
		if (len != le16_to_cpu(old_bos->desc->wTotalLength))
			return 1;
		if (memcmp(udev->bos->desc, old_bos->desc, len))
			return 1;
	}

	/* Since the idVendor, idProduct, and bcdDevice values in the
	 * device descriptor haven't changed, we will assume the
	 * Manufacturer and Product strings haven't changed either.
	 * But the SerialNumber string could be different (e.g., a
	 * different flash card of the same brand).
	 */
	if (udev->serial)
		serial_len = strlen(udev->serial) + 1;

	len = serial_len;
	for (index = 0; index < udev->descriptor.bNumConfigurations; index++) {
		old_length = le16_to_cpu(udev->config[index].desc.wTotalLength);
		len = max(len, old_length);
	}

	buf = kmalloc(len, GFP_NOIO);
	if (!buf)
		/* assume the worst */
		return 1;

	for (index = 0; index < udev->descriptor.bNumConfigurations; index++) {
		old_length = le16_to_cpu(udev->config[index].desc.wTotalLength);
		length = usb_get_descriptor(udev, USB_DT_CONFIG, index, buf,
				old_length);
		if (length != old_length) {
			dev_dbg(&udev->dev, "config index %d, error %d\n",
					index, length);
			changed = 1;
			break;
		}
		if (memcmp(buf, udev->rawdescriptors[index], old_length)
				!= 0) {
			dev_dbg(&udev->dev, "config index %d changed (#%d)\n",
				index,
				((struct usb_config_descriptor *) buf)->
					bConfigurationValue);
			changed = 1;
			break;
		}
	}

	if (!changed && serial_len) {
		length = usb_string(udev, udev->descriptor.iSerialNumber,
				buf, serial_len);
		if (length + 1 != serial_len) {
			dev_dbg(&udev->dev, "serial string error %d\n",
					length);
			changed = 1;
		} else if (memcmp(buf, udev->serial, length) != 0) {
			dev_dbg(&udev->dev, "serial string changed\n");
			changed = 1;
		}
	}

	kfree(buf);
	return changed;
}

主要用于检查USB设备的描述符是否发生了变化。

以下是这段代码的详细过程和作用:

函数接收三个参数:一个指向usb_device结构体的指针udev,表示要检查的USB设备;一个指向usb_device_descriptor结构体的指针old_device_descriptor,表示旧的设备描述符;一个指向usb_host_bos结构体的指针old_bos,表示旧的BOS描述符。

在函数中,首先比较新旧设备描述符是否相同。如果不同,那么返回1,表示描述符发生了变化。

然后,检查新旧BOS描述符是否相同。如果不同,那么返回1,表示描述符发生了变化。

接着,遍历设备的所有配置描述符,比较新旧配置描述符是否相同。如果不同,那么就将changed设置为1,表示描述符发生了变化,并跳出循环。

如果设备有序列号,那么就比较新旧序列号是否相同。如果不同,那么就将changed设置为1,表示描述符发生了变化。

最后,释放临时缓冲区buf,并返回changed。

总的来说,这个函数的作用就是检查USB设备的描述符是否发生了变化。这是通过比较新旧设备描述符、BOS描述符、配置描述符和序列号实现的。如果任何一个描述符发生了变化,那么就返回1,表示描述符发生了变化。

2.5 第68行:清除USB集线器指定端口的状态变化标志

第68行:作用是清除USB集线器指定端口的状态变化标志。

以下是这段代码的详细过程:

clear_bit(port1, hub->change_bits);:这行代码调用clear_bit函数,将hub->change_bits的第port1位设置为0。clear_bit是一个宏,用于将指定位置的位设置为0。hub->change_bits是一个位图,每一位对应集线器的一个端口,如果某一位为1,表示对应的端口的状态发生了变化。
总的来说,这段代码的作用就是清除USB集线器指定端口的状态变化标志。这是通过将hub->change_bits的第port1位设置为0实现的。这样,下次检查端口状态变化时,就不会误认为这个端口的状态发生了变化。

2.6 第70-72行:判断USB的操作结果

第70-72行:判断status的状态,前面会对这个标志位进行操作,如果无需操作 或 USB唤醒成功等 表示 已成功重新验证连接的状态下。
if (status == 0):这行代码是一个条件判断语句,它检查变量status是否等于0。
return;:如果status等于0,那么就执行这行代码。这行代码表示立即结束当前函数的执行,并返回到函数被调用的地方。
总的来说,这段代码的作用就是检查status是否等于0,如果等于0,那么就立即结束当前函数的执行。这通常表示一个操作成功完成,不需要进行后续的处理。

2.7 第74-76行:处理USB集线器中的端口连接

第74-76行:主要作用是处理USB集线器中的端口连接。

以下是这段代码的详细过程和作用:

usb_unlock_port(port_dev);:这行代码调用usb_unlock_port函数,解锁指定的USB端口。在多线程环境中,锁是用来保护共享资源的一种机制,防止多个线程同时访问或修改共享资源。解锁端口表示允许其他线程访问或修改端口。

hub_port_connect(hub, port1, portstatus, portchange);:这行代码调用hub_port_connect函数,处理端口的连接。这个函数会根据端口的状态(portstatus)和变化(portchange)来决定如何处理端口的连接。

usb_lock_port(port_dev);:这行代码调用usb_lock_port函数,锁定指定的USB端口。锁定端口表示阻止其他线程访问或修改端口,直到端口再次被解锁。

总的来说,这段代码的作用就是处理USB集线器中的端口连接。这是通过解锁端口,处理端口的连接,然后再锁定端口来实现的。这样可以保证在处理端口的连接过程中,端口不会被其他线程访问或修改。

三、总结

以上是对USB port event下的端口链接发生变化处理函数hub_port_connect_change()的分析。后面将继续分析,如果USB device 下hub port connect 发生变化的处理过程,例如USB设备插入、USB设备拔出等链接发生变化情况的处理。 由hub_port_connect()函数处理。请看篇分析。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

waterfxw

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

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

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

打赏作者

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

抵扣说明:

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

余额充值