<kernel>kernel 6.4 USB-之-usb_new_device()分析
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
一、前言
在Linux内核的USB子系统中,用于处理新连接的USB设备的函数usb_new_device。它的主要作用是初始化新连接的USB设备,并将其添加到设备系统中。
这个函数接受一个指向struct usb_device的指针作为参数,这个结构体包含了新连接的USB设备的所有信息。
函数的主要步骤和作用如下:
(1) 如果USB设备不是根集线器,则禁用设备唤醒功能。
(2) 向运行时电源管理框架报告设备处于活动状态,并启用设备的自动挂起功能。
(3) 禁用所有设备的自动挂起功能。
(4) 调用usb_enumerate_device函数读取设备的描述符。
(5) 如果读取描述符失败,则跳转到错误处理部分。
(6) 将设备的设备号设置为USB设备的主设备号和次设备号的组合。
(7) 调用announce_device函数公告设备。
(8) 如果设备有序列号、产品名或制造商名,则将这些信息添加到设备的随机性中。
(9) 启用设备的异步挂起功能。
(10) 检查集线器或固件是否将此端口标记为不可移动。
(11) 调用device_add函数将设备注册到设备系统中。
(12) 如果设备有父设备,则在设备和USB端口设备之间创建链接文件。
(13) 调用usb_create_ep_devs函数为设备创建端点设备。
(14) 将设备标记为最后一次忙碌。
如果所有步骤都成功,则返回0;否则,将设备状态设置为未连接,并禁用运行时电源管理,然后返回错误代码。
二、usb_new_device()函数
usb_new_device()函数内容如下:
int usb_new_device(struct usb_device *udev)
{
int err;
if (udev->parent) {
/* Initialize non-root-hub device wakeup to disabled;
* device (un)configuration controls wakeup capable
* sysfs power/wakeup controls wakeup enabled/disabled
*/
device_init_wakeup(&udev->dev, 0);
}
/* Tell the runtime-PM framework the device is active */
pm_runtime_set_active(&udev->dev);
pm_runtime_get_noresume(&udev->dev);
pm_runtime_use_autosuspend(&udev->dev);
pm_runtime_enable(&udev->dev);
/* By default, forbid autosuspend for all devices. It will be
* allowed for hubs during binding.
*/
usb_disable_autosuspend(udev);
err = usb_enumerate_device(udev); /* Read descriptors */
if (err < 0)
goto fail;
dev_dbg(&udev->dev, "udev %d, busnum %d, minor = %d\n",
udev->devnum, udev->bus->busnum,
(((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
/* export the usbdev device-node for libusb */
udev->dev.devt = MKDEV(USB_DEVICE_MAJOR,
(((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
/* Tell the world! */
announce_device(udev);
if (udev->serial)
add_device_randomness(udev->serial, strlen(udev->serial));
if (udev->product)
add_device_randomness(udev->product, strlen(udev->product));
if (udev->manufacturer)
add_device_randomness(udev->manufacturer,
strlen(udev->manufacturer));
device_enable_async_suspend(&udev->dev);
/* check whether the hub or firmware marks this port as non-removable */
set_usb_port_removable(udev);
/* Register the device. The device driver is responsible
* for configuring the device and invoking the add-device
* notifier chain (used by usbfs and possibly others).
*/
err = device_add(&udev->dev);
if (err) {
dev_err(&udev->dev, "can't device_add, error %d\n", err);
goto fail;
}
/* Create link files between child device and usb port device. */
if (udev->parent) {
struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
int port1 = udev->portnum;
struct usb_port *port_dev = hub->ports[port1 - 1];
err = sysfs_create_link(&udev->dev.kobj,
&port_dev->dev.kobj, "port");
if (err)
goto fail;
err = sysfs_create_link(&port_dev->dev.kobj,
&udev->dev.kobj, "device");
if (err) {
sysfs_remove_link(&udev->dev.kobj, "port");
goto fail;
}
if (!test_and_set_bit(port1, hub->child_usage_bits))
pm_runtime_get_sync(&port_dev->dev);
}
(void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev);
usb_mark_last_busy(udev);
pm_runtime_put_sync_autosuspend(&udev->dev);
return err;
fail:
usb_set_device_state(udev, USB_STATE_NOTATTACHED);
pm_runtime_disable(&udev->dev);
pm_runtime_set_suspended(&udev->dev);
return err;
}
下面就对usb_new_device()函数内容详细分析:
2.1 第5-11行:初始化非根集线器(root hub)设备的唤醒状态
第5-11行:主要作用是初始化非根集线器(root hub)设备的唤醒状态。
这段代码的详细过程如下:
首先,检查udev(指向struct usb_device的指针,它包含了USB设备的所有信息)的parent成员。如果parent成员存在,那么说明这个设备不是根集线器(root hub)。
然后,调用device_init_wakeup函数来初始化这个设备的唤醒状态。这个函数的第一个参数是一个指向设备的指针,第二个参数是唤醒的初始状态。在这里,设备的唤醒状态被初始化为0,也就是禁用状态。
这段代码的主要作用是控制非根集线器设备的唤醒能力。在USB子系统中,设备的唤醒能力是可以被配置的。设备的唤醒能力被禁用时,设备在系统挂起(suspend)时无法唤醒系统。设备的唤醒能力被启用时,如果设备在系统挂起时有活动(例如,接收到数据),那么设备可以唤醒系统。
2.2 第13-17行:USB 设备进行电源管理
第13-17行:对 USB 设备进行电源管理的部分。
pm_runtime_set_active(&udev->dev); 这行告诉运行时电源管理框架,该设备是活动的。运行时电源管理框架是 Linux 内核中用于管理设备电源的子系统。当设备是活动的,意味着设备正在使用中,系统不会尝试将其电源关闭以节省能源。
pm_runtime_get_noresume(&udev->dev); 这行是获取设备的运行时引用,但不会尝试恢复设备。这意味着,如果设备当前处于挂起状态(即电源关闭状态),这个函数不会尝试将其唤醒。
pm_runtime_use_autosuspend(&udev->dev); 这行是启用设备的自动挂起功能。当设备启用了自动挂起后,如果一段时间内没有使用设备,系统会自动将其电源关闭以节省能源。
pm_runtime_enable(&udev->dev); 这行是启用设备的运行时电源管理。当设备的运行时电源管理被启用后,系统将会根据设备的使用情况,自动地调整设备的电源状态,以平衡能源消耗和性能。
总的来说,这段代码的作用是启用并配置 USB 设备的运行时电源管理,以达到节省能源的目的。
2.3 第22行:禁止 USB 设备(udev)的自动挂起功能
第22行:作用是禁止 USB 设备(udev)的自动挂起功能。
自动挂起是 Linux 内核中的一个电源管理机制,当设备在一段时间内没有被使用时,系统会自动将其电源关闭,以节省能源。然而,对于某些设备,我们可能不希望它们在空闲时被自动关闭电源,因此需要禁用它们的自动挂起功能。
这段代码的注释提到,“默认情况下,禁止所有设备的自动挂起。在绑定过程中,将允许对集线器使用此功能。” 这意味着在 USB 设备被初始化时,会禁止其自动挂起功能,但在后续的过程中,可能会根据需要启用集线器(即 USB 设备的一种)的自动挂起功能。
2.3.1 usb_disable_autosuspend()函数
路径:drivers\usb\core\driver.c
void usb_disable_autosuspend(struct usb_device *udev)
{
pm_runtime_forbid(&udev->dev);
}
EXPORT_SYMBOL_GPL(usb_disable_autosuspend);
“这个函数阻止 @udev 设备被自动挂起,并且如果它已经被自动挂起,那么将唤醒它。调用者必须持有 @udev 设备的锁。” 这意味着,在调用这个函数之前,必须确保已经获取了设备的锁,以防止并发操作造成的问题。
路径:
void pm_runtime_forbid(struct device *dev)
{
spin_lock_irq(&dev->power.lock);
if (!dev->power.runtime_auto)
goto out;
dev->power.runtime_auto = false;
atomic_inc(&dev->power.usage_count);
rpm_resume(dev, 0);
out:
spin_unlock_irq(&dev->power.lock);
}
EXPORT_SYMBOL_GPL(pm_runtime_forbid);
其主要作用是阻止设备的运行时电源管理(runtime PM)。
struct device *dev 是函数的参数,表示要操作的设备。
spin_lock_irq(&dev->power.lock); 这行代码是获取设备电源管理的锁,防止并发操作。spin_lock_irq 是一个忙等待锁,如果锁已经被其他线程获取,那么调用线程会一直忙等待,直到获取到锁。irq 表示在获取锁的同时,禁止中断。
if (!dev->power.runtime_auto) goto out; 这行代码是检查设备的 runtime_auto 标志,如果该标志为 false,则直接跳转到 out 标签,释放锁。
dev->power.runtime_auto = false; 这行代码是将 runtime_auto 标志设置为 false,表示禁止设备的自动挂起。
atomic_inc(&dev->power.usage_count); 这行代码是原子地增加设备的使用计数。原子操作是一种不可中断的操作,保证了在多线程环境下的安全性。
rpm_resume(dev, 0); 这行代码是尝试恢复设备,即如果设备当前处于挂起状态,那么尝试唤醒设备。
spin_unlock_irq(&dev->power.lock); 这行代码是释放设备电源管理的锁,并允许中断。
EXPORT_SYMBOL_GPL(pm_runtime_forbid); 这行代码是将 pm_runtime_forbid 函数导出,使得其他的内核模块也可以调用这个函数。
总的来说,这个函数的作用是禁止设备的运行时电源管理,即使设备闲置,也不会自动挂起。
2.4 第24-32行:USB设备枚举过程中执行一些操作
第24-32行:在USB设备枚举过程中执行一些操作。
err = usb_enumerate_device(udev); 这行代码调用函数usb_enumerate_device,这个函数的作用是读取USB设备的描述符。这是USB设备枚举过程的一部分,其中设备将其能力和特性报告给主机。
if (err < 0) goto fail; 这行代码检查usb_enumerate_device函数的返回值。如果返回值小于0,表示函数执行失败,代码将跳转到fail标签处,这通常意味着出现错误处理。
dev_dbg(&udev->dev, “udev %d, busnum %d, minor = %d\n”, udev->devnum, udev->bus->busnum, (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); 这行代码是用于调试的,它将USB设备的一些信息(设备编号,总线编号,次设备号)打印出来。
最后两行代码设置udev设备的设备类型和设备号。MKDEV宏用于创建一个设备号,这个设备号由主设备号(这里是USB_DEVICE_MAJOR)和次设备号组成。这里的次设备号是通过一个算式计算得到的,算式中使用了USB设备所在的总线编号和设备编号。这个设备号将用于创建设备节点,使得用户空间的程序(例如libusb)可以访问这个USB设备。
总的来说,这段代码的主要作用是在USB设备枚举过程中读取设备描述符,打印设备信息,并设置设备号,以便用户空间程序可以访问USB设备。
2.5 第35行:将USB设备的信息打印到日志中
第35行:announce_device函数的作用是将USB设备的信息打印到日志中。这通常用于调试和诊断问题,也有助于了解USB设备的状态和属性。
路径:drivers\usb\core\hub.c
static void announce_device(struct usb_device *udev)
{
u16 bcdDevice = le16_to_cpu(udev->descriptor.bcdDevice);
dev_info(&udev->dev,
"New USB device found, idVendor=%04x, idProduct=%04x, bcdDevice=%2x.%02x\n",
le16_to_cpu(udev->descriptor.idVendor),
le16_to_cpu(udev->descriptor.idProduct),
bcdDevice >> 8, bcdDevice & 0xff);
dev_info(&udev->dev,
"New USB device strings: Mfr=%d, Product=%d, SerialNumber=%d\n",
udev->descriptor.iManufacturer,
udev->descriptor.iProduct,
udev->descriptor.iSerialNumber);
show_string(udev, "Product", udev->product);
show_string(udev, "Manufacturer", udev->manufacturer);
show_string(udev, "SerialNumber", udev->serial);
}
2.6 第37-43行:USB设备枚举过程中执行一些操作
第37-43行:在USB设备枚举过程中执行一些操作。
这段代码的作用是将USB设备的一些属性(序列号、产品名称、制造商名称)添加到系统的熵池中。熵池是Linux内核用于生成随机数的一个数据结构。当需要生成一个随机数时,内核会从熵池中取出一些数据,并使用这些数据作为随机数生成算法的输入。
在这段代码中,add_device_randomness函数被用于将数据添加到熵池。这个函数的第一个参数是要添加的数据,第二个参数是数据的长度。这里的数据是USB设备的序列号、产品名称和制造商名称,这些数据都是字符串,所以使用strlen函数来获取它们的长度。
总的来说,这段代码的作用是增加系统的熵,以提高随机数的质量。这对于需要高质量随机数的应用(如加密应用)来说是非常重要的。
2.6.1 add_device_randomness()函数
路径:drivers\char\random.c
void add_device_randomness(const void *buf, size_t len)
{
unsigned long entropy = random_get_entropy();
unsigned long flags;
spin_lock_irqsave(&input_pool.lock, flags);
_mix_pool_bytes(&entropy, sizeof(entropy));
_mix_pool_bytes(buf, len);
spin_unlock_irqrestore(&input_pool.lock, flags);
}
EXPORT_SYMBOL(add_device_randomness);
用于向输入池添加设备或启动特定的数据,以帮助初始化输入池。
这段代码的工作流程如下:
调用 random_get_entropy 函数获取一些随机性(熵),并将其存储在 entropy 变量中。
使用 spin_lock_irqsave 函数锁定输入池,以防止其他进程在此过程中修改输入池。这个函数还会保存当前的中断状态,并在稍后恢复它。
调用 _mix_pool_bytes 函数两次,首先将 entropy 变量的内容混入输入池,然后将 buf 参数指向的数据混入输入池。
使用 spin_unlock_irqrestore 函数解锁输入池,并恢复之前保存的中断状态。
这段代码的主要作用是增加系统的随机性,以避免在大量相同的设备上,熵池具有相似的初始状态。需要注意的是,这里添加的数据并不增加熵池的熵,也就是说,它们不会增加熵池的随机性。这些数据只是用来改变熵池的初始状态,以防止在大量相同的设备上出现相同的初始状态。
2.7 第45行:启用设备的异步挂起
第45行:device_enable_async_suspend是一个内核函数,它的作用是启用设备的异步挂起(asynchronous suspend)功能。异步挂起是一种电源管理策略,它允许设备在系统空闲时进入低功耗状态,以节省电能。当设备被需要时,系统会自动将其唤醒。
在这段代码中,&udev->dev是一个指向USB设备结构体的指针。这个结构体包含了设备的各种信息,包括设备的类型、制造商、序列号等。调用device_enable_async_suspend函数后,这个USB设备就被设置为支持异步挂起。
总的来说,这段代码的作用是启用USB设备的电源管理功能,以节省系统的电能。
2.8 第48行:检查 USB 设备是否被标记为不可移除
第48行:用于检查 USB 设备是否被标记为不可移除。
set_usb_port_removable(udev); 这行代码调用了一个函数 set_usb_port_removable,该函数的参数是 udev,代表了一个 USB 设备。
set_usb_port_removable 函数的主要作用是设置 USB 设备的可移动性。具体来说,它会检查 USB 设备(或者固件)是否将这个端口标记为不可移动。如果是,那么在系统中,这个 USB 设备将被视为不可移动设备,即使物理上可以拔插。
这个函数的具体实现可能会依赖于具体的硬件和固件实现,可能会涉及到读取硬件寄存器或者固件设置等操作。
2.8.1 set_usb_port_removable()
static void set_usb_port_removable(struct usb_device *udev)
{
struct usb_device *hdev = udev->parent;
struct usb_hub *hub;
u8 port = udev->portnum;
u16 wHubCharacteristics;
bool removable = true;
dev_set_removable(&udev->dev, DEVICE_REMOVABLE_UNKNOWN);
if (!hdev)
return;
hub = usb_hub_to_struct_hub(udev->parent);
/*
* If the platform firmware has provided information about a port,
* use that to determine whether it's removable.
*/
switch (hub->ports[udev->portnum - 1]->connect_type) {
case USB_PORT_CONNECT_TYPE_HOT_PLUG:
dev_set_removable(&udev->dev, DEVICE_REMOVABLE);
return;
case USB_PORT_CONNECT_TYPE_HARD_WIRED:
case USB_PORT_NOT_USED:
dev_set_removable(&udev->dev, DEVICE_FIXED);
return;
default:
break;
}
/*
* Otherwise, check whether the hub knows whether a port is removable
* or not
*/
wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
if (!(wHubCharacteristics & HUB_CHAR_COMPOUND))
return;
if (hub_is_superspeed(hdev)) {
if (le16_to_cpu(hub->descriptor->u.ss.DeviceRemovable)
& (1 << port))
removable = false;
} else {
if (hub->descriptor->u.hs.DeviceRemovable[port / 8] & (1 << (port % 8)))
removable = false;
}
if (removable)
dev_set_removable(&udev->dev, DEVICE_REMOVABLE);
else
dev_set_removable(&udev->dev, DEVICE_FIXED);
}
这个函数的目的是设置USB设备的移除性。
函数首先获取USB设备的父设备和对应的USB集线器。然后,它设置设备的移除性为未知。
如果没有父设备,函数就会返回。否则,它会获取设备对应的USB集线器。
接着,函数会检查平台固件是否为该端口提供了信息。如果是,它会根据此信息来确定设备是否可移除。如果设备是热插拔类型,它会设置设备为可移除并返回。如果设备是硬连线类型或端口未使用,它会设置设备为固定并返回。
如果固件没有提供端口信息,函数会检查集线器是否知道端口是否可移除。它获取集线器的特性,并检查集线器是否复合。如果不是,函数会返回。
然后,函数会根据集线器的速度来确定设备是否可移除。对于超速集线器,如果设备可移除位图中的对应位被设置,它会设置设备为不可移除。对于高速集线器,如果设备可移除位图的对应位被设置,它也会设置设备为不可移除。
最后,函数会根据设备是否可移除来设置设备的移除性。如果设备可移除,它会设置设备为可移除。否则,它会设置设备为固定。
总的来说,这个函数的作用是根据设备和集线器的信息来设置USB设备的移除性。
2.9 第54-58行:注册 USB 设备
第54-58行:主要负责注册 USB 设备。
首先,它调用 device_add(&udev->dev) 函数试图将 USB 设备(由 udev 指针表示)注册到系统中。device_add 函数是设备驱动模型(device driver model)的一部分,它将设备添加到系统中并初始化设备所需的所有数据结构。
如果 device_add 函数成功执行,那么 USB 设备就被成功注册到系统中,设备驱动程序负责配置设备并调用添加设备通知链(被 usbfs 和可能的其他一些模块使用)。
如果 device_add 函数执行失败,它会返回一个错误代码。这个错误代码被存储在 err 变量中。然后,dev_err(&udev->dev, “can’t device_add, error %d\n”, err) 语句会将错误信息打印到日志中,其中 %d 是一个占位符,它会被 err 的值替换,表示具体的错误代码。最后,goto fail 语句会使程序跳转到 fail 标签所在的位置,执行错误处理的代码。
2.10 第61-80行: sysfs 文件系统中创建链接
第61-80行:主要负责在 sysfs 文件系统中创建链接,以表示 USB 设备(udev)和其父设备(一个 USB 集线器)之间的关系。
首先,它检查 udev 是否有父设备。如果没有,那么这段代码就不会执行。
如果 udev 有父设备,那么它首先获取这个父设备对应的 USB 集线器(hub)。然后,它获取 udev 所在的端口号(port1)。接着,它获取这个端口对应的 USB 端口设备(port_dev)。
接下来,它试图在 sysfs 中创建一个从 udev 到 port_dev 的链接,链接名为 “port”。如果创建失败,它会跳转到 fail 标签执行错误处理。
如果上一步成功,它会继续尝试在 sysfs 中创建一个从 port_dev 到 udev 的链接,链接名为 “device”。如果创建失败,它会首先删除刚刚创建的 “port” 链接,然后跳转到 fail 标签执行错误处理。
最后,它会检查 port1 是否在 hub 的 child_usage_bits 中被设置。如果没有被设置,那么它会设置这个位,并调用 pm_runtime_get_sync 函数增加 port_dev 的运行时 PM 引用计数。这是为了防止 port_dev 在 udev 还在使用它时被系统挂起或关闭。
总的来说,这段代码的作用是在 sysfs 中表示 USB 设备和其父设备(一个 USB 集线器)之间的关系,并在需要时防止父设备被系统挂起或关闭。
2.11 第82-85行:创建 USB 设备的端点设备,并处理设备的电源管理
第82-85行:主要负责创建 USB 设备的端点设备,并处理设备的电源管理。
首先,它调用 usb_create_ep_devs(&udev->dev, &udev->ep0, udev); 函数,该函数的目的是在 sysfs 文件系统中为 USB 设备的每个端点创建设备节点。这样,用户空间的程序就可以通过这些设备节点来访问 USB 设备的端点。在这里,&udev->dev 是 USB 设备的设备结构体,&udev->ep0 是 USB 设备的端点0的端点结构体,udev 是 USB 设备的设备结构体。
然后,它调用 usb_mark_last_busy(udev); 函数,该函数的目的是标记 USB 设备的最后一次活动时间。这个时间戳用于设备的电源管理,例如,如果设备在一段时间内没有活动,系统可能会将其挂起以节省电源。
接着,它调用 pm_runtime_put_sync_autosuspend(&udev->dev); 函数,该函数的目的是减少设备的运行时电源管理引用计数,并可能触发设备的自动挂起。在这里,&udev->dev 是 USB 设备的设备结构体。
最后,它返回 err,这是一个错误代码。如果前面的操作都成功,err 应该是 0。如果有任何操作失败,err 将是一个错误代码。
总的来说,这段代码的作用是创建 USB 设备的端点设备,并处理设备的电源管理。
2.12 第87-91行:处理 USB 设备连接失败
第87-91行:用于处理 USB 设备连接失败的情况。
首先,usb_set_device_state(udev, USB_STATE_NOTATTACHED); 这行代码将 USB 设备的状态设置为 USB_STATE_NOTATTACHED,表示设备没有被连接到系统。
接着,pm_runtime_disable(&udev->dev); 这行代码调用电源管理(Power Management,简称 PM)函数,禁用设备的运行时电源管理。这意味着设备将不会在运行时进入低功耗状态。
然后,pm_runtime_set_suspended(&udev->dev); 这行代码将设备的运行时 PM 状态设置为挂起(suspended)。这通常意味着设备已被关闭或者不再活动。
最后,return err; 这行代码返回一个错误代码,表示设备连接失败。
总的来说,这段代码的作用是在 USB 设备连接失败时,将设备状态设置为未连接,并禁用设备的运行时电源管理。
三、总结
usb_new_device()主要是将USB设备添加到系统中,通过device_add()添加到系统中,该函数会匹配对于USB的驱动函数 进一步调用设备驱动的probe函数,后续就是设备驱动的内容了。
USB插入的整体流程大部是在hub驱动中完成的。目前hub部分的内容基本分析完成。
后面会继续分析 usb_control_msg_send()等函数 与USB设备的通信过程,以及以U盘插入后匹配storage 驱动的内容等等。