第 13 章。USB 设备
目录
13.1. 简介
通用串行总线 (USB) 是一种将设备连接到个人电脑的新方式。总线架构具有双向通信功能,作为对设备变得更智能以及需要与主机进行更多交互的响应而开发。所有当前的 PC 芯片组都包含 USB 支持,因此在所有最近制造的 PC 中都可用。苹果推出仅支持 USB 的 iMac 是促使硬件制造商生产其设备的 USB 版本的主要动力。未来的 PC 规范规定,PC 上的所有传统连接器都应替换为一个或多个 USB 连接器,从而提供通用的即插即用功能。在 NetBSD 的早期阶段就提供了对 USB 硬件的支持,由 Lennart Augustsson 为 NetBSD 项目开发。该代码已移植到 FreeBSD,我们目前维护一个共享代码库。对于 USB 子系统的实现,USB 的许多特性都非常重要。
Lennart Augustsson 为 NetBSD 项目完成了大部分 USB 支持的实现。感谢他做出的如此巨量的贡献。还要感谢 Ardy 和 Dirk 对本文的评论和校对。
设备可以连接到电脑上的端口,也可以连接到称为集线器的设备上,形成树状的设备结构。
设备可以在运行时连接和断开连接。
设备可以暂停自身并触发主机系统的恢复
由于设备可以从总线供电,因此主机软件必须跟踪每个集线器的电源预算。
不同设备类型对服务质量的不同要求,加上最多可以连接到同一个总线的 126 个设备,要求对共享总线上的传输进行适当的调度,以充分利用可用的 12Mbps 带宽。(USB 2.0 可超过 400Mbps)
设备很智能,包含易于访问的有关自身的信息
USB 子系统及其连接设备的驱动程序开发得到了已开发和将要开发的规范的支持。这些规范可从 USB 主页公开获得。苹果公司一直大力推动基于标准的驱动程序,方法是在其操作系统 MacOS 中提供通用类别的驱动程序,并阻止使用单独的驱动程序来支持每个新设备。本章尝试汇集基本了解 FreeBSD/NetBSD 中 USB 2.0 实现堆栈的基本信息。但是,建议将其与相关的 2.0 规范和其他开发人员资源一起阅读
USB 2.0 规范(http://www.usb.org/developers/docs/usb20_docs/)
通用主机控制器接口 (UHCI) 规范(ftp://ftp.netbsd.org/pub/NetBSD/misc/blymn/uhci11d.pdf)
开放主机控制器接口 (OHCI) 规范(ftp://ftp.compaq.com/pub/supportinformation/papers/hcir1_0a.pdf)
USB 主页的开发者部分(http://www.usb.org/developers/)
13.1.1. USB 堆栈的结构
FreeBSD 中的 USB 支持可以分为三层。最底层包含主机控制器驱动程序,它提供与硬件及其调度设施的通用接口。它支持硬件的初始化、传输的调度以及已完成和/或失败传输的处理。每个主机控制器驱动程序都实现了一个虚拟集线器,提供与控制机器背面的根端口的寄存器无关的访问。
中间层处理设备的连接和断开连接、设备的基本初始化、驱动程序选择、通信通道(管道)以及资源管理。此服务层还控制默认管道以及通过这些管道传输的设备请求。
最顶层包含支持特定(类别)设备的各个驱动程序。这些驱动程序实现通过除默认管道以外的其他管道使用的协议。它们还实现附加功能,以使设备可供内核或用户空间的其他部分使用。它们使用服务层公开的 USB 驱动程序接口 (USBDI)。
13.2. 主机控制器
主机控制器 (HC) 控制总线上的数据包传输。使用 1 毫秒的帧。在每帧的开始,主机控制器会生成一个帧开始 (SOF) 数据包。
SOF 数据包用于同步到帧的开始并跟踪帧号。在每帧中,数据包都会被传输,要么是从主机到设备(输出),要么是从设备到主机(输入)。传输始终由主机启动(轮询传输)。因此,每个 USB 总线只能有一个主机。每个数据包的传输都包含一个状态阶段,在此阶段,数据包的接收者可以返回 ACK(确认接收)、NAK(重试)、STALL(错误状况)或无(数据包损坏阶段、设备不可用或断开连接)。USB 2.0 规范的第 8.5 节更详细地解释了数据包的详细信息。USB 总线上可以发生四种不同类型的传输:控制、块、中断和等时。传输类型及其特性将在下面描述。
USB 总线上的设备与设备驱动程序之间的大量传输将被主机控制器或 HC 驱动程序拆分为多个数据包。
默认端点的设备请求(控制传输)是特殊的。它们包含两个或三个阶段:SETUP、DATA(可选)和STATUS。设置数据包被发送到设备。如果存在数据阶段,数据包的方向在设置数据包中给出。状态阶段的方向与数据阶段的方向相反,或者如果不存在数据阶段,则为 IN。主机控制器硬件还提供寄存器,其中包含根端口的当前状态以及自上次复位状态更改寄存器以来发生的更改。如 USB 规范中所述,可以通过虚拟集线器访问这些寄存器。虚拟集线器必须符合该规范第 11 章中给出的集线器设备类。它必须提供一个默认管道,通过该管道可以向其发送设备请求。它返回标准和集线器类特定的描述符集。它还应提供一个中断管道,用于报告其端口发生的更改。目前,有两种可用的主机控制器规范:来自英特尔的通用主机控制器接口 (UHCI) 和来自康柏、微软和国家半导体的开放主机控制器接口 (OHCI)。UHCI 规范旨在通过要求主机控制器驱动程序提供每帧传输的完整计划来降低硬件复杂性。OHCI 类型控制器通过提供更抽象的接口并自行完成大量工作,更加独立。
13.2.1. UHCI
UHCI 主机控制器维护一个帧列表,其中包含指向每帧数据结构的 1024 个指针。它理解两种不同的数据类型:传输描述符 (TD) 和队列头 (QH)。每个 TD 代表要与设备端点通信的数据包。QH 是一种将 TD(和 QH)组合在一起的方式。
每次传输包含一个或多个数据包。UHCI 驱动程序将大型传输拆分为多个数据包。对于每次传输(除了等时传输之外),都会分配一个 QH。对于每种类型的传输,这些 QH 都被收集到该类型的 QH 中。等时传输必须首先执行,因为它们具有固定的延迟要求,并且由帧列表中的指针直接引用。最后一个等时 TD 引用该帧的中断传输的 QH。所有中断传输的 QH 都指向控制传输的 QH,而控制传输的 QH 又指向批量传输的 QH。下图概述了这一点。
这导致在每帧中运行以下计划。从帧列表中获取当前帧的指针后,控制器首先执行该帧中所有等时数据包的 TD。这些 TD 中的最后一个引用该帧的中断传输的 QH。然后,主机控制器将从该 QH 下降到各个中断传输的 QH。完成该队列后,中断传输的 QH 将将控制器引用到所有控制传输的 QH。它将执行那里调度的所有子队列,然后执行在批量 QH 中排队的所有传输。为了便于处理已完成或失败的传输,硬件会在每帧结束时生成不同类型的中断。在传输的最后一个 TD 中,中断完成位由 HC 驱动程序设置,以在传输完成后标记中断。如果 TD 达到其最大错误计数,则会标记错误中断。如果 TD 中的短数据包检测位被设置,并且传输的数据少于设置的数据包长度,则会标记此中断以通知控制器驱动程序传输已完成。查找哪个传输已完成或产生错误是主机控制器驱动程序的任务。当调用中断服务例程时,它将找到所有已完成的传输并调用其回调函数。
有关更详细的描述,请参阅 UHCI 规范。
13.2.2. OHCI
对 OHCI 主机控制器进行编程要简单得多。控制器假设一组端点可用,并且知道调度优先级以及帧中传输类型的顺序。主机控制器使用的主要数据结构是端点描述符 (ED),一个传输描述符 (TD) 队列附加到该描述符。ED 包含端点允许的最大数据包大小,控制器硬件会将其拆分为数据包。数据缓冲区的指针在每次传输后更新,并且当开始指针和结束指针相等时,TD 会被退回到完成队列。四种类型的端点(中断、等时、控制和批量)都有自己的队列。控制和批量端点分别排队到自己的队列中。中断 ED 排列在一个树中,树中的级别定义了它们运行的频率。
主机控制器在每帧中运行的计划如下。控制器将首先运行非周期性控制和批量队列,直到 HC 驱动程序设置的时间限制。然后,通过使用帧号的低 5 位作为中断 ED 树级别 0 的索引来运行该帧号的中断传输。在这个树的末尾,等时 ED 连接起来,然后遍历它们。等时 TD 包含传输应在其中运行的第一帧的帧号。在所有周期性传输都运行完毕后,控制和批量队列将再次被遍历。周期性地调用中断服务例程来处理完成队列,并为每个传输调用回调函数,并重新调度中断和等时端点。
有关更详细的描述,请参阅 UHCI 规范。中间层以受控的方式提供对设备的访问,并维护不同驱动程序和服务层正在使用的资源。该层负责以下方面
设备配置信息
与设备通信的管道
探测、附加和分离设备。
13.3. USB 设备信息
13.3.1. 设备配置信息
每个设备提供不同级别的配置信息。每个设备都具有一种或多种配置,其中一种在探测/附加期间被选中。配置提供电源和带宽要求。在每个配置中,可以有多个接口。设备接口是端点的集合。例如,USB 扬声器可以有一个用于音频数据的接口(音频类)和一个用于旋钮、刻度盘和按钮的接口(HID 类)。配置中的所有接口同时处于活动状态,并且可以被不同的驱动程序附加。每个接口可以有备用设置,提供不同的服务质量参数。例如,在相机中,这用于提供不同的帧大小和每秒帧数。
在每个接口中,可以指定 0 个或多个端点。端点是与设备通信的单向访问点。它们提供缓冲区来临时存储来自设备的传入或传出数据。每个端点在配置中都有一个唯一的地址,即端点的编号加上其方向。默认端点(端点 0)不属于任何接口,并且在所有配置中都可用。它由服务层管理,设备驱动程序无法直接访问。
这种分层配置信息由设备中的一组标准描述符来描述(参见 USB 规范的第 9.6 节)。可以通过 Get Descriptor Request 请求它们。服务层缓存这些描述符,以避免在 USB 总线上进行不必要的传输。通过函数调用提供对描述符的访问。
设备描述符:有关设备的一般信息,例如供应商、产品和修订版本 ID、支持的设备类、子类和协议(如果适用)、默认端点的最大数据包大小等。
配置描述符:此配置中的接口数量、支持的挂起和恢复功能以及电源要求。
接口描述符:接口类、子类和协议(如果适用)、接口的备用设置数量以及端点数。
端点描述符:端点地址、方向和类型、支持的最大数据包大小以及如果类型是中断端点,则轮询频率。默认端点(端点 0)没有描述符,它从不在接口描述符中计数。
字符串描述符:在其他描述符中,为某些字段提供了字符串索引。这些可用于检索描述性字符串,可能使用多种语言。
类规范可以添加它们自己的描述符类型,这些类型可以通过 Get Descriptor Request 获得。
管道与设备上端点的通信通过所谓的管道进行。驱动程序将传输提交到端点上的管道,并提供一个回调函数,在传输完成或失败时调用该回调函数(异步传输)或等待完成(同步传输)。端点的传输在管道中被序列化。传输可以完成、失败或超时(如果设置了超时)。传输有两种类型的超时。超时可能由于 USB 总线上的超时(毫秒)而发生。这些超时被视为失败,可能是由于设备断开连接造成的。第二种形式的超时在软件中实现,并且在传输在指定时间内(秒)未完成时触发。这些是由设备对传输的数据包进行否定确认 (NAK) 造成的。造成这种情况的原因是设备未准备好接收数据、缓冲区不足或溢出或协议错误。
如果通过管道的传输大于关联端点描述符中指定的最大数据包大小,则主机控制器 (OHCI) 或 HC 驱动程序 (UHCI) 将将传输拆分为最大数据包大小的数据包,最后一个数据包可能小于最大数据包大小。
有时,设备返回的数据少于请求的数据并不成问题。例如,对调制解调器的批量传输可能会请求 200 字节数据,但调制解调器当时只有 5 字节可用。驱动程序可以设置短数据包 (SPD) 标志。它允许主机控制器即使传输的数据量小于请求的数据量也能接受数据包。此标志仅对输入传输有效,因为要发送到设备的数据量总是预先知道的。如果设备在传输过程中出现不可恢复的错误,则管道将被阻塞。在接受或发送更多数据之前,驱动程序需要解决阻塞的原因并通过默认管道发送清除端点停止设备请求来清除端点停止状态。默认端点永远不会停止。
有四种不同的端点类型和对应的管道:- 控制管道/默认管道:每个设备都有一个控制管道,连接到默认端点(端点 0)。管道承载设备请求和相关数据。默认管道传输和其他管道传输之间的区别在于,传输协议在 USB 规范中描述。这些请求用于重置和配置设备。USB 规范第 9 章提供了每个设备必须支持的基本命令集。设备类规范可以扩展此管道上的支持命令,以支持其他功能。
批量管道:这是 USB 等同于原始传输介质。
中断管道:主机向设备发送数据请求,如果设备没有要发送的数据,它将对数据包进行 NAK。中断传输以创建管道时指定的频率进行调度。
等时管道:这些管道 предназначены для等时数据,例如视频或音频流,具有固定延迟,但没有保证的交付。当前实现中提供了一些对这种类型管道的支持。如果传输过程中发生错误,或者设备由于例如缺乏存储传入数据的缓冲空间而对数据包进行否定确认 (NAK),则控制、批量和中断传输中的数据包将被重试。但是,等时数据包在交付失败或数据包 NAK 时不会重试,因为这可能会违反时间约束。
在创建管道时计算必要的带宽的可用性。传输在 1 毫秒的帧内进行调度。USB 规范第 5.6 节 [ 2] 规定了帧内的带宽分配。等时传输和中断传输允许在帧内占用最多 90% 的带宽。控制和批量传输的数据包在所有等时和中断数据包之后进行调度,并将消耗所有剩余的带宽。
有关传输调度和带宽回收的更多信息,请参阅 USB 规范第 5 章、UHCI 规范第 1.3 节和 OHCI 规范第 3.4.2 节。
13.4. 设备探测和连接
在集线器发出新设备已连接的通知后,服务层将打开端口,为设备提供 100 mA 电流。此时,设备处于其默认状态,并监听设备地址 0。服务层将继续通过默认管道检索各种描述符。之后,它将发送一个设置地址请求以将设备从默认设备地址(地址 0)移开。多个设备驱动程序可能能够支持设备。例如,调制解调器驱动程序可能能够通过 AT 兼容性接口支持 ISDN TA。但是,针对该特定型号 ISDN 适配器的驱动程序可能能够为该设备提供更好的支持。为了支持这种灵活性,探测器会返回优先级,表明其支持级别。对产品的特定版本的支持排名最高,而通用驱动程序的优先级最低。如果在一个配置中有多个接口,多个驱动程序也可以连接到一个设备。每个驱动程序只需要支持一部分接口。
为新连接的设备探测驱动程序会首先检查特定于设备的驱动程序。如果未找到,探测代码将遍历所有支持的配置,直到驱动程序在某个配置中连接。为了支持在不同接口上有多个驱动程序的设备,探测代码将遍历配置中尚未被驱动程序声明的所有接口。忽略超过集线器电源预算的配置。在连接期间,驱动程序应将设备初始化为其适当状态,但不要重置它,因为这将使设备从总线上断开连接并重新启动对其的探测过程。为了避免消耗不必要的带宽,不应该在连接时声明中断管道,而应该将分配管道推迟到文件打开并且实际使用数据时。当文件关闭时,即使设备可能仍然连接,也应关闭管道。
13.4.1. 设备断开连接和分离
设备驱动程序应预计在与设备的任何事务过程中都会收到错误。USB 的设计支持并鼓励随时断开设备。驱动程序应确保在设备消失时采取正确的措施。
此外,已断开连接并重新连接的设备不会以相同的设备实例重新连接。这在将来可能会发生变化,届时更多设备将支持序列号(请参阅设备描述符),或者已开发出定义设备身份的其他方法。
集线器在传递到集线器驱动程序的中断数据包中发出设备断开连接的信号。状态更改信息表明哪个端口看到了连接更改。将调用连接到该端口上的设备的所有设备驱动程序的设备分离方法,并清理结构。如果端口状态指示在此期间有设备连接到该端口,则将启动探测和连接设备的过程。设备重置将在集线器上产生断开连接-连接序列,并将按上述方式处理。
13.5. USB 驱动程序协议信息
USB 规范没有定义在默认管道以外的管道上使用的协议。有关这方面的信息,可以从各种来源获取。最准确的来源是 USB 主页上的开发者部分。从这些页面,越来越多的设备类规范可用。这些规范指定从驱动程序的角度来看,符合规范的设备应该是怎样的,它需要提供的基本功能以及要通过通信通道使用的协议。USB 规范包括集线器类的描述。已创建人类接口设备 (HID) 的类规范,以满足键盘、平板电脑、条形码阅读器、按钮、旋钮、开关等的需要。第三个例子是用于大容量存储设备的类规范。有关设备类的完整列表,请参阅 USB 主页上的开发者部分。
但是,对于许多设备而言,协议信息尚未发布。有关正在使用的协议的信息可能可以从制造设备的公司获得。一些公司要求您在提供规范之前签署保密协议 (NDA)。在大多数情况下,这将排除使驱动程序开源。
另一个很好的信息来源是 Linux 驱动程序源代码,因为许多公司已开始为其设备提供用于 Linux 的驱动程序。始终建议与这些驱动程序的作者联系,以获取他们的信息来源。
示例:人类接口设备 键盘、鼠标、平板电脑、按钮、旋钮、拨号盘等人类接口设备的规范在其他设备类规范中被引用,并在许多设备中使用。
例如,音频扬声器为数字到模拟转换器提供端点,并且可能提供一个额外的管道用于麦克风。它们还在一个单独的接口中提供一个 HID 端点,用于设备正面的按钮和旋钮。监视器控制类也是如此。通过可用的内核和用户空间库以及 HID 类驱动程序或通用驱动程序,可以轻松构建对这些接口的支持。另一个用作一个配置内由不同设备驱动程序驱动的接口示例的设备是带内置传统鼠标端口的廉价键盘。为了避免在设备中包含 USB 集线器硬件的成本,制造商将从键盘背面 PS/2 端口接收的鼠标数据和来自键盘的按键组合到同一配置中的两个单独接口中。鼠标和键盘驱动程序分别连接到相应的接口,并将管道分配到两个独立的端点。
示例:固件下载 许多已开发的设备基于通用处理器,并添加了额外的 USB 内核。由于 USB 设备的驱动程序和固件的开发还很新,因此许多设备需要在连接后下载固件。
遵循的程序很简单。设备通过供应商 ID 和产品 ID 识别自身。第一个驱动程序会探测并连接到它,并将固件下载到它。之后,设备会软重置自身,驱动程序会分离。短暂暂停后,设备会在总线上宣布其存在。设备将更改其供应商/产品/修订版 ID 以反映已提供固件的事实,因此第二个驱动程序将探测它并连接到它。
这类设备的一个例子是基于 EZ-USB 芯片的 ActiveWire I/O 板。该芯片提供通用固件下载器。下载到 ActiveWire 板的固件会更改版本 ID。然后它将对 EZ-USB 芯片的 USB 部分进行软重置,以断开与 USB 总线的连接,然后再重新连接。
例如:大容量存储设备。对大容量存储设备的支持主要基于现有的协议。Iomega USB Zipdrive 基于其驱动器的 SCSI 版本。SCSI 命令和状态消息被封装在块中,并通过批量管道传输到设备和从设备传输,模拟了 USB 线上的 SCSI 控制器。ATAPI 和 UFI 命令以类似的方式支持。
大容量存储规范支持两种不同的命令块封装方式。最初的尝试是基于通过默认管道发送命令和状态,并使用批量传输来移动主机和设备之间的数据。基于经验,设计了第二种方法,它基于封装命令和状态块,并将它们通过批量输出和输入端点发送。规范精确地规定了何时必须执行哪些操作,以及在遇到错误条件时必须执行的操作。为这些设备编写驱动程序时最大的挑战是将基于 USB 的协议融入到现有的对大容量存储设备的支持中。CAM 提供了一些钩子以相当直接的方式实现这一点。ATAPI 比较复杂,因为历史上 IDE 接口从来没有很多不同的外观。
对 Y-E Data 生产的 USB 软盘的支持再次变得不那么直接,因为已经设计了新的命令集。
最后修改时间:2024 年 3 月 9 日,作者:Danilo G. Baio