Linux USB 概述
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 传输,您很少在那里找到批量传输的用途。
在内核源代码树的 /drivers/usb/host 下,您可以找到定义的五种类型的 USB 主机控制器
用于 USB2.0 的 EHCI,EHCI 代表增强型主机控制器接口 (enhanced host controller interface)。
OHCI 或 OpenHCI,开放式主机控制器接口 (open host controller interface)。根据 /usb/host/Kconfig,OHCI “在硬件方面比 Intel 的 UHCI 规范做得更多”。例如,使用 OHCI 发送 4KB 缓冲区需要一个 TD,涵盖 64+ 个数据包;而使用 UHCI,每个数据包都需要一个 TD。此外,OHCI 为控制、批量和周期性传输设置了单独的调度,而 UHCI 只有一个。
UHCI,通用主机控制器接口 (universal host controller interface),起源于英特尔。带有英特尔 PCI 芯片组的主板通常使用此标准。
SL811 主机控制器,由 Cypress 制造。它具有 256MB 的内部 SRAM 缓冲区。
SA-1111 主机控制器,这些控制器的驱动程序位于 /drivers/usb/host 下。它们有时被称为 HCD,代表主机控制器驱动程序 (host controller driver)。
那么 USB 初始化中有什么呢?让我们看一下 /drivers/usb/core/usb.c 以获取该信息。我们调用了六个初始化例程,但其中只有三个是子子系统的一部分。
bus_register(&usb_bus_type):bus_register() 方法不是 USB 子系统的一部分。它是一个通用的总线方法,其实现位于 /drivers/base/bus.c。
usb_host_init():这会调用 class_register(),但它不是 USB 子系统的一部分。它的实现存储在 /drivers/base/class.c 中。
usb_major_init():这会调用 register_chre_dev() 以及 devfs_mk_dir("usb") 和 class_register()。USB 的主设备号是 180。/dev/usb 下的所有条目都具有主设备号 180,除了 /dev/ttyUSB*,它们的主设备号为 188。对 class_register() 的调用是相对较新的,在 2.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 调用常规文件系统方法来实现的。
usb_hub_init():创建一个名为 khubd 的内核守护线程;它的实现在 /drivers/usb/core/hub.c 中。khubd 线程负责配置设备。
driver_register():此方法不是 USB 子系统的一部分。它的实现可以在 /drivers/base/driver.c 中找到。
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 设备,而无需考虑操作系统。
我将以简要讨论 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 的休眠模式。
关于新的 USBMon 补丁程序的 Linux 内核邮件列表 (lkml) 线程
GNU/Linux usbnet 驱动程序,作者:David Brownell
“在不更改源代码的情况下修改动态库”。《Linux Journal》文章,作者:Greg Kroah-Hartman,他在其中解释了如何使用 libusb。
Linux 的 USB 大容量存储驱动程序。但是请注意,此站点上次更新是在 2002 年 7 月。
Rami Rosen 是一位软件工程师,最近一直在为一家开发视频会议和 VoIP 解决方案的初创公司从事 Linux 软件工作。他是一位忠实的 Linux 内核黑客,并且他还热爱音乐、阅读和跑步。