Linux USB设备驱动(一)
USB(通用串行总线的缩写)被设计为一种低成本的串行接口解决方案,总线电源由USB主机提供,以支持广泛的外围设备。USB最初的总线速度是1.5 Mbps的低速,然后是12 Mbps的全速,然后是480 Mbps的高速。随着USB 3.0规范的出现,超级速度被定义为4.8 Gbps。最大数据吞吐量,即线路速率减去开销,在低速、全速和高速下分别约为384 Kbps、9.728 Mbps和425.984 Mbps。注意,这是最大数据吞吐量,它可能受到各种因素的不利影响,包括软件处理、同一总线上的其他USB带宽利用率等。
USB最大的优点之一是它支持动态连接和删除,这是一种被称为“即插即用”的接口类型。在连接USB外围设备之后,主机和设备进行通信,自动将外部可见的设备状态从所连接的状态通过通电、默认、寻址,最后达到配置状态。此外,所有设备都必须支持挂起状态,在这种状态下必须满足非常低的总线功耗规范。在挂起状态下节省电力是USB的另一个优势。
本文我们将重点介绍USB 2.0规范,其中包括低速、全速和高速设备规范。外设设备符合USB 2.0规范并不一定表明该设备是高速设备,但是一个被宣传为USB2.0兼容的集线器,必须具有高速能力。USB 2.0设备可以是高速、全速或低速设备。
USB 2.0总线拓扑
USB设备属于集线器的类别,它提供了额外的下游连接点或功能,为系统提供了一种能力。USB物理互连是一个分层的星形拓扑(见下图)。从第1层的主机和“root hub”开始,最多可支持7层,最多可支持127个设备。第2层到第6层可能有一个或多个集线器设备,以便支持与下一层的通信。复合设备(同时具有集线器和外围设备功能的设备)不能存在于第7层。
物理USB互连是通过一个简单的4线接口,双向差分数据(D+和D-),电源(VBUS)和接地,为所有USB 2.0(最高高速)设备完成的。VBUS电源名义上是+5V。“type-A”连接器和配套插头用于所有主机端口以及下游面向集线器端口。“type-B”连接器和配套插头用于所有外围设备以及集线器的上游面向端口。主机、集线器和设备之间的电缆连接每个最多可以是5米或~16英尺。最多有7层,电缆连接可达30米或约98英尺。
USB总线枚举和设备布局
USB是主机控制的轮询总线,其中所有事务都由USB主机发起。如果主机没有首先启动它,总线上就不会发生任何事情;USB设备不能发起传输,是主机轮询每个设备,请求数据或发送数据。所有连接和移除的USB设备都通过称为“总线枚举”的过程进行标识。
连接的设备被主机识别,其速度(低、全或高)通过使用D+/D- USB数据对的信号机制识别。当一个新的USB设备通过集线器连接到总线时,设备枚举过程开始。每个集线器提供一个IN端点,用于通知主机有关新连接的设备。主机不断轮询该端点以接收来自集线器的设备添加和删除事件。一旦连接了新设备,并且集线器将此事件通知主机,主机的USB总线驱动程序将启用所连接的设备,并开始向该设备请求信息。这是通过标准的USB请求完成的,这些请求通过默认的控制管道发送到设备的零端点。
以描述符(descriptors)的形式请求信息。USB描述符是设备提供的用于描述其所有属性的数据结构。这包括产品/供应商ID,任何设备类关联,以及描述产品和供应商的字符串。此外,还提供了有关所有可用端点的信息。当主机从设备读取所有必要的信息后,它会尝试找到一个匹配的设备驱动程序。这个过程的细节取决于所使用的操作系统。从连接的USB设备读取第一个描述符后,主机使用设备描述符中的供应商和产品ID来查找匹配的设备驱动程序。
连接的设备最初将使用默认的USB地址0。此外,所有USB设备都由许多独立的端点组成,这些端点为主机和设备之间的通信流提供了一个终端。
端点可以分为控制(control)端点和数据(data)端点。每个USB设备必须在地址0提供至少一个控制端点,称为默认端点或Endpoint0。该端点是双向的,即主机可以在一次传输中向该端点发送数据并从其接收数据。控制传输的目的是使主机能够获取设备信息、配置设备或执行设备特有的控制操作。
端点是一个缓冲区,通常由存储器块或寄存器组成,用于存储接收到的数据或包含准备传输的数据。每个端点被分配一个在设计时确定的唯一端点编号,但是,所有设备必须支持默认控制端点(ep0),该控制端点的编号为0,并且可以双向传输数据。所有其他端点都可以标记在一个方向上传输数据(总是从主机角度),“Out”,即来自主机的数据,或“In”,即到主机的数据。端点编号是一个与端点相关的4位整数(0-15);相同的端点编号用于描述两个端点,例如EP 1 IN和EP 1 OUT。端点地址是端点编号和端点方向的组合,例如:EP 1 IN, EP 1 OUT, EP 3 IN。端点地址用一个字节中的方向和数字进行编码,其中方向是MSB (1= IN, 0=OUT),数字是较低的4位。例如:
- EP 1 IN = 0x81
- EP 1 OUT = 0x01
- EP 3 IN = 0x83
- EP 3 OUT = 0x03
USB配置定义了设备的功能和特性,主要是电源功能和接口。设备可以有多个配置,但一次只能有一个是活跃的。配置可以有一个或多个USB接口来定义设备的功能。通常,功能和接口之间存在一对一的相关性。但是,某些设备公开与一个功能相关的多个接口。例如,包含带有内置扬声器的键盘的USB设备将提供用于播放音频的接口和用于按键的接口。此外,该接口包含用于定义与该接口关联的功能的带宽需求的可选择的设置。每个接口包含一个或多个端点,用于向设备传输数据或从设备接收数据。综上所述,一组端点构成一个接口,一组接口构成设备中的一个配置。下图显示了一个多接口USB设备:
找到并加载匹配的设备驱动程序后,设备驱动程序的任务是选择提供的一个设备配置、该配置中的一个或多个接口以及每个接口的可选择的设置。大多数USB设备不提供多个接口或多个可选择的设置。设备驱动程序根据自身的能力和总线上的可用带宽选择一种配置,并在连接的设备上激活该配置。此时,所选配置的所有接口及其端点都已设置好,设备可以使用了。
从主机到每个设备端点的通信使用在枚举期间建立的通信“管道”。管道是主机和设备之间的逻辑关联。管道纯粹是一个软件术语。管道与设备上的端点通信,该端点有一个地址。管道的另一端总是主控制器。当设备通过选择配置和接口的可选择的设置进行配置时,将打开端点的管道。因此,它们成为I/O操作的目标。管道具有端点的所有属性,但它是活跃的,可用于与主机通信。设备地址、端点编号和方向的组合允许主机唯一地引用每个端点。
USB数据传输
一旦枚举完成,主机和设备就可以自由地进行从主机到设备的数据传输通信,反之亦然。传输的两个方向都由主机发起。这里定义了四种不同类型的传输。这些类型是:
- Control Transfers:用于在连接时配置设备,并可用于其他特定于设备的目的,例如特定于设备的寄存器读/写访问以及设备上其他管道的控制。控制传输最多由三个不同的阶段组成,一个包含请求的设置阶段,一个由主机发送或接收的数据阶段(如果需要的话),以及一个表示传输成功的状态阶段。USB有许多通过使用控制传输实现的标准化事务。例如,“Set Address”和“Get Descriptor”事务总是在上面描述的设备枚举过程中使用。“Set Configuration”请求是另一个标准事务,也在设备枚举期间使用。
- Bulk Data Transfers: 能够传输相对大量的数据或突发数据。批量传输不能保证时间,但可以提供最快的数据传输速率,如果USB总线不被其他活动占用。
- Interrupt Data Transfers: 用于及时但可靠地传递数据,例如具有人类可感知的回声或反馈响应特征的字符或坐标。中断传输有保证的最大延迟,即事务尝试之间的时间。USB鼠标和键盘通常使用中断数据传输。
- Isochronous Data Transfers: 占用预先商定的USB带宽和预先商定的传输延迟。同步传输有定时保证,但没有纠错能力。同步数据必须以接收到的速率来传输,以保持其时序,并且可能对传输延迟很敏感。同步传输的典型用途是音频或视频流。
USB设备类
USB规范和补充文档定义了许多设备类,根据功能和接口要求对USB设备进行分类。当主机检索到设备信息时,通过分类来确定如何与USB设备通信。集线器是一种特殊指定的设备,在USB规范中有额外的要求。外围设备的其他类别的例子是人机界面,也称为HID,打印机,图像,大容量存储和通信。USB UART设备通常属于USB设备的通信设备类(CDC)。
人机界面设备类
HID类设备通常以某种方式与人类交互。HID类设备包括鼠标、键盘、打印机等。然而,HID规范仅仅定义了设备和数据传输协议的基本要求,并且设备不一定依赖于任何直接的人类交互。为了保持HID接口的标准化和高效,HID设备必须满足一些通用的要求。
- 所有HID设备都必须有一个控制端点(Endpoint 0)和一个中断IN端点。许多设备也使用中断OUT端点。在大多数情况下,HID设备不允许有一个以上的OUT和IN端点。
- 传输的所有数据必须格式化为reports,其结构在report描述符中定义。
- HID设备除了响应所有标准USB请求外,还必须响应标准HID请求。
在HID设备进入正常工作模式并向主机传输数据之前,设备必须进行正确的枚举。枚举过程由主机对存储在设备中描述设备功能的描述符的多次调用组成。设备必须响应符合标准格式的描述符。描述符包含设备的所有基本信息。USB规范定义了检索到的一些描述符,而HID规范定义了其他必需的描述符。下一节讨论主机期望接收的描述符结构。
USB描述符
主机软件通过在枚举期间向默认端点发送各种标准控制请求,在连接设备时立即从连接设备获取描述符。这些请求指定要检索的描述符类型。为了响应这样的请求,设备发送描述符,其中包括有关设备、配置、接口和相关端点的信息。设备描述符包含关于整个设备的信息。
每个USB设备都公开一个设备描述符(device descriptor),该描述符指示设备的类别信息、供应商和产品标识符以及配置数量。每个配置都公开它的配置描述符(configuration descriptor),该描述符指示接口数量和电源特性。每个接口为其每个可选择的设置公开一个接口描述符(interface descriptor),该描述符包含关于类和端点数量的信息。每个接口中的每个端点都公开端点描述符(endpoint descriptors),这些描述符指示端点类型和最大数据包大小。
描述符以一个字节开头,这个字节表示描述符的字节长度。这个长度等于描述符中的字节总数,包括存储该长度的字节。下一个字节表示描述符类型,允许主机正确解释描述符中包含的其余字节。其余字节的内容和值特定于正在传输的描述符类型。描述符结构必须完全遵循规范;主机将忽略接收到的包含大小或值错误的描述符,这些描述符会潜在地导致枚举失败并禁止设备和主机之间的进一步通信。
USB设备描述符
每个通用串行总线(USB)设备必须能够提供一个包含设备相关信息的单个设备描述符。例如,idVendor和idProduct字段分别指定供应商和产品标识符。bcdUSB字段表示设备遵循的USB规范版本。例如,0x0200表示设备是按照USB 2.0规范设计的。bcdDevice值表示设备定义的修订号。设备描述符还表示设备支持的配置总数。你可以在下面看到一个包含所有设备描述符字段的结构示例:
typedef struct __attribute__ ((packed)) { uint8_t bLength; // Length of this descriptor. uint8_t bDescriptorType; // DEVICE descriptor type(USB_DESCRIPTOR_DEVICE). uint16_t bcdUSB; // USB Spec Release Number (BCD). uint8_t bDeviceClass; // Class code (assigned by the USB-IF). 0xFF-Vendor specific. uint8_t bDeviceSubClass; // Subclass code (assigned by the USB-IF). uint8_t bDeviceProtocol; // Protocol code (assigned by the USB-IF). 0xFF-Vendor specific. uint8_t bMaxPacketSize0; // Maximum packet size for endpoint 0. uint16_t idVendor; // Vendor ID (assigned by the USB-IF). uint16_t idProduct; // Product ID (assigned by the manufacturer). uint16_t bcdDevice; // Device release number (BCD). uint8_t iManufacturer; // Index of String Descriptor describing the manufacturer. uint8_t iProduct; // Index of String Descriptor describing the product. uint8_t iSerialNumber; // Index of String Descriptor with the device's serial number. uint8_t bNumConfigurations; // Number of possible configurations. } USB_DEVICE_DESCRIPTOR
第一项bLength描述了描述符的长度,对所有USB设备描述符通用。
bDescriptorType是设备描述符的常量单字节指示符,对所有设备描述符通用。
bcd编码的双字节bcdUSB项告诉系统设备遵循哪个USB规范发布指南。在利用USB规范未来修订中包含的附加或更改的设备中,这个数字可能需要更改,因为主机将使用这个条目来帮助确定为设备加载什么驱动程序。
如果USB设备类是在设备描述符中定义的,这个bDeviceClass项将包含一个在USB规范中定义的常量。在其他描述符中定义的设备类应该将设备描述符中的设备类项设置为0x00。
如果上面讨论的设备类项(bDeviceClass)设置为0x00,那么设备bDeviceSubClass项也应该设置为0x00。这一项可以告诉主机关于设备子类设置的信息。
bDeviceProtocol项告诉主机设备是否支持高速传输。如果上面两项(bDeviceClass和bDeviceSubClass)设置为0x00,那么这一项也应该设置为0x00。
bMaxPacketSize0项告诉主机在单个控制端点传输中可以包含的最大字节数。对于低速设备,这个字节必须设置为8,而全速设备的端点0的最大数据包大小可以是8、16、32或64。
双字节项idVendor标识设备的供应商ID。供应商id可以通过USB.org网站获得。主机应用程序将搜索添加的USB设备的供应商id,以查找应用程序所需的特定设备。
与供应商ID一样,双字节项idProduct唯一标识所连接的USB设备。产品id可以通过USB.org网站获得。
bcdDevice项与供应商ID和产品ID一起使用,以唯一地标识每个USB设备。
接下来的三个单字节项告诉主机在检索描述系统在屏幕上显示的附加设备的UNICODE字符串时使用哪个字符串数组索引。这个字符串描述了附加设备的制造商。iManufacturer字符串索引值为0x00向主机表示设备内存中没有存储该字符串的值。当主机希望检索描述已连接产品的字符串时,将使用索引iProduct。例如,字符串可以为“USB键盘”。索引iSerialNumber所指向的字符串可以包含产品序列号的UNICODE文本。
bNumConfigurations项告诉主机设备支持多少个配置。配置是设备功能能力的定义,包括端点配置。所有设备必须包含至少一个配置,但可以支持多个配置。
USB配置描述符
USB设备可以有几种不同的配置,尽管大多数设备只有一种配置。配置描述符指定设备是如何供电的,它的最大功耗和它的接口数量。有两种可能的配置,一种是总线供电,另一种是单独供电。你可以在下面看到一个包含所有配置描述符字段的结构的例子:
typedef struct __attribute__ ((packed)) { uint8_t bLength; // Size of Descriptor in Bytes uint8_t bDescriptorType; // Configuration Descriptor (0x02) uint16_t wTotalLength; // Total length in bytes of data returned uint8_t bNumInterfaces; // Number of Interfaces uint8_t bConfigurationValue; // Value to use as an argument to select this configuration uint8_t iConfiguration; // Index of String Descriptor describing this configuration uint8_t bmAttributes; // power parameters for the configuration uint8_t bMaxPower; // Maximum Power Consumption in 2mA units } USB_CONFIGURATION_DESCRIPTOR;
bLength项定义了配置描述符的长度。这是长度是标准的。
bDescriptorTye项是配置描述符的单字节常量0x02指示符。
两个字节的wTotalLength项定义了这个描述符的长度以及与这个配置相关联的所有其他描述符的长度。例如,可以通过添加配置描述符、接口描述符、HID类描述符和与此接口关联的两个端点描述符的长度来计算长度。这个两字节项遵循“小端序”数据格式。该项定义了此描述符的长度以及与此配置关联的所有其他描述符的长度。
bNumInterfaces项定义了此配置中包含的设置的接口数量。
SetConfiguration请求使用bConfigurationValue项来选择此配置。
iConfiguration项是一个字符串描述符的索引,该字符串描述符以人类可读的形式描述配置。
bmAttributes项告诉主机设备是否支持USB功能,如远程唤醒。项位被设置或清除以描述这些条件。请查看USB规范以获得关于此项的详细讨论。
bMaxPower项告诉主机在此配置下设备需要多少电流才能正常工作。
USB接口描述符
USB接口描述符可能包含有关USB接口的可选择的设置的信息。接口描述符有一个bInterfaceNumber字段,用于指定接口编号,还有一个bAlternateSetting字段,用于允许该接口的其他设置。例如,您可以有一个具有两个接口的设备。第一个接口可以将bInterfaceNumber设置为0,这表明它是第一个接口描述符,并将bAlternateSetting设置为0。第二个接口可以将bInterfaceNumber设置为1,将bAlternateSetting设置为0(默认值)。第二个接口也可以将bAlternateSetting设置为1,作为第二个接口的可选设置。
bNumEndpoints项提供了接口使用的端点数量。
bInterfaceClass, bInterfaceSubClass和bInterfaceProtocol项指定支持的类(例如HID,大容量存储等)。这允许许多设备使用类驱动程序,从而避免为您的设备编写特定的驱动程序。iInterface项允许接口的字符串描述。
你可以在下面看到一个包含接口描述符字段的结构的例子:
typedef struct __attribute__ ((packed)) { uint8_t bLength; // Size of Descriptor in Bytes (9 Bytes) uint8_t bDescriptorType; // Interface Descriptor (0x04) uint8_t bInterfaceNumber; // Number of Interface uint8_t bAlternateSetting; // Value used to select alternative setting uint8_t bNumEndPoints; // Number of Endpoints used for this interface uint8_t bInterfaceClass; // Class Code (Assigned by USB Org) uint8_t bInterfaceSubClass; // Subclass Code (Assigned by USB Org) uint8_t bInterfaceProtocol; // Protocol Code (Assigned by USB Org) uint8_t iInterface; // Index of String Descriptor Describing this interface } USB_INTERFACE_DESCRIPTOR;
USB端点描述符
USB端点描述符描述与端点0不同的端点。端点0是一个控制端点,它在任何其他描述符之前配置。主机将使用这些USB端点描述符返回的信息来指定每个端点的传输类型、方向、轮询间隔和最大数据包大小。你可以在下面看到一个包含所有端点描述符字段的结构示例:
typedef struct __attribute__ ((packed)) { uint8_t bLength; // Size of Descriptor in Bytes (7 bytes) uint8_t bDescriptorType; // Endpoint Descriptor (0x05) uint8_t bEndpointAddress; // Endpoint Address.Bits 0..3b Endpoint Number. Bits 4..6b Reserved. Set to Zero.Bits 7 Direction 0 = Out, 1 = In uint8_t bmAttributes // Transfer type uint16_t wMaxPacketSize; // Maximum Packet Size this endpoint can send or receive uint8_t bInterval; // Interval for polling endpoint data transfers } USB_ENDPOINT_DESCRIPTOR;
bEndpointAddress表示该描述符描述的端点。
bmAttributes指定传输类型。这可以是控制,中断,同步或批量传输。如果指定了同步端点,则可以选择其他属性,例如同步和使用类型。Bits 0..1为传输类型: 00 =控制,01 =同步,10 =批量,11 =中断。Bits 2..7保留。如果端点是同步传输端点,Bits 3..2 = Synchronisation Type (ISO Mode): 00 = No Synchonisation, 01 = Asynchronous, 10 =Adaptive, 11 = Synchronous。Bits 5..4 = Usage Type (ISO Mode): 00 = Data Endpoint, 01 = Feedback Endpoint, 10 = Explicit Feedback Data Endpoint, 11 = Reserved。
wMaxPacketSize项指示该端点的最大有效负载大小。
bInterval项用于指定端点数据传输的轮询间隔。忽视批量和控制端点。单位以帧表示,因此这等同于低速/全速设备的1ms和高速设备的125us。
USB字符串描述符
USB字符串描述符(USB_STRING_DESCRIPTOR)为其他描述符提供人类可读的信息。它们是可选的。如果一个设备不支持字符串描述符,那么在设备、配置和接口描述符中对字符串描述符的所有引用都必须设置为0。字符串描述符是UNICODE编码的字符,因此单个产品可以支持多种语言。当请求字符串描述符时,请求者使用USB-IF定义的16位语言ID (LANGID)指定所需的语言。字符串索引0用于所有语言,并返回一个字符串描述符,其中包含设备支持的两字节LANGID代码数组。
UNICODE字符串描述符不是以null结尾的。字符串长度是通过描述符的第一个字节的值减去2来计算的。
USB HID描述符
USB HID设备类支持人类用来控制计算机系统操作的设备。HID类设备包括各种各样的人机界面、数据指示器和数据反馈设备,这些设备具有指向最终用户的各种输出类型。HID类设备的一些常见示例包括:
- 键盘
- 指向设备,如标准鼠标、操纵杆和轨迹球
- 前面板控件,如旋钮、开关、按钮和滑块
- 电话、游戏或模拟设备上的控制装置,如方向盘、舵踏板和拨号盘
- 数据设备,如条形码扫描仪,温度计,分析仪
在USB HID设备中需要以下描述符:
- 标准设备描述符
- 标准配置描述符
- HID类的标准接口描述符
- 特定类的HID描述符
- 中断IN端点的标准端点描述符
- 特定类的report描述符
特定类的HID描述符是这样的:
typedef struct __attribute__((packed)) { uint8_t bLength; uint8_t bDescriptorType; uint16_t bcdHID; uint8_t bCountryCode; uint8_t bNumDescriptors; uint8_t bReportDescriptorType; uint16_t wItemLength; } USB_HID_DESCRIPTOR;
bLength项描述了HID描述符的大小。它可以根据包含在这个HID配置定义中的下属描述符(如report描述符)的数量而变化。
bDescriptorType 0x21值是设备描述符的常量单字节指示符,对所有HID描述符通用。
两个字节的bcdHID项告诉主机设备遵循的是HID类规范的哪个版本。USB规范要求该值被格式化为二进制编码的十进制数字,这意味着每个字节的上下位代表数字'0'…9'。例如, 0x0101表示数字0101,它等于带有隐含小数点的修订号1.01。
如果设备被设计为本地化到特定的国家,bCountryCode项告诉主机是哪个国家。将该项设置为0x00告诉主机设备没有被设计为本地化到任何国家。
bNumDescriptors项告诉主机这个HID配置中包含了多少个report描述符。以下两个字节的项对描述了每个包含的report描述符。
bReportDescriptorType项描述了在这个HID描述符传输之后的第一个描述符。例如,如果值为“0x22”,则表示接下来的描述符是report描述符。
wItemLength项告诉主机前面项中描述的描述符的大小。
HID report描述符是描述设备数据包的hardcode字节数组。这包括:设备支持多少数据包,数据包有多大,以及数据包中每个字节和位的用途。例如,带有计算器程序按钮的键盘可以告诉主机按下/释放按钮的状态存储在4号数据包中第6字节的第2位。
Linux USB子系统
USB支持早在2.2内核系列中就被添加到Linux中,并且从那时起就一直在发展。除了对新一代USB的支持外,还支持各种主机控制器,添加了外设的新驱动程序,并引入了延迟测量和改进电源管理的高级功能。
USB Linux框架既支持USB设备,也支持控制设备的主机。USB Linux框架还支持在这些外围设备中使用的Gadget驱动程序和类。本章将重点介绍在主机上运行的Linux USB设备驱动程序的开发。
在Linux中,“USB核心”是一个支持USB外围设备和主机控制器的特定API。这个API通过定义一组数据结构、宏和函数来抽象所有硬件。USB设备的主机端驱动程序与这些“usbcore” API对话。有两套API,一个用于通用的USB设备驱动程序(将通过本章开发),另一个用于作为核心部分的驱动程序。这样的核心驱动程序包括集线器驱动程序(它管理USB设备的树)和几种不同类型的USB主机适配器驱动程序,这些驱动程序控制单个总线。下图显示了一个Linux USB子系统的示例:
Linux USB API支持控制和批量消息的同步调用。它还通过使用称为“urb”(USB Request Blocks)的请求结构来支持各种数据传输的异步调用。
唯一真正接触硬件的主机端驱动程序(读/写寄存器,处理irq等)是主控制器设备(HCDs)驱动程序。理论上,所有HCD(Host Controller Devices)都通过相同的API提供相同的功能。在实践中,这变得越来越正确,但仍然会出现差异,特别是在不太常见的控制器上的错误处理。不同的控制器不一定会报告相同的故障方面,并且从故障中恢复(包括软件引起的故障,如unlink URB)还不是完全一致的。
本章的主要重点是Linux主机USB设备驱动程序的开发。接下来的所有章节都与开发这种类型的驱动程序有关。
编写Linux USB设备驱动程序
在接下来的实验中,您将开发几个USB设备驱动程序,通过这些驱动程序您将了解Linux USB设备驱动程序的基本框架。但是在继续进行实验之前,您需要熟悉USB的主要数据结构和功能。下面几节将详细解释这些结构和功能。
USB设备驱动程序注册
Linux USB设备驱动程序需要做的第一件事是向Linux USB核心注册自己,向它提供一些有关驱动程序支持哪些设备的信息,以及当驱动程序支持的设备从系统中插入或移除时调用哪些函数。所有这些信息都通过usb_driver结构传递给USB核心。参见下面usb_driver定义的USB seven segment驱动程序位于/linux/drivers/USB/misc/usbsevseg.c:
static struct usb_driver sevseg_driver = { .name = "usbsevseg", .probe = sevseg_probe, .disconnect = sevseg_disconnect, .suspend = sevseg_suspend, .resume = sevseg_resume, .reset_resume = sevseg_reset_resume, .id_table = id_table, };
变量name是描述驱动程序的字符串。它用于打印到系统日志的信息性消息。当发现或删除与id_table变量中提供的信息匹配的设备时,probe()和disconnect()热插拔回调将被调用。
probe()函数由驱动程序中的USB核心调用,以查看驱动程序是否愿意管理设备上的特定接口。如果是,probe()函数返回零,并使用usb_set_intfdata()将特定于驱动程序的数据与接口关联起来。它还可以使用usb_set_interface()来指定适当的可选择的设置。如果不愿意管理接口,则返回-ENODEV,如果发生真正的IO错误,则返回适当的负errno值。
int (* probe) (struct usb_interface *intf,const struct usb_device_id *id);
disconnect()回调函数在接口不再可访问时被调用,通常是因为它的设备已经(或正在)断开连接,或者驱动模块被卸载:
void disconnect(struct usb_device *dev, void *drv_context);
在usb_driver结构中,定义了一些电源管理(PM)回调:
- suspend:当设备将要挂起时调用。
- resume:设备恢复时调用。
- reset_resume:挂起设备被复位而不是被恢复时调用。
并且还定义了一些设备级的操作:
- pre_reset:设备即将被复位时调用。
- post_reset:设备复位后调用。
USB设备驱动程序使用ID table来支持热插拔。usb_driver结构中包含的指针变量id_table指向usb_device_id类型的结构数组,该结构数组声明USB设备驱动程序支持的设备。大多数驱动程序使用USB_DEVICE()宏来创建usb_device_id结构。这些结构通过使用MODULE_DEVICE_TABLE(usb, xxx)宏注册到USB核心。类中包含的以下代码行/linux/drivers/usb/misc/usbsevseg.c驱动程序创建并注册一个usb设备到usb内核:
#define VENDOR_ID 0x0fc5 #define PRODUCT_ID 0x1227
/* table of devices that work with this driver */ static const struct usb_device_id id_table[] = { { USB_DEVICE(VENDOR_ID, PRODUCT_ID) }, { }, }; MODULE_DEVICE_TABLE(usb, id_table);
usb_driver结构通过使用module_usb_driver()函数注册到总线核心:
module_usb_driver(sevseg_driver);
Linux主机端数据类型
USB设备驱动程序实际上绑定到接口,而不是设备。可以把它们看作是“接口驱动程序”,尽管你可能不会在很多设备上看到重要的区别。大多数USB设备都很简单,只有一个功能、一个配置、一个接口和一个可选择的设置。USB接口由usb_interface结构表示。它是当这个回调函数被调用时,USB核心传递给USB驱动程序的probe()函数的内容。
struct usb_interface { struct usb_host_interface * altsetting; struct usb_host_interface * cur_altsetting; unsigned num_altsetting; struct usb_interface_assoc_descriptor * intf_assoc; int minor; enum usb_interface_condition condition; unsigned sysfs_files_created:1; unsigned ep_devs_created:1; unsigned unregistering:1; unsigned needs_remote_wakeup:1; unsigned needs_altsetting0:1; unsigned needs_binding:1; unsigned resetting_device:1; unsigned authorized:1; struct device dev; struct device * usb_dev; atomic_t pm_usage_cnt;
struct work_struct reset_ws; };
下面是usb_interface结构的主要成员:
- altsetting: usb_host_interface结构的数组,每个可选设置对应一个。每一个都包含一组端点配置。它们没有特别的顺序。每个可选择的设置的usb_host_interface结构允许访问每个端点的usb_endpoint_descriptor结构:
interface->altsetting[i]->endpoint[j]->desc
- cur_altsetting: 当前的altsetting.
- num_altsetting: altsettings定义的数量.
每个接口可能有可选择的设置。设备的初始配置设置为altsetting 0,但是设备驱动程序可以使用usb_set_interface()来更改该设置。可选择的设置通常用于控制周期性端点的使用,例如让不同端点使用不同数量的预留USB带宽。所有使用同步端点的符合标准的USB设备将在非默认设置中使用它们。
usb_host_interface结构包括一个usb_host_endpoint结构数组。
/* host-side wrapper for one interface setting's parsed descriptors */ struct usb_host_interface { struct usb_interface_descriptor desc;
int extralen; unsigned char *extra; /* Extra descriptors */
/* array of desc.bNumEndpoints endpoints associated with this * interface setting. these will be in no particular order. */ struct usb_host_endpoint *endpoint;
char *string; /* iInterface string, if present */ };
每个usb_host_endpoint结构都包含一个usb_endpoint_descriptor结构。
struct usb_host_endpoint { struct usb_endpoint_descriptor desc; struct usb_ss_ep_comp_descriptor ss_ep_comp; struct usb_ssp_isoc_ep_comp_descriptor ssp_isoc_ep_comp; struct list_head urb_list; void *hcpriv; struct ep_device *ep_dev; /* For sysfs info */unsigned char *extra; /* Extra descriptors */ int extralen; int enabled; int streams; };
usb_endpoint_descriptor结构包含设备本身介绍的所有usb特定数据。
struct usb_endpoint_descriptor { __u8 bLength; __u8 bDescriptorType; __u8 bEndpointAddress; __u8 bmAttributes; __le16 wMaxPacketSize; __u8 bInterval; /* NOTE: these two are _only_ in audio endpoints. */ /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */ __u8 bRefresh; __u8 bSynchAddress; } __attribute__ ((packed));
你可以使用下面的代码从IN和OUT端点描述符中获取IN和OUT端点地址,这包括在USB接口的当前可选择的设置中:
struct usb_host_interface *altsetting = intf->cur_altsetting; int ep_in, ep_out;
/* there are two usb_host_endpoint structures in this interface altsetting.Each usb_host_endpoint structure contains a usb_endpoint_descriptor */ ep_in = altsetting->endpoint[0].desc.bEndpointAddress; ep_out = altsetting->endpoint[1].desc.bEndpointAddress;
USB请求块(URB)
主机和设备之间的任何通信都是通过使用USB请求块(urbs)异步完成的。
- URB包含执行任何USB事务并将数据和状态传回的所有相关信息。
- URB的执行本质上是一个异步操作,即usb_submit_urb()调用在成功地将所请求的操作排队后立即返回。
- 一个URB的传输可以在任何时候使用usb_unlink_urb()取消。
- 每个URB都有一个完成处理程序,在操作成功完成或取消后调用该处理程序。URB还包含一个上下文指针,用于将信息传递给完成处理程序。
- 设备的每个端点在逻辑上支持一个请求队列。您可以填充该队列,以便USB硬件仍然可以在您的驱动程序处理另一个端点的完成时将数据传输到另一个端点。这将最大限度地利用USB带宽,并在使用定期传输模式时支持无缝数据流到(或从)设备。
以下是urb结构的一些字段:
struct urb { // (IN) device and pipe specify the endpoint queue struct usb_device *dev; // pointer to associated USB device unsigned int pipe; // endpoint information unsigned int transfer_flags; // URB_ISO_ASAP, URB_SHORT_NOT_OK, etc. // (IN) all urbs need completion routines void *context; // context for completion routine usb_complete_t complete; // pointer to completion routine // (OUT) status after each completion int status; // returned status // (IN) buffer used for data transfers void *transfer_buffer; // associated data buffer u32 transfer_buffer_length; // data buffer length int number_of_packets; // size of iso_frame_desc // (OUT) sometimes only part of CTRL/BULK/INTR transfer_buffer is used u32 actual_length; // actual data buffer length // (IN) setup stage for CTRL (pass a struct usb_ctrlrequest) unsigned char *setup_packet; // setup packet (control only) // Only for PERIODIC transfers (ISO, INTERRUPT) // (IN/OUT) start_frame is set unless URB_ISO_ASAP isn't set int start_frame; // start frame int interval; // polling interval // ISO only: packets are only "best effort"; each can have errors int error_count; // number of errors struct usb_iso_packet_descriptor iso_frame_desc[0]; };
USB驱动程序必须使用它所声明的接口中适当端点描述符的值创建一个“管道”。
urb通过调用usb_alloc_urb()来分配:
struct urb *usb_alloc_urb(int isoframes, int mem_flags)
返回值是一个指向分配的URB的指针,如果分配失败则返回0。参数isoframes指定要调度的同步传输帧的数量。对于CTRL/BULK/INT,使用0。mem_flags参数保存标准内存分配标志,允许您控制底层代码是否阻塞(除其他外)。
释放一个URB,使用usb_free_urb():
void usb_free_urb(struct urb *urb)
中断传输是周期性的,间隔为两个单位的幂(1、2、4等)。单位是用于全速和低速设备的帧,和用于高速设备的微帧。您可以使用usb_fill_int_urb()宏来填充INT传输字段。当使用usb_fill_int_urb()函数用正确的信息填充了写urb时,您应该将urb的完成回调指向调用您自己的回调函数。当USB子系统完成urb时调用此函数。回调函数是在中断上下文中调用的,因此必须小心不要在那个时候做太多处理。usb_submit_urb()调用将urb->interval修改为实现的interval值,该值小于或等于请求的interval值。
URB是通过usb_submit_urb()函数提交的:
int usb_submit_urb(struct urb *urb, int mem_flags)
mem_flags参数(例如GFP_ATOMIC)控制内存分配,例如当内存紧张时,较低的级别可能会阻塞。它立即返回,状态为0(请求排队)或一些错误代码,通常由以下原因引起:
- 内存不足(-ENOMEM)
- 设备已拔出(-ENODEV)
- 停滞的端点(-EPIPE)
- 太多排队的ISO传输(-EAGAIN)
- 太多请求的ISO帧(-EFBIG)
- 无效的INT间隔(-EINVAL)
- INT有多个包(-EINVAL)
提交后,urb->status为-EINPROGRESS;但是,除了在你的完成回调中,你不应该查看这个值。
有两种方法可以取消已经提交但还没有返回到驱动程序的URB。对于异步取消,调用usb_unlink_urb():
int usb_unlink_urb(struct urb *urb)
它从内部列表中移除urb并释放所有分配的HW描述符。状态被更改为反映断开连接。注意,当usb_unlink_urb()返回时,URB通常不会结束;您仍然必须等待调用完成处理程序。
要同步取消一个URB,调用usb_kill_urb():
void usb_kill_urb(struct urb *urb)
它完成usb_unlink_urb()所做的一切,此外,它等待直到URB返回并且完成处理程序执行完成。
完成处理程序的类型如下:
typedef void (*usb_complete_t)(struct urb *)
在完成处理程序中,您应该查看urb->status以检测任何USB错误。由于上下文参数包含在URB中,您可以将信息传递给完成处理程序。
本文来自博客园,作者:闹闹爸爸,转载请注明原文链接:https://www.cnblogs.com/wanglouxiaozi/p/17024290.html