Linux USB 概述

作者:Rami Rosen

USB 子系统基于消息传递事务。消息称为 URB,代表 USB 请求块 (USB request block)。URB 通过调用 usb_submit_urb 方法发送,struct urb *urb, int mem_flags)。这是一个异步调用,并立即返回。URB 被放入队列中,稍后到达完成处理程序。完成处理程序是 URB 结构的一个成员,称为 complete,是指向 URB 结构中函数的指针。在完成处理程序中,您可以检查 urb -> status 以查看是否检测到任何错误。

要取消挂起的请求,请使用usb_unlink_urb()。URB 通过调用 usb_alloc_urb() 分配,并通过调用 usb_free_urb() 释放。有三种辅助方法可用于帮助填充 URB:usb_fill_control_urb()、usb_fill_bulk_urb 和 usb_fill_int_urb。

URB 结构位于 include/usb/usb.h 中,它是定义 USB 接口的两个最重要的头文件之一。第二个是 include/linux/usb_ch9.h。另一个小文件也称为 usb.h,可以在 usb/core 中找到,但在本文上下文中并不重要。

名为 usb_ch9.h 的头文件保存了 USB 的常量和结构。该名称来自 USB 2.0 规范的第 9 章。目前,USB 有两个规范,2.0 和 1.1。USB 2.0 以高速运行,定义为 60MB/s (480Mb/s),比 USB 全速快 40 倍。USB 1.1 要么以全速 1.5MB/s (12Mb/s) 运行,要么以低速 1.5Mb/s 运行。当您将高速设备连接到 USB 1.1 系统时,它们以 USB 1.1 速度运行。

USB_SPEED_LOW、USB_SPEED_FULL 和 USB_SPEED_HIGH 常量在 usb_ch9.h 中定义。这些常量在内核的 USB 源代码中随处可见。

存在四种类型的传输:控制传输、批量传输、中断传输和高速同步 (ISO) 传输。USB 网络摄像头通常使用 ISO 传输,您很少在那里找到批量传输的用途。

USB 主机控制器

在内核源代码树的 /drivers/usb/host 下,您可以找到定义的五种类型的 USB 主机控制器

  1. 用于 USB2.0 的 EHCI,EHCI 代表增强型主机控制器接口 (enhanced host controller interface)。

  2. OHCI 或 OpenHCI,开放式主机控制器接口 (open host controller interface)。根据 /usb/host/Kconfig,OHCI “在硬件方面比 Intel 的 UHCI 规范做得更多”。例如,使用 OHCI 发送 4KB 缓冲区需要一个 TD,涵盖 64+ 个数据包;而使用 UHCI,每个数据包都需要一个 TD。此外,OHCI 为控制、批量和周期性传输设置了单独的调度,而 UHCI 只有一个。

  3. UHCI,通用主机控制器接口 (universal host controller interface),起源于英特尔。带有英特尔 PCI 芯片组的主板通常使用此标准。

  4. SL811 主机控制器,由 Cypress 制造。它具有 256MB 的内部 SRAM 缓冲区。

  5. SA-1111 主机控制器,这些控制器的驱动程序位于 /drivers/usb/host 下。它们有时被称为 HCD,代表主机控制器驱动程序 (host controller driver)。

那么 USB 初始化中有什么呢?让我们看一下 /drivers/usb/core/usb.c 以获取该信息。我们调用了六个初始化例程,但其中只有三个是子子系统的一部分。

  1. bus_register(&usb_bus_type):bus_register() 方法不是 USB 子系统的一部分。它是一个通用的总线方法,其实现位于 /drivers/base/bus.c。

  2. usb_host_init():这会调用 class_register(),但它不是 USB 子系统的一部分。它的实现存储在 /drivers/base/class.c 中。

  3. usb_major_init():这会调用 register_chre_dev() 以及 devfs_mk_dir("usb") 和 class_register()。USB 的主设备号是 180。/dev/usb 下的所有条目都具有主设备号 180,除了 /dev/ttyUSB*,它们的主设备号为 188。对 class_register() 的调用是相对较新的,在 2.4 内核中不存在。

  4. usbfs_init():在大多数发行版中,USBFS(也称为 usbdevfs)都挂载在 /proc/bus 下。当然,这可以通过发出卸载 (umount)然后挂载 (mount)在不同的挂载点上或通过更改 /etc/fstab 来更改。

    USBFS 是一个文件系统,在某些方面类似于普通文件系统,例如 ext3 或 XFS。它们彼此相似,因为您调用 /drivers/usb/core/inode.c 中的 register_filesystem() 以注册文件系统,并且您传递一个指向 file_system_type 的指针。USBFS 的大多数相关代码都存储在 usb/core/inode.c 中,您可以在其中找到 usbfs_fill_super() 等方法,这些方法通常在其他文件系统的 super.c 中找到。您还可以在那里找到文件操作,包括 default_read_file() 和 default_write_file()。

    USBFS 负责创建和更新设备文件 /proc/bus/usb/devices。设备文件是动态更新的;如果您插入网络摄像头或任何其他 USB 设备并运行cat /proc/bus/usb/devices,您将发现一些配置条目已添加到设备文件中。同样,如果您移除网络摄像头并运行cat /proc/bus/usb/devices,这些设备将从设备文件中移除。

    设备文件的创建最终是通过从 dcache.c 调用常规文件系统方法来实现的。

  5. usb_hub_init():创建一个名为 khubd 的内核守护线程;它的实现在 /drivers/usb/core/hub.c 中。khubd 线程负责配置设备。

  6. driver_register():此方法不是 USB 子系统的一部分。它的实现可以在 /drivers/base/driver.c 中找到。

插入和拔出 USB 设备

