通用串行总线(USB)是一种将设备连接到个人计算机的新方法。总线架构具有双向通信功能,并作为设备变得更智能并需要与主机进行更多交互的响应而开发。USB 支持已包含在所有当前 PC 芯片组中,因此在所有最近构建的 PC 中都可以使用。苹果推出的仅支持 USB 的 iMac 对硬件制造商生产其设备的 USB 版本起到了重要的推动作用。未来的 PC 规范规定,PC 上的所有传统连接器都应该被一个或多个 USB 连接器取代,提供通用即插即用功能。USB 硬件的支持在 NetBSD 的早期阶段就已经可用,并由 Lennart Augustsson 为 NetBSD 项目开发。该代码已被移植到 FreeBSD,我们目前正在维护一个共享的代码库。为了实现 USB 子系统,USB 的许多特性都是重要的。
Lennart Augustsson 为 NetBSD 项目完成了大部分 USB 支持的实现。非常感谢他做出的大量工作。也要感谢 Ardy 和 Dirk 对本文的评论和校对。
设备直接连接到计算机上的ports,或连接到称为集线器的设备上,形成类似树状的设备结构。
设备可以在运行时连接和断开。
设备可以暂停自身并触发主机系统的恢复。
由于设备可以从总线供电,主机软件必须跟踪每个集线器的功率预算。
不同设备类型对服务质量的不同要求,加上最多可连接到同一总线的 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/)
FreeBSD 中的 USB 支持可以分为三个层。最底层包含主机控制器驱动程序,提供与硬件及其调度功能的通用接口。它支持硬件的初始化,传输的调度以及已完成和/或失败的传输的处理。每个主机控制器驱动程序实现了一个虚拟集线器,提供对控制机器背面根处的寄存器的硬件独立访问。
中间层处理设备的连接和断开,设备的基本初始化,驱动程序选择,通信通道(管道)和资源管理。此服务层还控制默认管道和通过它们传输的设备请求。
顶层包含支持特定(类别的)设备的各个驱动程序。这些驱动程序实现了在默认管道之外使用的协议。它们还实现了附加功能,使设备可供内核或用户区的其他部分使用。它们使用服务层提供的 USB 驱动程序接口(USBDI)。
主机控制器(HC)控制总线上数据包的传输。每隔 1 毫秒发送一帧。在每一帧的开始,主机控制器会生成一个帧起始(SOF)数据包。
SOF 数据包用于同步到帧的开始并跟踪帧号。在每帧内,数据包的传输可以是从主机到设备(输出),也可以是从设备到主机(输入)。传输始终由主机发起(轮询式传输)。因此,每个 USB 总线只能有一个主机。每个数据包的传输都有一个状态阶段,在该阶段数据的接收方可以返回 ACK(确认接收)、NAK(重试)、STALL(错误条件)或无回应(数据混乱阶段、设备不可用或已断开连接)。USB 2.0 规范的第 8.5 节更详细地解释了数据包的细节。USB 总线上可以发生四种不同类型的传输:控制传输,块传输,中断传输和等时传输。下面描述了各种传输类型及其特性。
USB 总线上设备与设备驱动程序之间的大型传输被主控制器或主控制器驱动程序分割成多个数据包。
设备请求(控制传输)到默认端点是特殊的。它们由两到三个阶段组成:设置(SETUP)、数据(可选)和状态(STATUS)。设置数据包被发送到设备。如果有数据阶段,数据包的方向在设置数据包中给出。状态阶段中的方向与数据阶段期间的方向相反,或者如果没有数据阶段,则为 IN。主控制器硬件还提供具有根ports当前状态和自上次状态更改寄存器重置以来发生的更改的寄存器。通过 USB 规范建议的虚拟化集线器提供对这些寄存器的访问。虚拟集线器必须符合该规范第 11 章中给出的集线器设备类。它必须提供一个默认管道,通过该管道可以将设备请求发送到它。它返回标准和集线器类别特定的描述符集。它还应提供一个中断管道,报告发生在其ports上的更改。目前有两种可用的主控制器规范:英特尔的通用主控制器接口(UHCI)和康柏、微软和国家半导体的开放主控制器接口(OHCI)。UHCI 规范旨在通过要求主控制器驱动程序为每个帧提供完整的传输计划来减少硬件复杂性。OHCI 类型控制器更加独立,通过提供一个更抽象的接口来自行完成大量工作。
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 规范以获取更详细的描述。
编程 OHCI 主机控制器要简单得多。控制器假定一组端点可用,并且知道帧中传输类型的调度优先级和顺序。主机控制器使用的主要数据结构是端点描述符(ED),附加到该端点描述符的是传输描述符(TD)队列。ED 包含端点允许的最大数据包大小,控制器硬件进行数据包分割。在每次传输后更新数据缓冲区指针,并且当起始指针和结束指针相同时,TD 将被放入完成队列。四种类型的端点(中断,等时,控制和块)都有自己的队列。控制和块端点各自排队。中断 ED 在树中排队,树中的级别定义了它们运行的频率。
主机控制器每帧运行的调度如下。控制器首先运行非周期性控制和块队列,直到由 HC 驱动程序设置的时间限制。然后运行该帧编号的中断传输,通过使用帧编号的低五位作为中断 ED 树级别 0 的索引。在这个树的末尾连接等时 ED,并依次遍历这些 ED。等时 TD 包含传输应在其中运行的第一帧的帧编号。运行所有周期性传输之后,再次遍历控制和块队列。定期调用中断服务例程来处理完成队列,并为每次传输调用回调函数并重新安排中断和等时端点。
查看 UHCI 规范以获取更详细的描述。中间层以受控方式访问设备,并维护不同驱动程序和服务层正在使用的资源。该层负责以下方面:
设备配置信息
用于与设备通信的管道
探测、连接和分离设备。
每个设备提供不同级别的配置信息。 每个设备都有一个或多个配置,在探测/附加期间选择一个配置。 配置提供功率和带宽要求。 在每个配置中,可以有多个接口。 设备接口是端点的集合。 例如,USB 扬声器可以具有用于音频数据(音频类)的界面和用于旋钮,拨号和按钮(HID 类)的界面。 配置中的所有接口同时处于活动状态,并且可以由不同的驱动程序附加。 每个接口可以有备用项,提供不同的服务质量参数。 例如相机可以用来提供不同的帧大小和每秒帧数。
在每个接口内,可以指定 0 个或更多个端点。端点是与设备通信的单向访问点。它们提供缓冲区,用于临时存储来自设备的传入或传出数据。每个端点在配置内有一个唯一地址,即端点号加上其方向。默认端点,端点 0,不属于任何接口,并且在所有配置中都可用。它由服务层管理,不直接提供给设备驱动程序。
此分层配置信息在设备中由一组标准描述符描述(请参阅 USB 规范的第 9.6 节)。它们可以通过获取描述符请求来请求。服务层会缓存这些描述符,以避免在 USB 总线上进行不必要的传输。通过函数调用提供对描述符的访问。
设备描述符:有关设备的一般信息,如供应商、产品和修订 ID,支持的设备类、子类和协议(如果适用),默认端点的最大包大小等。
配置描述符:此配置中的接口数量、支持的挂起和恢复功能以及电源要求。
接口描述符:接口类、子类和协议(如果适用)、接口的备用设置数量以及端点数量。
端点描述符:端点地址、方向和类型、支持的最大数据包大小以及轮询频率(如果类型为中断端点)。默认端点(端点 0)没有描述符,并且在接口描述符中永远不计数。
字符串描述符:在其他描述符中,为某些字段提供了字符串索引。这些索引可用于检索描述性字符串,可能是多种语言。
类规范可以添加它们自己的描述符类型,通过 GetDescriptor 请求可用。
管道通信:设备上的端点之间的流量通过所谓的管道进行。驱动程序向端点提交传输到管道,并提供在传输完成或失败时调用的回调(异步传输),或等待完成(同步传输)。端点的传输在管道中进行序列化。传输可以完成、失败或超时(如果设置了超时)。传输的超时有两种类型。传输可能由于 USB 总线上的超时(毫秒级)而发生。这些超时被视为失败,可能是由于设备断开连接。第二种形式的超时是在软件中实现的,当传输在指定的时间内未完成时触发(秒级)。这些超时是由设备否定(NAK)传输的数据包而触发的。造成这种情况的原因是设备尚未准备好接收数据,缓冲区下溢或上溢,或协议错误。
如果通过管道的传输大于关联端点描述符中指定的最大数据包大小,则主机控制器(OHCI)或 HC 驱动程序(UHCI)将将传输拆分为最大数据包大小的数据包,最后一个数据包可能小于最大数据包大小。
有时,设备返回的数据量少于请求的数据量并不是问题。例如,对于调制解调器的批量输入传输,可能请求 200 字节的数据,但调制解调器在那时只有 5 字节可用。驱动程序可以设置短数据包(SPD)标志。它允许主机控制器接受一个数据包,即使传输的数据量少于请求的数据量。该标志仅对输入传输有效,因为要发送到设备的数据量始终是预先知道的。如果在传输过程中设备发生不可恢复的错误,则管道将被停止。在接受或发送更多数据之前,驱动程序需要解决停止的原因,并通过在默认管道上发送清除端点停止设备请求来清除端点停止条件。默认端点不应该停止。
有四种不同类型的端点和相应的管道:- 控制管道/默认管道:每个设备有一个控制管道,连接到默认端点(端点 0)。该管道携带设备请求和相关数据。与通过默认管道和其他管道进行的传输之间的区别在于传输的协议在 USB 规范中描述。这些请求用于重置和配置设备。USB 规范的第 9 章提供了每个设备必须支持的基本命令集。在此管道上支持的命令可以通过设备类规范进行扩展,以支持附加功能。
大容量管道: 这是 USB 等效于原始传输媒介。
中断管道: 主机向设备发送数据请求,如果设备没有要发送的数据,它将 NAK 数据包。中断传输的调度频率在创建管道时指定。
等时管道: 这些管道用于等时数据,例如视频或音频流,具有固定的延迟,但无保证交付。当前实现中提供了对这种类型管道的一些支持。如果在传输期间发生错误或设备因例如缺少缓冲区空间而否定数据包(NAK)而设备确认包时,在控制、大容量和中断传输中的数据包会重试。然而,如果传递失败或数据包被否定(NAK)时,等时数据包不会重试,因为这可能会违反时间约束。
在创建 pipe 时计算必要带宽的可用性。传输在 1 毫秒的帧内安排。帧内的带宽分配由 USB 规范第 5.6 节 [2] 规定。等时传输和中断传输允许消耗帧内多达 90% 的带宽。控制传输和批量传输的数据包在所有等时和中断数据包之后安排,并将消耗所有剩余的带宽。
关于传输调度和带宽回收的更多信息,可以在 USB 规范的第 5 章,UHCI 规范的第 1.3 节和 OHCI 规范的第 3.4.2 节中找到。
在中心通知有新设备连接后,服务层打开port,为设备提供 100 毫安的电流。此时,设备处于默认状态,并监听设备地址 0。服务层将通过默认管道继续检索各种描述符。之后,它将发送一个设置地址请求,将设备从默认设备地址(地址 0)移开。多个设备驱动程序可能能够支持该设备。例如,调制解调器驱动程序可能通过 AT 兼容接口支持 ISDN TA。然而,特定型号的 ISDN 适配器的驱动程序可能能够为该设备提供更好的支持。为了支持这种灵活性,探针返回优先级,指示其支持水平。对产品特定版本的支持排名最高,通用驱动程序排名最低。也可能是,如果一个配置中有多个接口,多个驱动程序可以连接到一个设备。每个驱动程序只需要支持接口的一个子集。
当搜索新连接设备的驱动程序时,首先检查设备特定驱动程序。如果未找到,探测代码将会迭代所有支持的配置,直到驱动程序在某个配置中附加。为支持具有不同接口上多个驱动程序的设备,探测程序将迭代配置中所有尚未被驱动程序占用的接口。忽略超出集线器功率预算的配置。在附加过程中,驱动程序应将设备初始化为正确状态,但不应复位设备,因为这将导致设备从总线断开连接并重新开始对其进行探测的过程。为避免占用不必要的带宽,在附加时不应声明中断管道,而应推迟分配管道,直到打开文件并实际使用数据为止。在关闭文件时,管道应再次关闭,即使设备可能仍处于附加状态。
设备驱动程序应当对与设备的任何事务接收到错误有所期望。USB 的设计支持并鼓励随时断开设备。驱动程序应确保在设备消失时采取正确的措施。
此外,已断开并重新连接的设备将不会重新附加到相同的设备实例。当更多设备支持序列号(请参阅设备描述符)或其他定义设备标识的方法被开发出来时,这种情况可能会发生变化。
设备的断开是由集线器在传递给集线器驱动程序的中断数据包中发出的。状态更改信息指示哪个port已经看到连接更改。对于连接在该port上的设备的所有设备驱动程序的设备分离方法将被调用,并清理结构。如果port状态表明在此期间已连接了一个设备到该port,则将启动用于探测和附加设备的过程。设备复位将在集线器上产生一个断开连接的序列,并将如上所述进行处理。
除了默认管道外,USB 规范未定义其他管道上使用的协议。关于此信息可以从各种来源找到。最准确的来源是 USB 主页上的开发者部分。从这些页面上,可以找到越来越多的设备类规范。这些规范指定了符合标准的设备在驱动程序视角下应该是什么样子,需要提供的基本功能以及在通信通道上应该使用的协议。USB 规范包括 Hub 类的描述。已创建了用于键盘、平板、条形码阅读器、按钮、旋钮、开关等的人机界面设备(HID)的类规范。第三个例子是用于大容量存储设备的类规范。有关设备类的完整列表,请参阅 USB 主页上的开发者部分。
然而,对于许多设备,协议信息尚未公开。有关使用的协议的信息可能可以从制造设备的公司获得。一些公司可能要求您在提供规范之前签署保密协议。在大多数情况下,这将阻止将驱动程序开源化。
另一个信息来源是 Linux 驱动程序源代码,因为许多公司已开始为其设备提供 Linux 驱动程序。与那些驱动程序的作者联系以获取信息源始终是一个好主意。
示例:人机接口设备 人机接口设备的规范,如键盘、鼠标、平板、按钮、拨号等,在其他设备类规范中被引用,并在许多设备中使用。
例如,音频扬声器为数字到模拟转换器提供端点,可能还提供一个用于麦克风的额外管道。它们还为设备前面的按钮和拨号提供一个 HID 端点的单独接口。监视器控制类也是如此。通过可用的内核和用户空间库以及 HID 类驱动程序或通用驱动程序,构建对这些接口的支持是直截了当的。作为一个在一个配置中由不同设备驱动程序驱动的接口的示例的另一个设备是带有内置传统鼠标的廉价键盘。为了避免在设备中包含 USB 集线器的成本,制造商将从键盘背面的 PS/2 接收的鼠标数据和键盘的按键合并到同一配置中的两个独立接口中。鼠标和键盘驱动程序分别连接到适当的接口,并为两个独立端点分配管道。
示例:固件下载 许多已开发的设备基于通用处理器,并添加了额外的 USB 核心。由于 USB 设备的驱动程序和固件开发仍然非常新颖,许多设备在连接后需要下载固件。
遵循的过程很简单。设备通过供应商和产品 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 软驱的支持也不那么简单,因为已设计了新的命令集。