在USB摄像头驱动中,hub.c文件扮演着USB集线器(Hub)驱动的角色。USB集线器是用于连接多个USB设备的设备,它提供了额外的USB端口,并负责数据传输的分配和管理。hub.c文件中的内容主要涉及USB集线器的初始化、事件处理、数据传输和管理等功能。以下是该文件中常见的功能和作用的概括:
- 集线器的初始化和设备连接管理:
- 检测和识别连接到集线器的USB设备。
- 分配和配置集线器的USB端口。
- 跟踪已连接设备的状态和属性,如端口状态、速度等。
- 处理设备的插拔事件,并通知相应的驱动程序进行设备的初始化或释放。
- 端口状态和速度管理:
- 监控集线器端口的状态变化,如设备插入或拔出。
- 管理端口的电源状态、连接状态和速度信息。
- 处理集线器和设备之间的速度协商和协议转换。
- 数据传输和分配:
- 为连接到集线器的设备分配传输带宽。
- 管理端口和传输通道的分配和释放。
- 处理数据的转发和路由,确保正确的数据传输路径。
- 集线器事件和通知处理:
- 监听和处理集线器和端口相关的事件,如连接状态变化、错误状态等。
- 向上层驱动程序和应用程序发送事件通知,以便它们可以做出相应的处理。
- 集线器电源管理:
- 管理集线器的电源控制,如启用/禁用电源、进入/退出睡眠状态等。
- 处理集线器的电源管理策略,如自动挂起、节能模式等。
总体而言,hub.c文件中的内容实现了USB摄像头驱动中与USB集线器相关的功能,包括集线器的初始化、事件处理、数据传输和管理等。它负责管理USB集线器和连接的USB设备之间的通信,并确保数据的正确传输和设备的正常运行。
/driver/usb/core/hub.c
usb_hub_init
在内核中,这个函数通常在系统启动期间的初始化阶段被调用。具体来说,它会在USB子系统初始化期间被调用。
这个函数的主要作用是初始化USB hub驱动程序。USB hub是一种用于扩展USB接口的设备,它允许将多个USB设备连接到单个USB端口上。USB hub驱动程序负责管理和控制USB hub设备,并处理与USB设备的连接、断开、通信等操作。
在函数内部,它会注册USB hub驱动程序,使得内核能够正确识别和处理连接到USB hub的设备。它还分配一个工作队列(workqueue),用于处理USB hub相关的后台任务,例如处理连接和断开USB设备的事件。
总之,这个函数在内核中的作用是初始化USB hub驱动程序,确保USB hub设备能够正常工作并与其他USB设备进行通信。它是USB子系统中重要的初始化函数之一。
static struct usb_driver hub_driver = { .name = "hub", // 驱动程序名称 .probe = hub_probe, // 探测函数 .disconnect = hub_disconnect, // 断开连接函数 .suspend = hub_suspend, // 暂停函数 .resume = hub_resume, // 恢复函数 .reset_resume = hub_reset_resume, // 重置恢复函数 .pre_reset = hub_pre_reset, // 重置前函数 .post_reset = hub_post_reset, // 重置后函数 .unlocked_ioctl = hub_ioctl, // ioctl函数 .id_table = hub_id_table, // 设备表 .supports_autosuspend = 1, // 支持自动挂起 };
// 初始化USB hub驱动程序 int usb_hub_init(void) { // 注册USB hub驱动程序 if (usb_register(&hub_driver) < 0) { printk(KERN_ERR "%s: 无法注册hub驱动程序\n", usbcore_name); return -1; } /* * 工作队列需要是可冻结的,以避免干扰USB-PERSIST端口移交。 * 否则,它可能会在EHCI控制器将其端口移交给附属全速控制器之前看到一个全速设备已经消失。 */ hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0); if (hub_wq) return 0; /* 如果kernel_thread失败,则跳过 */ usb_deregister(&hub_driver); pr_err("%s: 无法为USB hub分配工作队列\n", usbcore_name); return -1; }
这个函数是用于初始化USB hub驱动程序的。下面是对函数的分析:
- 首先,通过调用usb_register(&hub_driver)来注册USB hub驱动程序。usb_register函数返回一个小于0的值表示注册失败。如果注册失败,会打印错误信息并返回-1。
- 接下来,函数尝试为USB hub分配一个工作队列(workqueue)。工作队列是用于异步执行后台任务的机制。它使用alloc_workqueue函数进行分配,并设置为可冻结(WQ_FREEZABLE)。如果成功分配工作队列,函数将返回0。
- 如果无法分配工作队列,说明内核线程(kernel_thread)失败。在这种情况下,函数调用usb_deregister(&hub_driver)来取消注册之前注册的USB hub驱动程序,以避免不完整的初始化。然后打印错误信息并返回-1。
总体而言,这个函数的目标是注册USB hub驱动程序并分配一个工作队列。如果其中任何一个步骤失败,函数将返回-1,表示初始化过程出现问题。
/driver/usb/core/hub.c
hub_probe
这个函数是一个USB hub驱动程序中的probe函数,它在与USB hub设备匹配时被调用。下面是对该函数的作用的解释:
作用:
- 该函数用于处理USB hub设备的探测和初始化过程。当一个USB设备与这个驱动程序匹配时,就会调用该函数。
- 在probe函数中,可以执行与特定设备相关的初始化任务,例如配置设备、分配资源、注册设备接口等。
- 通过该函数,驱动程序可以与内核USB子系统进行交互,以确保设备正确识别并能够与其他子系统进行通信。
- 在probe函数中,还可以注册设备的操作函数和回调函数,以处理设备的读写操作、事件处理等。
总之,这个函数的作用是处理与USB hub设备的探测和初始化相关的任务,以确保设备能够正常工作并与其他子系统进行通信。它是USB hub驱动程序中重要的函数之一。
static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_host_interface *desc; // USB主机接口描述符 struct usb_endpoint_descriptor *endpoint; // USB端点描述符 struct usb_device *hdev; // USB设备 struct usb_hub *hub; // USB集线器 desc = intf->cur_altsetting; // 获取当前USB接口的描述符 hdev = interface_to_usbdev(intf); // 获取USB设备 /* * Set default autosuspend delay as 0 to speedup bus suspend, * based on the below considerations: * * - Unlike other drivers, the hub driver does not rely on the * autosuspend delay to provide enough time to handle a wakeup * event, and the submitted status URB is just to check future * change on hub downstream ports, so it is safe to do it. * * - The patch might cause one or more auto supend/resume for * below very rare devices when they are plugged into hub * first time: * * devices having trouble initializing, and disconnect * themselves from the bus and then reconnect a second * or so later * * devices just for downloading firmware, and disconnects * themselves after completing it * * For these quite rare devices, their drivers may change the * autosuspend delay of their parent hub in the probe() to one * appropriate value to avoid the subtle problem if someone * does care it. * * - The patch may cause one or more auto suspend/resume on * hub during running 'lsusb', but it is probably too * infrequent to worry about. * * - Change autosuspend delay of hub can avoid unnecessary auto * suspend timer for hub, also may decrease power consumption * of USB bus. * * - If user has indicated to prevent autosuspend by passing * usbcore.autosuspend = -1 then keep autosuspend disabled. */ #ifdef CONFIG_PM if (hdev->dev.power.autosuspend_delay >= 0) pm_runtime_set_autosuspend_delay(&hdev->dev, 0); #endif /* * Hubs have proper suspend/resume support, except for root hubs * where the controller driver doesn't have bus_suspend and * bus_resume methods. */ if (hdev->parent) { /* 普通设备 */ usb_enable_autosuspend(hdev); // 启用自动挂起 } else { /* 根集线器 */ const struct hc_driver *drv = bus_to_hcd(hdev->bus)->driver; if (drv->bus_suspend && drv->bus_resume) usb_enable_autosuspend(hdev); // 启用自动挂起 } if (hdev->level == MAX_TOPO_LEVEL) { // 如果设备层级过深 dev_err(&intf->dev, "Unsupported bus topology: hub nested too deep\n"); // 输出错误信息 return -E2BIG; // 返回错误码 } #ifdef CONFIG_USB_OTG_BLACKLIST_HUB if (hdev->parent) { dev_warn(&intf->dev, "ignoring external hub\n"); return -ENODEV; } #endif /* Some hubs have a subclass of 1, which AFAICT according to the */ /* specs is not defined, but it works */ if ((desc->desc.bInterfaceSubClass != 0) && (desc->desc.bInterfaceSubClass != 1)) { // 如果子类不为0或1 descriptor_error: dev_err (&intf->dev, "bad descriptor, ignoring hub\n"); // 输出错误信息 return -EIO; // 返回错误码 } /* Multiple endpoints? What kind of mutant ninja-hub is this? */ // 多个端点?这是什么神奇的集线器? if (desc->desc.bNumEndpoints != 1) // 如果端点数不为1 goto descriptor_error; // 跳转到错误处理 endpoint = &desc->endpoint[0].desc; // 获取端点描述符 /* If it's not an interrupt in endpoint, we'd better punt! */ // 如果不是中断端点,我们最好放弃! if (!usb_endpoint_is_int_in(endpoint)) // 如果不是中断输入端点 goto descriptor_error; // 跳转到错误处理 /* We found a hub */ // 我们找到了一个集线器 dev_info (&intf->dev, "USB hub found\n"); // 输出信息 hub = kzalloc(sizeof(*hub), GFP_KERNEL); // 分配内存 if (!hub) { // 如果分配失败 dev_dbg (&intf->dev, "couldn't kmalloc hub struct\n"); // 输出调试信息 return -ENOMEM; // 返回错误码 } kref_init(&hub->kref); // 初始化引用计数 hub->intfdev = &intf->dev; // 设置接口设备 hub->hdev = hdev; // 设置USB设备 INIT_DELAYED_WORK(&hub->leds, led_work); // 初始化延迟工作 INIT_DELAYED_WORK(&hub->init_work, NULL); // 初始化延迟工作 INIT_WORK(&hub->events, hub_event); // 初始化工作 usb_get_intf(intf); // 获取接口 usb_get_dev(hdev); // 获取USB设备 usb_set_intfdata (intf, hub); // 设置接口数据 intf->needs_remote_wakeup = 1; // 设置需要远程唤醒 pm_suspend_ignore_children(&intf->dev, true); // 忽略子设备的挂起 if (hdev->speed == USB_SPEED_HIGH) // 如果速度为高速 highspeed_hubs++; // 高速集线器数加1 if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND) // 如果驱动信息中包含检查端口自动挂起的标志 hub->quirk_check_port_auto_suspend = 1; // 设置检查端口自动挂起的标志 if (hub_configure(hub, endpoint) >= 0) // 如果配置集线器成功 return 0; // 返回0 hub_disconnect (intf); // 断开集线器 return -ENODEV; // 返回错误码 }
- 获取USB接口描述符和USB设备。
- 根据设备类型和层级判断设备是否为普通设备或根集线器,并根据情况启用自动挂起功能。
- 验证USB hub设备的描述符是否有效,包括子类和端点的检查。
- 分配内存并初始化USB hub结构体,设置相关参数和工作队列。
- 设置接口数据和设备引用计数,设置需要远程唤醒,并忽略子设备的挂起。
- 根据设备的速度和驱动信息进行相应处理,例如统计高速集线器数和设置检查端口自动挂起的标志。
- 如果配置集线器成功,返回0;否则断开集线器并返回错误码。
总之,这个函数的主要功能是初始化USB hub设备并进行相关设置,以确保设备能够正常工作。它涉及到USB设备描述符的验证、内存分配、参数设置等操作,是USB hub驱动程序中关键的探测和初始化函数之一
line1811:
INIT_WORK(&hub->events, hub_event); // 初始化工作
这行代码创建了一个名为events的工作结构体,并将其初始化为待处理的工作任务。hub_event函数是在这个工作任务被调度时将会执行的函数。
通过初始化工作结构体,可以将特定的函数(例如hub_event)与一个工作队列相关联,以便在适当的时机异步执行这个函数。这对于处理USB hub设备的事件或异步操作非常有用,因为可以将这些任务放入工作队列中,并在适当的时候由内核调度执行,而不会阻塞其他任务的执行。
总之,INIT_WORK(&hub->events, hub_event);这行代码的作用是初始化一个工作结构体,并将其与hub_event函数关联,以便在适当的时机异步执行hub_event函数的任务。
hub_event
hub_probe->hub_event
在内核中,static void hub_event(struct work_struct *work)函数是USB hub驱动程序中的一个工作函数,用于处理USB hub设备的事件。
这个函数在特定的工作队列中被调度执行,以处理与USB hub相关的事件。通常,在USB hub设备发生状态变化、端口插拔、速度变更等事件时,内核会调度执行这个函数。
static void hub_event(struct work_struct *work) { struct usb_device *hdev; // USB设备 struct usb_interface *intf; // USB接口 struct usb_hub *hub; // USB集线器 struct device *hub_dev; // 集线器设备 u16 hubstatus; // 集线器状态 u16 hubchange; // 集线器状态改变 int i, ret; hub = container_of(work, struct usb_hub, events); // 获取USB集线器 hdev = hub->hdev; // 获取USB设备 hub_dev = hub->intfdev; // 获取集线器设备 intf = to_usb_interface(hub_dev); // 获取USB接口 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); // 锁定USB设备 if (unlikely(hub->disconnected)) // 如果集线器已经断开连接 goto out_hdev_lock; /* If the hub has died, clean up after it */ if (hdev->state == USB_STATE_NOTATTACHED) { // 如果USB设备未连接 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); // 重置USB设备 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); // 释放端口设备的运行时电源管理 } } /* 处理集线器状态变化 */ if (test_and_clear_bit(0, hub->event_bits) == 0) ; /* 什么也不做 */ 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 */ // 等待500ms 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: /* 平衡上面的usb_autopm_get_interface() */ usb_autopm_put_interface_no_suspend(intf); out_hdev_lock: usb_unlock_device(hdev); // 解锁USB设备 /* 平衡kick_hub_wq()中的内容并允许自动挂起 */ usb_autopm_put_interface(intf); kref_put(&hub->kref, hub_release); }
- 获取USB设备、USB接口、USB集线器和集线器设备的指针。
- 打印调试信息,包括USB设备的状态、端口数量、状态变化位和事件位。
- 锁定USB设备,检查设备是否已经断开连接。
- 如果USB设备未连接,设置错误码并关闭USB集线器。
- 执行自动恢复操作,自动恢复USB接口的运行状态。
- 如果集线器正在关闭,直接退出。
- 如果集线器出现错误,重置USB设备。
- 遍历集线器的所有端口,处理端口状态变化的事件。
- 处理集线器状态变化的事件,包括本地电源改变和过流改变。
- 释放锁定的USB设备和端口,并进行自动挂起。
- 释放USB集线器的引用计数,并执行相应的清理操作。
总之,这个函数负责处理USB hub设备的各种事件,包括端口状态变化和集线器状态变化。它通过锁定设备、执行必要的操作以及释放资源来确保USB hub的正常运行。
段代码处理USB hub的端口状态变化事件。它通过遍历所有的端口,检查端口的事件位、状态改变位和唤醒位是否被设置,如果设置了任意一个位,则执行相应的处理操作。
/* 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); // 释放端口设备的运行时电源管理 }
}
具体的处理步骤如下:
- 使用变量i从1遍历到hdev->maxchild,表示遍历所有的端口。
- 获取当前端口的设备指针,存储在变量port_dev中,hub->ports[i - 1]表示获取USB hub中的第i个端口设备。
- 如果当前端口的事件位、状态改变位或唤醒位被设置,即调用test_bit()函数进行判断。
- 在满足上述条件的情况下,执行以下操作:
- 调用pm_runtime_get_noresume()函数获取端口设备的运行时电源管理,确保在处理端口事件期间保持端口处于活动状态。
- 调用pm_runtime_barrier()函数等待端口设备的运行时电源管理操作完成。
- 调用usb_lock_port()函数锁定端口设备,以确保在处理端口事件期间其他操作不会干扰。
- 调用port_event()函数处理端口事件,将USB hub和端口号作为参数传递给该函数。
- 调用usb_unlock_port()函数解锁端口设备。
- 调用pm_runtime_put_sync()函数释放端口设备的运行时电源管理。
这段代码的作用是针对每个端口处理其状态变化的事件。它获取并锁定端口设备,执行相应的事件处理函数,并在处理完成后释放端口设备,同时处理端口的运行时电源管理,确保在处理事件期间端口保持活动状态。
port_event
hub_probe->hub_event->port_event
port_event()函数用于处理USB hub的端口事件,它会在特定情况下被调用。
函数签名中的__must_hold(&port_dev->status_lock)表示该函数在调用之前必须获取端口设备的status_lock锁,并在函数执行期间持有该锁。这是为了确保在处理端口事件期间不会有其他线程同时修改端口的状态,保证数据的一致性和正确性。
具体来说,port_event()函数的作用如下:
- 接收USB hub和端口号作为参数,即hub和port1。
- 获取与port1对应的端口设备的指针。
- 根据端口设备的状态进行相应的处理,例如检测连接状态、重置端口、配置设备等。
- 更新端口设备的状态,包括连接状态、速度、带宽等信息。
- 根据端口的状态变化执行相应的操作,例如通知驱动程序、发送事件通知等。
port_event()函数会在以下情况下被调用:
- 当USB hub检测到某个端口的事件发生时,例如设备连接、断开、速度变化等。
- 当USB hub的状态发生变化时,需要通知端口设备进行相应的处理。
- 当需要对特定端口进行配置或重置操作时。
总而言之,port_event()函数是用于处理USB hub端口事件的核心函数,它在特定的事件发生时被调用,并负责处理和更新与端口相关的操作和状态信息。
// 处理端口事件 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]; // 获取端口下的 USB 设备 struct usb_device *udev = port_dev->child; // 获取 USB hub 设备 struct usb_device *hdev = hub->hdev; // 端口状态和变化 u16 portstatus, portchange; // 测试端口是否有变化 connect_change = test_bit(port1, hub->change_bits); // 清除事件位和唤醒位 clear_bit(port1, hub->event_bits); clear_bit(port1, hub->wakeup_bits); // 获取端口状态和变化 if (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. */ // 如果端口未启用且存在 USB 设备 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; dev_dbg(&port_dev->dev, "over-current change\n"); // 打印调试信息 usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_OVER_CURRENT); // 清除端口过流标志 msleep(100); /* Cool down */ // 等待100ms hub_power_on(hub, true); // 打开集线器电源 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); // 清除端口复位标志 } // 如果端口处于SS.Inactive状态 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); // 清除端口配置错误标志 } // 如果端口未启用 if (!pm_runtime_active(&port_dev->dev)) return; // 处理远程唤醒 if (hub_handle_remote_wakeup(hub, port1, portstatus, portchange)) connect_change = 1; /* * 如果端口处于SS.Inactive状态,进行热复位 */ if (hub_port_warm_reset_required(hub, port1, portstatus)) { dev_dbg(&port_dev->dev, "do warm reset\n"); // 打印调试信息 if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) || udev->state == USB_STATE_NOTATTACHED) { if (hub_port_reset(hub, port1, NULL, HUB_BH_RESET_TIME, true) < 0) hub_port_disable(hub, port1, 1); } else { 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; } } // 如果连接状态发生变化 if (connect_change) hub_port_connect_change(hub, port1, portstatus, portchange); // 处理连接状态变化 }
该函数用于处理USB hub的端口事件,并且需要持有端口设备的status_lock锁。
函数内容的概括如下:
获取与给定端口号port1对应的端口设备port_dev。
获取端口设备下的USB设备udev以及USB hub设备hdev。
检测端口是否发生连接状态变化,并清除相应的事件位和唤醒位。
获取端口的状态和状态变化。
根据状态变化进行不同的处理:
如果连接状态发生变化,清除连接状态变化标志,并标记需要连接变化。
如果启用状态发生变化,清除启用状态变化标志,并根据特定情况处理端口启用状态变化。
如果端口发生过流,清除过流标志,等待一段时间后重新打开集线器电源,并根据端口状态判断是否存在过流情况。
如果端口发生复位,清除复位标志。
如果端口处于SS.Inactive状态并且为超速设备,清除端口复位标志。
如果端口连接状态发生变化,清除连接状态变化标志。
如果端口发生配置错误,清除配置错误标志。
如果端口设备未处于活动状态,则返回。
处理远程唤醒情况,如果成功处理,则标记需要连接变化。
如果端口处于SS.Inactive状态,进行热复位操作:
如果USB设备不存在、端口未连接或USB设备处于未连接状态,则执行集线器端口复位操作或禁用端口。
否则,对USB设备进行复位操作。
如果连接状态发生变化,处理连接状态变化。
综上所述,该函数根据端口事件的不同情况,执行相应的处理操作,包括处理连接状态变化、启用状态变化、过流情况、复位等,以确保USB hub端口的正常工作。
如果连接状态发生变化,调用端口连接变化处理函数hub_port_connect_change
// 如果连接状态发生变化
if (connect_change)
hub_port_connect_change(hub, port1, portstatus, portchange); // 处理连接状态变化
hub_port_connect_change
hub_probe->hub_event->port_event->hub_port_connect_change
该函数用于处理USB hub端口的连接状态变化,并且需要持有端口设备的status_lock锁。
函数的作用是在端口连接状态变化时执行相应的处理操作,包括连接设备、断开设备、通知驱动程序等。
函数被调用的时机是在port_event()函数中,当检测到端口的连接状态发生变化时,会调用hub_port_connect_change()函数进行处理。
// USB hub 端口连接变化处理函数 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]; // 获取端口下的 USB 设备 struct usb_device *udev = port_dev->child; // 状态 int status = -ENODEV; // 打印端口状态和变化信息 dev_dbg(&port_dev->dev, "status %04x, change %04x, %s\n", portstatus, portchange, portspeed(hub, portstatus)); // 如果 USB hub 有指示灯 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 */ // 如果是 HNP 模式 if (hub->hdev->bus->is_b_host) // 清除连接状态和启用状态变化标志 portchange &= ~(USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE); #endif // 如果端口已连接且存在 USB 设备且 USB 设备状态不是未连接状态 if ((portstatus & USB_PORT_STAT_CONNECTION) && udev && udev->state != USB_STATE_NOTATTACHED) { // 如果端口已启用 if (portstatus & USB_PORT_STAT_ENABLE) { status = 0; /* Nothing to do */ #ifdef CONFIG_PM // 如果 USB 设备状态是挂起状态且持久化使能 } else if (udev->state == USB_STATE_SUSPENDED && udev->persist_enabled) { /* 对于挂起的设备,将其视为远程唤醒事件。 */ // 解锁端口 usb_unlock_port(port_dev); // 远程唤醒 USB 设备 status = usb_remote_wakeup(udev); // 锁定端口 usb_lock_port(port_dev); #endif } else { /* Don't resuscitate */; } } // 清除连接状态变化标志 clear_bit(port1, hub->change_bits); // 如果成功重新验证连接 if (status == 0) return; // 解锁端口 usb_unlock_port(port_dev); // USB hub 端口连接处理 hub_port_connect(hub, port1, portstatus, portchange); // 锁定端口 usb_lock_port(port_dev); }
函数内容的具体作用如下:
获取与给定端口号port1对应的端口设备port_dev和端口下的USB设备udev。
打印端口的状态和变化信息。
如果USB hub具有指示灯功能,将端口的指示灯设置为自动模式。
根据配置选项和HNP模式的情况,对端口状态变化标志进行调整。
根据端口的连接状态和USB设备的状态执行以下操作:
如果端口已连接且存在USB设备且USB设备的状态不是未连接状态:
如果端口已启用,表示连接状态正常,无需进行任何操作。
如果USB设备处于挂起状态且启用了持久化功能,将其视为远程唤醒事件,通过调用usb_remote_wakeup()函数远程唤醒USB设备。
清除连接状态变化标志。
如果成功重新验证连接(即状态为0),直接返回,无需进行后续处理。
解锁端口设备。
调用hub_port_connect()函数处理USB hub端口的连接,包括连接设备、初始化设备信息等。
锁定端口设备。
综上所述,hub_port_connect_change()函数在USB hub端口的连接状态变化时被调用,用于处理设备的连接和断开操作,并进行相应的指示灯设置、HNP模式处理和远程唤醒等操作。
hub_port_connect
hub_probe->hub_event->port_event->hub_port_connect_change->hub_port_connect
hub_port_connect()函数用于处理USB hub端口的连接操作。
函数的作用是在端口连接状态发生变化时,根据连接状态和变化信息执行相应的操作,包括连接设备、初始化设备信息、设置电源属性等。
该函数会被调用的时机包括以下情况:
当检测到端口的连接状态发生变化,并且需要进行连接操作时,会调用该函数进行处理。
// 当前端口上有设备连接时,执行此函数 static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange) { int status, i; unsigned unit_load; struct usb_device *hdev = hub->hdev; // 获取hub所在的设备 struct usb_hcd *hcd = bus_to_hcd(hdev->bus); // 获取hub所在的HCD struct usb_port *port_dev = hub->ports[port1 - 1]; // 获取当前端口的port struct usb_device *udev = port_dev->child; // 获取当前端口上的设备 static int unreliable_port = -1; // 记录连接不稳定的端口 // 如果当前端口上已经有设备连接,则断开连接 if (udev) { if (hcd->usb_phy && !hdev->parent) usb_phy_notify_disconnect(hcd->usb_phy, udev->speed); usb_disconnect(&port_dev->child); } // 如果当前端口上没有设备连接,或者连接状态发生了变化,则清除removed_bits if (!(portstatus & USB_PORT_STAT_CONNECTION) || (portchange & USB_PORT_STAT_C_CONNECTION)) clear_bit(port1, hub->removed_bits); // 如果连接状态或者使能状态发生了变化,则进行debounce if (portchange & (USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE)) { status = hub_port_debounce_be_stable(hub, port1); if (status < 0) { if (status != -ENODEV && port1 != unreliable_port && printk_ratelimit()) dev_err(&port_dev->dev, "connect-debounce failed\n"); portstatus &= ~USB_PORT_STAT_CONNECTION; unreliable_port = port1; } else { portstatus = status; } } /* 如果debounce失败,或者没有设备连接,或者设备已经被移除,则直接返回 */ if (!(portstatus & USB_PORT_STAT_CONNECTION) || test_bit(port1, hub->removed_bits)) { /* * 如果端口可供电,但是当前端口的电源关闭了,且端口没有被占用,则重新开启电源 * (例如,根集线器被重置),但是只有当端口没有被其他设备占用时才能重新开启电源。 */ if (hub_is_port_power_switchable(hub) && !port_is_power_on(hub, portstatus) && !port_dev->port_owner) set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); if (portstatus & USB_PORT_STAT_ENABLE) goto done; return; } if (hub_is_superspeed(hub->hdev)) unit_load = 150; else unit_load = 100; status = 0; for (i = 0; i < SET_CONFIG_TRIES; i++) { /* 为每次尝试重新分配内存,因为对于前一个内存的引用可能会以各种方式逃逸 */ udev = usb_alloc_dev(hdev, hdev->bus, port1); if (!udev) { dev_err(&port_dev->dev, "couldn't allocate usb_device\n"); goto done; } usb_set_device_state(udev, USB_STATE_POWERED); udev->bus_mA = hub->mA_per_port; udev->level = hdev->level + 1; udev->wusb = hub_is_wusb(hub); /* 只有USB 3.0设备连接到超级速度集线器。 */ if (hub_is_superspeed(hub->hdev)) udev->speed = USB_SPEED_SUPER; else udev->speed = USB_SPEED_UNKNOWN; choose_devnum(udev); if (udev->devnum <= 0) { status = -ENOTCONN; /* 不要重试 */ goto loop; } /* 重置(非USB 3.0设备)并获取描述符 */ usb_lock_port(port_dev); status = hub_port_init(hub, udev, port1, i); usb_unlock_port(port_dev); if (status < 0) goto loop; /* 检测设备的quirks */ usb_detect_quirks(udev); if (udev->quirks & USB_QUIRK_DELAY_INIT) msleep(1000); /* 连续的总线供电集线器不可靠;它们可能会违反电压降预算。如果新的子设备有“供电”LED,则用户应该注意到我们没有启用它(无需阅读syslog),即使父设备没有每个端口的LED也可以。 */ if (udev->descriptor.bDeviceClass == USB_CLASS_HUB && udev->bus_mA <= unit_load) { u16 devstat; status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstat); if (status) { dev_dbg(&udev->dev, "get status %d ?\n", status); goto loop_disable; } if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) { dev_err(&udev->dev, "can't connect bus-powered hub " "to this port\n"); if (hub->has_indicators) { hub->indicator[port1-1] = INDICATOR_AMBER_BLINK; queue_delayed_work( system_power_efficient_wq, &hub->leds, 0); } status = -ENOTCONN; /* 不要重试 */ goto loop_disable; } } /* check for devices running slower than they could */ if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200 // 如果设备支持 USB 2.0 或更高版本 && udev->speed == USB_SPEED_FULL // 如果设备速度为全速 && highspeed_hubs != 0) // 如果存在高速 USB hub check_highspeed (hub, udev, port1); // 检查是否需要升级到高速 /* Store the parent's children[] pointer. At this point * udev becomes globally accessible, although presumably * no one will look at it until hdev is unlocked. */ // 存储父设备的 children[] 指针。此时,udev 变为全局可访问,尽管可能没有人会在 hdev 解锁之前查看它。 status = 0; mutex_lock(&usb_port_peer_mutex); /* We mustn't add new devices if the parent hub has * been disconnected; we would race with the * recursively_mark_NOTATTACHED() routine. */ // 如果父 USB hub 已断开连接,则不能添加新设备;我们将与 recursively_mark_NOTATTACHED() 例程竞争。 spin_lock_irq(&device_state_lock); if (hdev->state == USB_STATE_NOTATTACHED) // 如果父 USB hub 已断开连接 status = -ENOTCONN; // 设置状态为未连接 else port_dev->child = udev; // 将 udev 存储到 port_dev 的 child 中 spin_unlock_irq(&device_state_lock); mutex_unlock(&usb_port_peer_mutex); /* Run it through the hoops (find a driver, etc) */ // 运行它通过 hoops(查找驱动程序等) if (!status) { status = usb_new_device(udev); // 新建 USB 设备 if (status) { // 如果新建设备失败 mutex_lock(&usb_port_peer_mutex); spin_lock_irq(&device_state_lock); port_dev->child = NULL; // 将 port_dev 的 child 设置为 NULL spin_unlock_irq(&device_state_lock); mutex_unlock(&usb_port_peer_mutex); } else { // 如果新建设备成功 if (hcd->usb_phy && !hdev->parent) // 如果存在 USB 物理层并且 hdev 不是根 USB hub usb_phy_notify_connect(hcd->usb_phy, udev->speed); // 通知 USB 物理层连接成功 } } // 获取 USB hub 剩余电源预算 status = hub_power_remaining(hub); if (status) dev_dbg(hub->intfdev, "%dmA power budget left\n", status); return; loop_disable: // 禁用 USB hub 端口 hub_port_disable(hub, port1, 1); loop: // 重新初始化 USB 设备的端点 0 usb_ep0_reinit(udev); // 释放 USB 设备编号 release_devnum(udev); // 释放 USB 设备 hub_free_dev(udev); // 释放 USB 设备引用计数 usb_put_dev(udev); // 如果状态为未连接或不支持,则跳出循环 if ((status == -ENOTCONN) || (status == -ENOTSUPP)) break; } // 如果存在父 USB hub 或者驱动程序没有移交端口或者驱动程序移交端口失败 if (hub->hdev->parent || !hcd->driver->port_handed_over || !(hcd->driver->port_handed_over)(hcd, port1)) { // 如果状态不是未连接或未定义 if (status != -ENOTCONN && status != -ENODEV) // 打印错误信息 dev_err(&port_dev->dev, "unable to enumerate USB device\n"); } done: // 禁用 USB hub 端口 hub_port_disable(hub, port1, 1); // 如果驱动程序释放端口并且 hdev 不是根 USB hub if (hcd->driver->relinquish_port && !hub->hdev->parent) // 驱动程序释放端口 hcd->driver->relinquish_port(hcd, port1); }
hub_port_connect()函数的作用是处理USB hub端口的连接操作。
函数首先检查当前端口上是否已经连接了USB设备,如果有,会断开连接。接下来,检查连接状态和变化信息,进行去抖动操作。如果连接状态或使能状态发生了变化,则进行去抖动以确保稳定连接。
如果端口没有连接或设备已被移除,则会执行相应的操作并返回。
如果端口连接正常,则根据USB hub的类型和设备的属性进行初始化设置。然后为USB设备分配设备号、初始化端点等操作。
如果设备是USB 2.0设备且连接到高速USB hub上,则检查设备是否可以以更高的速度运行。
然后,将新连接的设备添加到父USB hub的子设备列表中,并通知USB物理层设备连接成功。
最后,禁用端口并释放相关资源,如果驱动程序支持端口释放,则进行相应的处理。
总体而言,hub_port_connect()函数用于处理USB hub端口的连接操作,包括断开连接、去抖动、初始化设备和端口等操作。它是USB hub驱动程序中的重要函数之一。
usb_new_device
hub_probe->hub_event->port_event->hub_port_connect_change->hub_port_connect->usb_new_device
usb_new_device()函数的作用是为USB设备进行初始化并与合适的驱动程序进行匹配。
该函数在以下情况下被调用:
当一个USB设备被连接到USB总线上时,USB核心会检测到设备的存在并创建一个
usb_device结构体来表示该设备。
当设备被检测到后,USB核心会调用
usb_new_device()函数为该设备进行初始化,并为设备分配资源。
初始化过程中,USB核心会遍历已加载的驱动程序,尝试将设备与合适的驱动程序进行匹配。
如果找到了适合的驱动程序,USB核心会调用该驱动程序的
probe()函数来完成设备的特定初始化和配置。
int usb_new_device(struct usb_device *udev) { int err; if (udev->parent) { /* 初始化非根集线器设备唤醒为禁用状态; * 设备(非)配置控制唤醒能力 * sysfs power/wakeup 控制唤醒启用/禁用 */ device_init_wakeup(&udev->dev, 0); } /* 告诉运行时-PM框架设备处于活动状态 */ pm_runtime_set_active(&udev->dev); pm_runtime_get_noresume(&udev->dev); pm_runtime_use_autosuspend(&udev->dev); pm_runtime_enable(&udev->dev); /* 默认情况下,禁止所有设备的自动挂起。它将被允许在绑定期间用于集线器。 */ usb_disable_autosuspend(udev); err = usb_enumerate_device(udev); /* 读取描述符 */ 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))); /* 导出usbdev设备节点供libusb使用 */ udev->dev.devt = MKDEV(USB_DEVICE_MAJOR, (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); /* 告诉世界! */ 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); // 启用异步挂起 /* 检查集线器或固件是否将此端口标记为不可移除 */ if (udev->parent) set_usb_port_removable(udev); // 设置USB端口是否可移除 /* 注册设备。设备驱动程序负责配置设备并调用添加设备通知器链(由usbfs和可能的其他程序使用)。 */ err = device_add(&udev->dev); // 添加设备 if (err) { dev_err(&udev->dev, "can't device_add, error %d\n", err); // 添加设备失败 goto fail; } /* 为子设备和USB端口设备之间创建链接文件 */ 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设备并进行一系列配置和设置。以下是该函数的概括:
如果设备不是根集线器设备,将设备唤醒功能禁用。
设置设备为活动状态,并启用运行时电源管理。
禁用设备的自动挂起。
调用
usb_enumerate_device()函数读取设备描述符。
导出USB设备节点供其他程序使用。
发送设备连接通知。
如果设备有序列号、产品名称或制造商信息,添加设备随机性。
启用异步挂起。
如果设备有父集线器,设置USB端口是否可移除。
注册设备并添加到设备链表中。
如果设备有父集线器,创建链接文件以连接子设备和USB端口设备。
创建端点设备。
标记设备的最后一次活动时间。
唤醒设备并挂起,启用自动挂起。
如果发生错误,设置设备状态为未连接,并禁用设备运行时。
函数返回错误码来指示操作的成功与否。
总的来说,这个函数负责对USB设备进行初始化、配置和设置,并在必要时与父集线器和其他设备建立链接。
announce_device
hub_probe->hub_event->port_event->hub_port_connect_change->hub_port_connect->usb_new_device->announce_device
发现新的 USB 设备时,打印设备信息
/** * announce_device - 发现新的 USB 设备时,打印设备信息 * @udev: 新发现的 USB 设备 */ static void announce_device(struct usb_device *udev) { dev_info(&udev->dev, "New USB device found, idVendor=%04x, idProduct=%04x\n", le16_to_cpu(udev->descriptor.idVendor), le16_to_cpu(udev->descriptor.idProduct)); 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); // 打印设备的序列号 }
接如usb摄像头时对应打印信息如下
如果文章对您有帮助,点赞👍支持,感谢🤝