USBcore 的九个强制性组成部分是:usb.c、hub.c、hcd.c、urb.c、message.c、config.c、file.c、buffer.c 和 sysfs.c。如果配置内核时,您将 CONFIG_USB_DEVICEFS 设置为 yes,则向 USBcore 列表添加另外三个项目:devio.c、inode.c 和 devices.c。inode.c 实现了 USBFS,它调用 register_filesystem。

khubd 是一个守护线程。创建它之后,我们调用 daemonize(),它会阻止所有信号。有关更多详细信息,请参阅 /kernel/exit.c 中的 daemonize() 实现。我们希望能够在清理时发送 kill 信号 (SIGKILL),因此我们在创建 khubd 后立即调用 allow_signal(SIGKILL) 来启用此功能。

通常,当您运行ps -aux并查看 khubd 进程时,您会看到它处于睡眠状态,用字母 S 表示。当我们将 USB 设备插入 USB 端口时,硬件层会启动一个中断;我们到达 hub_irq() 方法。hub 数据(由 struct usb_hub 表示)作为 URB 的一部分传递给 hub_irq() 方法,即 struct urb 的上下文成员。

一个名为 hub_event_list 的全局 hub 事件列表可用。如果此列表为空,我们将一个事件添加到此列表中,以便 khubd 线程可以处理它。我们还调用 wake_up(&khubd_wait),因为 khubd 处于等待状态。唤醒会导致我们调用 hub_events()。我们还到达主机控制器 IRQ。例如,当我在使用 OHCI 时,我到达 ohci_irq())。hub 驱动程序编写者承认,每次都重新启动列表以避免因删除此当前 hub 下游的 hub 而造成死锁并不是最有效的方法,但它是安全的。

当我们从 USB 端口拔出 USB 设备时,重复上述过程,直到我们到达 hub_events()。在那里,我们调用 hub_port_connect_change() 方法,该方法在 hub.c 中实现。此方法的第二个参数是端口号,因此此方法通过调用 usb_disconnect() 断开此端口上的所有现有设备。usb_disconnect(struct usb_device **pdev) 也是 usb.c 中实现的 USBcore 方法。

usb_disconnect() 调用 usb_disable_device(),它禁用 USB 设备的所有端点。它还通过调用 usbfs_remove_device(dev) 从 /proc/bus/usb/devices 中删除该 USB 设备端点。但是请注意,在此过程中不会调用 hub_disconnect()。hub_disconnect() 在我们执行rmmod ohci_hcd时调用,例如,当使用 OHCI 时。它也在启动时的探测过程中调用;更具体地说,hub_probe() 调用 hub_disconnect() 作为其返回之前的最后一步。/sys/usb/usb/drivers/hub 下的条目在拔出设备后不会被移除。例如,在rmmod ohci_hcd执行后移除,假设您正在使用 ohci_hcd。

工具和库

在使用 USB 子系统时,我发现一些工具和库特别有帮助。USBView 是 Greg Kroah-Hartman 编写的基于 GTK+ 1.2 的工具,可让您查看 USB 设备的特性。此工具显示插入 Linux 机器上的 USB 总线的设备的拓扑结构。当插入和拔出 USB 设备时,显示会实时更新和刷新。源代码也可用。

其他可能有帮助的工具和库包括 UsbSnoop,它是一种仅在 Windows 上运行的 USB 嗅探器。Pete Zaitcev 编写的一个名为 USBMon 的新补丁程序可用,可让您监控 USB 流量。最后,libusb 项目正在尝试创建一个库,用户级应用程序可以使用该库访问 USB 设备,而无需考虑操作系统。

Linux 的 USB 大容量存储驱动程序

我将以简要讨论 USB 大容量存储驱动程序来结束本文。此驱动程序旨在用于大容量存储 USB 设备,包括 USB 磁盘和 USB DiskOnKey。以下内容与 USB DiskOnKey 有关,但我假设 USB 磁盘的行为也大致相同。

要挂载 DiskOnKey,您首先应该创建一个挂载点,例如 /mnt/dok,然后运行

mount /dev/sda /mnt/dok

USB 大容量存储驱动程序的代码自然位于 drivers/usb/storage 下。模块名称为 usb-storage.ko;它使用 SCSI 仿真。

storage/usb.h 中的 us_data 结构可能是存储大容量存储中最重要的结构。我们使用的协议是透明 SCSI。传输层是批量传输,对应于 usb/transport.h 中的 US_PR_BULK。

SCSI 仿真的使用也出现在 ATAPI 设备中,例如 CD-RW 设备。问一个好问题是,我们为什么要使用这种仿真?为什么不使用 IDE 接口?我个人想知道原因是什么。对我来说,这似乎与 IDE 磁盘不是热插拔的而 USB 磁盘可以是热插拔的事实无关。

usb_stor_control_thread() 是一个控制 USB 设备的守护线程。它将 PF_NOFREEZE 标志添加到当前进程。除了此模块之外,PF_NOFREEZE 的使用仅出现在 Linux 内核源代码的 drivers 子树下的另外两个位置:block/loop.c 中的 loop_thread(void *data) 方法和 scsi/scsi_error.c 中的 scsi_error_handler(void *data)。PF_NOFREEZE 标志用于 Swsusp,它是 Linux 中的软件挂起 (software suspend),相当于 Windows 的休眠模式。

注意

本文中引用的代码来自 2.67 内核中的 USB 层;它在新版本中几乎没有变化。

Rami Rosen 是一位软件工程师,最近一直在为一家开发视频会议和 VoIP 解决方案的初创公司从事 Linux 软件工作。他是一位忠实的 Linux 内核黑客,并且他还热爱音乐、阅读和跑步。

加载 Disqus 评论