转载 linux usb
本篇为转自他人博客,因找不到出处,若有侵权,请留言告知,会即刻删除。
1、 简述:
USB 出自豪门,一问世便有 IBM,Microsoft,compaq 等前呼后拥,不红实在是没有道理,以致于连三岁小毛孩都知道买游戏手柄要买 USB 的。
USB 名气这么大,但 USB 是什么呢?要是想找份写驱动的活谋生,常会被问到这样的要求: “ 给我讲讲 USB 。 ”
USB 系统概述
少 许的艺术细胞加上平时的使用经验就能理解第一张图的含义,一个 USB 主机可以通过 hub 可以连接许多的从设备,组成像树一样的结构。
关键是第二张图,详细的描述的 linux USB 系统的几大板块,这种结构基本上适用所有操作系统的 USB 架构。
可能是 USB 这三个字母看起来比较酷,稍和 USB 沾边的开发人员都会吹自己做过 USB 开发,特别是那些介绍 USB 的垃圾书,从头到尾扯了一通他也没有告诉读者他讲的是 USB 技术的那一部份,在 USB 的整个架构中的作用。
首先 ,USB 分为主从两大体系,一般而言, PC 中的 USB 系统就是作主,而一般的 USB 鼠标, U 盘则是典型的 USB 从系统。即使是一个 USB 鼠标,也不要小看它,它其中也有很精深的 USB 技术。作为 USB 鼠标的第一大生产国,我们实在没有什么好骄傲的,如果从零开始,花个十年八年我们国家也不一定能研发出一个像样的 USB 鼠标。
先说控制器这一块,我们至少要开发出 USB 的主控制器与从控制器,鼠标是低速设备,所需的是最简单的一类从控制器。主控制器则复杂得多,因为太过于复杂了,所以就形成了一些标准。在一个复杂的系统中,标准的好处就是可以让开发者把精力集中在自己负责的一块中来,只需要向外界提供最标准的接口,而免于陷于技术的汪洋大海中。
USB 主控制器主要有 1.1 时代的 OHCI 和 UHCI , 2.0 时代的 EHCI ,这些标准规定了主控制器的功能和接口(寄存器的序列及功能),对我们驱动工程师而言,这样的好处就是只要你的驱动符合标某一标准,你就能轻而易举的驱动所有这个标准的主控制器。要想把主控制器驱动起来,本来是一件很难的事情,估计全球的 IT 工程师没几个能有这样的水平,但有了标准,我们就可以轻松的占有这几个高水平的 IT 工程师的劳动成果。
主控制器和驱动有了,我们还需要 USB 协议栈,这就是整个 USB 系统的软件部分的核心(有的资料中直接把其称为 USB 核心), USB 协议栈一方面向使用 USB 总线的设备驱动提供操作 USB 总线的 API ,另一方面则管理上层驱动传下来的的数据流,按 USB 主控制器的要求放在控制器驱动规定的位置, USB 主控制器会调度这些数据。
我们这里用到了调度这个词, USB 主控制器的调度其实和火车的调度 CPU 的调度有相似之处,物理上的通路只有一条,但 USB 中规定的逻辑上的通路却有许多条,有时一个设备就会占用几条逻辑通道,而 USB 系统中又会有多个设备同时运行。这就好像是只有一条铁路线,但来来往往的火车却有许多, USB 主控制器的作用就是调度这些火车,而 USB 协议栈的作用则向上层的 USB 设备驱动提供不同的车次。
有了以上的这些模块,才能为 USB 鼠标设计驱动,这一点上 ps/2 鼠标的驱动和 USB 鼠标的驱动结构基本一样,只不过我们的数据通路是 USB 总线。
USB 系统甚至把设备驱动都给标准化了,只要是支持 USB 的主机,就可以支持任何一个厂商的 USB 鼠标,任何一个厂商的 U 盘,只要是被 USB 系统包函的设备,只要这些设备支持相应的标准,就无需重新设计驱动而直接使用。
下是简单的列出了 USB 设备类型,理想的情况 USB 系统要对这些设备作完整的支持,设备也必须符合 USB 规范中的要求。
1 - audio :表示一个音频设 备。 |
|
2 - communication device :通讯设备,如电话, moden 等等。 |
|
3 - HID :人机交互设备,如键盘,鼠标等。 |
|
6 - image 图象设备,如扫描仪,摄像头等,有时数码相 机也可归到这一类。 |
|
7 -打印机类。如单向,双向打印机等。 |
|
8 - mass storage 海量存储类。所有带有一定存储功能的都可以归到这一类。如数码相机大多数都归这一类。 |
|
9 - hub 类。 |
|
11 - chip card/smart card 。 |
|
13 -- Content Security |
|
14 -- Video ( Interface ) |
|
15 -- Personal Healthcare |
|
220 -- Diagnostic Device |
|
224 -- Wireless Controller ( Interface ) |
|
239 -- Miscellaneous |
|
254 -- Application Specific ( Interface ) |
|
255 - vendor specific. 厂家的自定义类,主要用于一些特殊的设备。如接口转接卡等。 |
理解了这两张图,基本能应付一下面试官了,但我建议还是随身带着这两张张图,因为 USB 系统确实太复杂,言语难以表达,很可能他说的 USB 和你讲的 USB 压根就不是一个东西,只是都和 USB 相关。
但这两张图也不能回答所有的问题,随着 USB 技术的发展, USB 系统中的一些不足也逐渐被承认, OTG 就是这种情况下的主要产物。
现在市面上有些设备(比如一些 MP4 )即能插上电脑当 U 盘使,也能被 U 盘插上读取 U 盘。这样的设备在 USB 系统中是作主还是作从呢?
这就是 OTG(On-The-Go), 即可以作主也可以作从,传说中的雌雄同体。这主要是为嵌入式设备准备的,因为 USB 是一种主从系统,不能支持点对点平等的传输数据, OTG 正是在这种需求下产生的, OTG 不仅支持控制器的主从切换,在一定层度上,也支持相同设备之间的数据交换。
1、 USB 连接的基本知识
信号线名称 |
颜色 |
||
1 |
Vbus |
红 |
|
2 |
D- |
白 |
|
3 |
D+ |
绿 |
|
4 |
GNU |
黑 |
|
shell(金属壳) |
屏敝层 |
有了上面的表,剥开 USB 线看看花花绿绿的信号线都是有来头的,这些色彩也是 USB 规范中的一部份。
USB 线览倒没有什么名堂,倒是 USB 接插件在这几年搞出不少事。
随着 USB OTG 技术的发展,对接插件有了新的要求, STD 标准的东西尺寸太大,于是有了 MINI 标准,但有人觉得 MINI 标准的接插件还是太大,又忽悠出 mirco 标准,而且 MINI 和 mirco 标准的接插件由 4pin 变成了 5pin 。
一 般而言,靠近 host 一则的插头和插座称作 A, 靠近从设备的则称 B ,在 OTG 中, A 则是指供电方。
Connector Color |
||
mirco/mini-A receptacle |
White |
|
mirco/min-AB receptacle |
Gray |
|
mirco/min-B receptacle |
Black |
|
mirco/min-A plug |
White |
|
mirco/min-B plug |
Black |
mirco/mini 标准的接插件都是 5pin, 除了传统的 vbus,D+,D-,GNU 外,还多了一个 ID pin 。
细的的人都会发现, mirco/mini 的接插件定义是 5 pin ,但线缆的信号线却是 4 根。这就是 OTG 的玄机。
OTG 规范中要求设备即能作主,也能作从,要实现这个功能,必须有寻找一种方法来区别设备是作主还是作从。 OTG 的作方就是增来一个 ID pin 来判断设备是接入设备的是主还是从,按 OTG 的要求,同时作主和从的设备要使用 mirco/min-AB receptacle ,这样可以接入 A 型的 plug, 也可以接入 B 型的 plug 。
在 a 型 plug 中, ID 线与地线相连,这样 A 型 plug 接入时 ID 线就被拉低, OTG 控制器切换到主模式,当 B 型 plug 中, ID 线悬空,这样 ID 线就为默认值(高电平), OTG 控制器就处于从状态。
上图中 pin 脚的序列是 vbus,D-,D+,ID,GND ,我们要注意上图中 pin4(ID) 的连接方法, OTG 需要通过这个 PIN 脚来判断控制器是作主还是作从。
对驱动而言, OTG 中的 ID 脚是我们需要主要关注的,至于其它的,了解一下就可以了, vbus 主要是供电, D+/D- 则是用来传输数据,就是我们前面所讲的主设备和从设备间唯一的一条铁路。 USB 中用差分信号来传送数据,这就增加了传输的的抗干扰能力,高频率传输成为可能, usb2.0 最高速度可以达到 480Mbps/s 。数据的传输主要由主控制器和从控制器来控制,这就回到了前面所说的, IC 技术的发展给 USB 技术铺平了道路, USB 的主从控制器实际上是一个专用的 CPU ,专门负责编解码 USB 数据和搬运数据,如果这些工作全交给 cpu 去做, CPU 早就累瘫了。在 2.0 问世之初, 480Mbps/s 的频率远远超出许多 CPU 的极限速度。
1、 OTG 控制器
OTG 的基本概念
首先,提出一个问题, OTG 和 EHCI/OHCI/UHCI 是同一类概念吗?
OTG Device :使用 Micro AB 插座,可以在运行时切换 Host/Device 。
仅外设 B-Device :仅仅能作为外设的 B-Device (分为插头一体和插头线缆分离的)。
可见, OTG 主要是负责控制器状态的切换,这种切换要根据接入的设备来判断。 OTG 主要使用在嵌入式设备中,说到嵌入式不能不提降低功耗了,所以仅有 ID 线的检测还是不够的。
ADP ( Attach Detection Protocol ):
HNP ( Host Negotiation Protocol ):
OTG 设备通过 HNP 来切换 Host/Device 角色。
OTG Device /Embedded Host 与 仅作为外设的 B-device (带 A 插头型)
OTG Device/Embedded Host 与 仅作为外设的 B-device ( A 插头为线缆连接)
回来我们前面的问题, OTG 和 EHCI/UHCI/OHCI 是同一类的概念吗?
当 OTG 己经发现设备, VBUS 供电开启,系统处于 HOST 或 DEVICE 状态,这时 OTG 的使命就完成了,接下来的工作就交给主控制器( EHCI/OHCI/UHIC )或从控制器了。
Linux 下的 OTG 架构
内核 定义了一个 struct otg_transceiver 的结构体,这个结构体描述的 OTG 需要支持的接口
struct otg_transceiver { struct device *dev; const char *label; u8 default_a; enum usb_otg_state state; //记录 OTG 控制器的状态,在实际的处理中这个比较重要。 struct usb_bus *host; struct usb_gadget *gadget; /* to pass extra port status to the root hub */ u16 port_status; u16 port_change; /* bind/unbind the host controller */ int (*set_host)(struct otg_transceiver *otg, struct usb_bus *host);//这个接口用来启用或禁用主控制器,是关键的接口 /* bind/unbind the peripheral controller */ int (*set_peripheral)(struct otg_transceiver *otg, struct usb_gadget *gadget); /* effective for B devices, ignored for A-peripheral */ int (*set_power)(struct otg_transceiver *otg, unsigned mA);//一般用来设置 vbus 上的供电 /* for non-OTG B devices: set transceiver into suspend mode */ int (*set_suspend)(struct otg_transceiver *otg, int suspend); // 下面是 OTG 的三大高级功能的接口。 /* for B devices only: start session with A-Host */ int (*start_srp)(struct otg_transceiver *otg); /* start or continue HNP role switch */ int (*start_hnp)(struct otg_transceiver *otg); /* b device disconnect during hnp */ int (*disconnect)(struct otg_transceiver *otg); /* b device connect */ int (*connect)(struct otg_transceiver *otg, struct usb_device *udev); /* host suspend the bus during hnp */ int (*host_suspend)(struct otg_transceiver *otg); /* hand interrupt related to usb otg */ int (*otg_interrupt)(struct otg_transceiver *otg);//中断处理接口 }; |
上表的结构体很清晰的描述了 OTG 的功能,只要实现几个重要的接口,就能把你的 OTG 控制器和 linux 系统联接起来。
系统中作了如下定义:
struct otg_transceiver *xceiv;
这个定义就是一个 OTG 控制器的对像,然后使用 otg_set_transceiver 来实现这两者之间的联接。
1、 EHCI控制器
EHCI的基本概念
OTG成功的发现了设备并把控制器切换到相应的状态,如果这种状态是HOST状态,那EHCI控制器就该上场了。
EHCI算是后起之秀,到了USB2.0以后才提出来的。EHCI主要是为了支持高速设备,如果一个控制器还要支持低速和全速设备,还需要有UHCI或OHCI的支持。
USB2.0中与EHCI配合使用支持低速和全速设备的OHCI/和UHCI叫作兼容控制器,下面这张有名的图很好的描述了USB2.0主控制器的组成。
可见,一个号称支持USB2.0的控制器很可能不止一个主控制器,除了必不可少的EHCI之外,很可能还会有OHCI/UHCI等来辅助处理低速设备的数据传输。如果只有EHCI控制器又想支持低速设备呢,那就需要能够支持(Transaction Translator)事务翻译的HUB来配合了,ECHI与HUB之间通过“分割事务(一种数据传输方式)”来处理低速和全速等设备。
主机控制器的初始化:
当系统起动时,主控制器枚举,为EHCI寄存器分配一个基址,设置 FLADJ寄存器一个指定值 ,当初始化电源或 HCReset寄存器时,寄存器都初设置成默认值,如下下所示,当硬件重置后,仅有操作系列寄存器不是默认值 。
Operational Register Default Value (after Reset) USBCMD 00080000h (00080B00h if Asynchronous Schedule Park Capability is a one) USBSTS 00001000h USBINTR 00000000h FRINDEX 00000000h CTRLDSSEGMENT 00000000h PERIODICLISTBASE Undefined ASYNCLISTADDR Undefined CONFIGFLAG 00000000h PORTSC 00002000h (w/PPC set to one); 00003000h (w/PPC set to a zero) |
为了初始化EHCI主控制器,系统软件操作如下:
1)在所有的接口数据结构创建后,设置 CTRLDSSEGMENT寄存器为 4-Gigabyte模式。
2)写一个合适的值到 USBINTR寄存器,使能合适的中断。
3)向 PERIODICLIST BASE写入周期调度链表的基址。如果周期调度链表没有可调度的数据,将其链表成员的 T-Bits设置 为1。
4) 设置USBCMD寄存器,指定中断处理程序入口,调度链表的长度,设置 Run/Stop 位来控制调度是否开始。
5)向 CONFIGFLAG寄存器写1, 查找USB主控制器由几个控制器(一个EHCI和零个以上的OHCH/UHCI)组成。
经过以上的操作,整个USB2.0主控制器(EHCI和兼容控制器)就可以工作了,端口(这里的端口和设备的端点的概念有区别,主要是指EHCI/OHCI/UHCI的数据端口)寄性器报告系统的连接状态。
即然一个USB2.0是由EHCI和几个兼容控制器组成,那这几个控制器是怎么配个的呢,系统软件又应该如何控制它们呢。这就涉及到一个路由(route)的问题。这个路由和网络上的路由概念并不多,一个主机上有几个网卡,数据要从正确的网卡传出去,不至于迷路,就需要路由算法。USB主控制器也有这个问题,主控制器可能是一个EHCI和几个OHCI/UHCI组成,数据是怎样找到合适的端口并传送出去呢。
所幸的是,EHCI的规中对此有要求,硬件完成了大多数的工作,系统软件是需要作一下简单的控制。
EHCI和兼容控制器有自己的状态和独立的寄存器,每一个传输都可以通过路由逻辑选择一个适当的控制器来传输, Configured Flag ( CF)寄存器用来作路由的控制,当重置或上电后,是缺省的路由控制策略,如果系统只有兼容控制器的驱动而没有EHCI驱动,则系统只支持全速和低速设备。
HCSPARAMS寄存器的N_CC位指示兼容控制器是由那一种(OHCI/EHCI)控制器来实现的,如果N_CC位是0,则表示没有兼容控制器,这时USB2.0-HCI系统则不能支持全速和低速的设备,如果要支持,只有通过USB2.0标准 的HUB来协助。
EHCI协议中规定有几种数据模型:
Periodic Frame List
Asynchronous List Queue Head Pointer
Isochronous (High-Speed) Transfer Descriptor (iTD)
Split Transaction Isochronous Transfer Descriptor (siTD)
Queue Element Transfer Descriptor (qTD)
Queue Head
Periodic Frame Span Traversal Node (FSTN)
以上数据模型(或称数据结构)就是EHCI的关键,具体的定义可以查找EHCI 的spec (Enhanced Host Controller Interface Specification for Universal Serial Bus)。EHCI控制器驱动实出上就是对这几种据结构的管理与操作。
EHCI控制器以协议的形式将这些数据模型规范下来,方案驱动设计者设计出通用的驱动,也方便非驱动设计者重用己有的驱动代码。要做到这一点,EHCI对硬件部份也需要作出一些必要的规定,这就是硬件对软件的接口--寄存器。
<>中对寄存器的序列及功能作了详细的定义,主要有以下三部份:
PCI Configuration Registers (USB)
Host Controller Capability Registers
Host Controller Operational Registers
PCI Configuration Registers的定义我们不必太关心,arm中一般不会有这一部份,我们需要详细了解的是Host Controller Capability Registers 和Host Controller Operational Registers这两大板块,
Enhanced Host Controller Capability Registers
Offset |
Size |
Mnemonic |
Power Well |
Register Name |
|
00h |
1 |
CAPLENGTH |
Capability Register Length |
||
01h |
1 |
Reserved |
N/A |
||
02h |
2 |
HCIVERSION |
Interface Version Number |
||
04h |
4 |
HCSPARAMS |
Structural Parameters |
||
08h |
4 |
HCCPARAMS |
Capability Parameters |
||
0Ch |
8 |
HCSP-PORTROUTE |
Companion Port Route Description |
Host Controller Operational Registers
Offset |
Mnemonic |
Register Name |
Power Well |
||
00h |
USBCMD |
USB Command |
|||
04h |
USBSTS |
USB Status |
|||
08h |
USBINTR |
USB Interrupt Enable |
|||
0ch |
FRINDEX |
USB Frame Index |
|||
10h |
CTRLDSSEGMENT |
4G Segment Selector |
|||
14h |
PERIODICLISTBASE |
Frame List Base Address |
|||
18h |
ASYNCLISTADDR |
Next Asynchronous List Address |
|||
1C-3F |
Reserved |
||||
40H |
CONFIGFLAG |
Configured Flag Register |
|||
44H |
PORTSC(1-N_PORTS) |
Port Status/Control |
在一些主芯片的spec中,USB主控制器的部份就介绍得很简单,大多数只是像我这样简单的说一下USB主控制器的标准,再列一下寄存器的序列,然后让读者去查找<这样的文档。
上表中标成红色的寄存器是我们需要主要关注的,echi主控制器会以此为入口,对各种数据模型进行调度。
主控制器的调度主要分为两大数,一类可以称为时间片的调度,多数控制器会以此种调度为主,另一种则是异步(Asynchronous)调度。
USB协议中把USB的传输类型分为控制传输,批量传输,中断传输,等时传输。这几种传输类型的定义其实是逻辑上的。我们知道,USB的物理数据通道就一条(D+/D-),要怎样才能达到USB协议中这几种传输类型的要求呢,这就要看主控制器是如何调度的了。
在EHCI中,把等时传输和中断传输都用进间片调度来控制。请看下图:
所谓的分时调度,就是把每秒的时间分为若干片(一般是1024/256等),每一个时间片(Frame)处理一组(一般是ISO数据)数据。
CPU会把ISO数据和INT数据建立一张表放在内核中,而ECHI的寄存器FRINDEX则会跟踪这个表,每一个时间片加-,FRINDEX所指之处,控制器就会把这处指针所指向的数据结构中的数据送到总线上去。整个过程看起来有点像是CPU的调度。
有了时间片的调度,其实控制器就可以完成所有的功能,但为了方便用户的使用,控制器还提拱了另一种调度来处理实时性要求不是很强的数据。
这种调试一般叫做异步调度,也就是AsyncListAddr发威的时候了,CPU把块传输和控制传输的数据按协议要求的数据结构在内存中安排好并建立一个链表,AsyncListAddr则会跟踪这个链表,控制器则把AsyncListAddr所指向的数据搬运到USB总线上去。
异步调度比ISO调度要简单得多了。至于异步调试和同步调度之间如何协调,EHCI控制器会处理这个问题,再也不用软件来操心了。
有了以上的简章介绍,我们知道EHCI的规范中对寄存器,数据结构和控制方式都有了详细的规定,linux是怎样执行这个规定的呢。
Linux中EHCI控制器驱动的架构
首 先,让我们来看看linux中是如何来定义这些寄存器的。
PCI系列的EHCI寄存器我们不关心,我们只关心Capability系列的和Controller系列的寄存器。
Host Controller Capability Registers |
|
struct ehci_caps { /* these fields are specified as 8 and 16 bit registers, * but some hosts can't perform 8 or 16 bit PCI accesses. */ u32 hc_capbase; #define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */ #define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */ u32 hcs_params; /* HCSPARAMS - offset 0x4 */ #define HCS_DEBUG_PORT(p) (((p)>>20)&0xf) /* bits 23:20, debug port? */ #define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ #define HCS_N_CC(p) (((p)>>12)&0xf) /* bits 15:12, #companion HCs */ #define HCS_N_PCC(p) (((p)>>8)&0xf) /* bits 11:8, ports per CC */ #define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */ #define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ #define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ u32 hcc_params; /* HCCPARAMS - offset 0x8 */ #define HCC_EXT_CAPS(p) (((p)>>8)&0xff) /* for pci extended caps */ #define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ #define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ #define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */ #define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/ #define HCC_64BIT_ADDR(p) ((p)&(1)) /* true: can use 64-bit addr */ u8 portroute [8]; /* nibbles for routing - offset 0xC */ } __attribute__ ((packed)); |
Host Controller Operational Registers |
|
struct ehci_regs { /* USBCMD: offset 0x00 */ u32 command; /* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */ #define CMD_PARK (1<<11) /* enable "park" on async qh */ #define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */ #define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ #define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */ #define CMD_ASE (1<<5) /* async schedule enable */ #define CMD_PSE (1<<4) /* periodic schedule enable */ /* 3:2 is periodic frame list size */ #define CMD_RESET (1<<1) /* reset HC not bus */ #define CMD_RUN (1<<0) /* start/stop HC */ /* USBSTS: offset 0x04 */ u32 status; #define STS_ASS (1<<15) /* Async Schedule Status */ #define STS_PSS (1<<14) /* Periodic Schedule Status */ #define STS_RECL (1<<13) /* Reclamation */ #define STS_HALT (1<<12) /* Not running (any reason) */ /* some bits reserved */ /* these STS_* flags are also intr_enable bits (USBINTR) */ #define STS_IAA (1<<5) /* Interrupted on async advance */ #define STS_FATAL (1<<4) /* such as some PCI access errors */ #define STS_FLR (1<<3) /* frame list rolled over */ #define STS_PCD (1<<2) /* port change detect */ #define STS_ERR (1<<1) /* "error" completion (overflow, ...) */ #define STS_INT (1<<0) /* "normal" completion (short, ...) */ /* USBINTR: offset 0x08 */ u32 intr_enable; /* FRINDEX: offset 0x0C */ u32 frame_index; /* current microframe number */ /* CTRLDSSEGMENT: offset 0x10 */ u32 segment; /* address bits 63:32 if needed */ /* PERIODICLISTBASE: offset 0x14 */ u32 frame_list; /* points to periodic list */ /* ASYNCLISTADDR: offset 0x18 */ u32 async_next; /* address of next async queue head */ u32 reserved [9]; /* CONFIGFLAG: offset 0x40 */ u32 configured_flag; #define FLAG_CF (1<<0) /* true: we'll support "high speed" */ /* PORTSC: offset 0x44 */ u32 port_status [0]; /* up to N_PORTS */ /* 31:23 reserved */ #define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */ #define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ #define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ /* 19:16 for port testing */ #define PORT_LED_OFF (0<<14) #define PORT_LED_AMBER (1<<14) #define PORT_LED_GREEN (2<<14) #define PORT_LED_MASK (3<<14) #define PORT_OWNER (1<<13) /* true: companion hc owns this port */ #define PORT_POWER (1<<12) /* true: has power (see PPC) */ #define PORT_USB11(x) (((x)&(3<<10)) == (1<<10)) /* USB 1.1 device */ /* 11:10 for detecting lowspeed devices (reset vs release ownership) */ /* 9 reserved */ #define PORT_RESET (1<<8) /* reset port */ #define PORT_SUSPEND (1<<7) /* suspend port */ #define PORT_RESUME (1<<6) /* resume it */ #define PORT_OCC (1<<5) /* over current change */ #define PORT_OC (1<<4) /* over current active */ #define PORT_PEC (1<<3) /* port enable change */ #define PORT_PE (1<<2) /* port enable */ #define PORT_CSC (1<<1) /* connect status change */ #define PORT_CONNECT (1<<0) /* device connected */ #define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) } __attribute__ ((packed)); |
上面两个结构体就是linux对EHCI寄存器的定义,我们可以对照<>2.2和2.3节来详细了解各个字段的定义。即使没有spec,代码中的注释和宏也己经能让我们理解个八九成。
Linux要管理ehci,仅有寄存器的定义还不够,下面我们再来看看ehci的定义:
struct ehci_hcd { /* one per controller */ /* glue to PCI and HCD framework */ struct ehci_caps __iomem *caps; struct ehci_regs __iomem *regs; struct ehci_dbg_port __iomem *debug; __u32 hcs_params; /* cached register copy */ spinlock_t lock; /* async schedule support */ struct ehci_qh *async; struct ehci_qh *reclaim; unsigned scanning : 1; /* periodic schedule support */ #define DEFAULT_I_TDPS 1024 /* some HCs can do less */ unsigned periodic_size; __hc32 *periodic; /* hw periodic table */ dma_addr_t periodic_dma; unsigned i_thresh; /* uframes HC might cache */ union ehci_shadow *pshadow; /* mirror hw periodic table */ int next_uframe; /* scan periodic, start here */ unsigned periodic_sched; /* periodic activity count */ /* list of itds completed while clock_frame was still active */ struct list_head cached_itd_list; unsigned clock_frame; /* per root hub port */ unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; /* bit vectors (one bit per port) */ unsigned long bus_suspended; /* which ports were already suspended at the start of a bus suspend */ unsigned long companion_ports; /* which ports are dedicated to the companion controller */ unsigned long owned_ports; /* which ports are owned by the companion during a bus suspend */ unsigned long port_c_suspend; /* which ports have the change-suspend feature turned on */ unsigned long suspended_ports; /* which ports are suspended */ /* per-HC memory pools (could be per-bus, but ...) */ struct dma_pool *qh_pool; /* qh per active urb */ struct dma_pool *qtd_pool; /* one or more per qh */ struct dma_pool *itd_pool; /* itd per iso urb */ struct dma_pool *sitd_pool; /* sitd per split iso urb */ struct timer_list iaa_watchdog; struct timer_list watchdog; unsigned long actions; unsigned stamp; unsigned long next_statechange; u32 command; /* SILICON QUIRKS */ unsigned no_selective_suspend:1; unsigned has_fsl_port_bug:1; /* FreeScale */ unsigned big_endian_mmio:1; unsigned big_endian_desc:1; unsigned has_amcc_usb23:1; /* required for usb32 quirk */ #define OHCI_CTRL_HCFS (3 << 6) #define OHCI_USB_OPER (2 << 6) #define OHCI_USB_SUSPEND (3 << 6) #define OHCI_HCCTRL_OFFSET 0x4 #define OHCI_HCCTRL_LEN 0x4 __hc32 *ohci_hcctrl_reg; u8 sbrn; /* packed release number */ /* irq statistics */ #ifdef EHCI_STATS struct ehci_stats stats; # define COUNT(x) do { (x)++; } while (0) #else # define COUNT(x) do {} while (0) #endif /* debug files */ #ifdef DEBUG struct dentry *debug_dir; struct dentry *debug_async; struct dentry *debug_periodic; struct dentry *debug_registers; #endif #ifdef CONFIG_USB_OTG /* * OTG controllers and transceivers need software interaction; * other external transceivers should be software-transparent */ struct otg_transceiver *transceiver; #endif }; |
struct ehci_hcd就像是一个大杂汇,把所有与EHCI的东西都放了进来,在内核中,一个ehci_hcd 的定义 (分配了实际的内存)就描述一个硬件上的EHCI控制器。
我们看看ehci_hcd的主要成员:
struct ehci_caps __iomem *caps; struct ehci_regs __iomem *regs; struct ehci_dbg_port __iomem *debug; __u32 hcs_params; /* cached register copy */ __hc32 *periodic; |
硬件相关,这些结构体的成员都要指向寄存器的地址。 |
|
struct ehci_qh *async; struct ehci_qh *reclaim; unsigned scanning : 1; unsigned periodic_size; __hc32 *periodic; dma_addr_t periodic_dma; unsigned i_thresh; union ehci_shadow *pshadow; int next_uframe; unsigned periodic_sched; |
管理EHCI的等时调度和异性调度。 |
|
struct otg_transceiver *transceiver; |
如果系统中有OTG,此成员负责OTG的操作和管理。 |
|
struct dma_pool *qh_pool; / struct dma_pool *qtd_pool; struct dma_pool *itd_pool; struct dma_pool *sitd_pool; struct timer_list iaa_watchdog; struct timer_list watchdog; unsigned long actions; unsigned stamp; unsigned long next_statechange; struct list_head cached_itd_list; unsigned clock_frame; |
EHCI控制过程中所需要用到的操作系统的资源的管理。 |
|
我们用usb_create_hcd (const struct hc_driver *driver, struct device *dev, const char *bus_name)这个函数向内核中增加一个控制器,也就是创建一个usb_hcd结构,并把usb_hcd与 hc_driver 关联接起来。
Struct usb_hcd 定义的是一个抽象的HCD结构,但我们实际的HCD可能是EHCI,也可能是OHCI/UHCI,这两者之间如何转换呢。HCD中有一个成员是hcd_priv,解决问题的关键就是它。
我们先看两个关键的代码:
struct usb_hcd { ...... /* The HC driver's private data is stored at the end of * this structure. */ unsigned long hcd_priv[0] __attribute__ ((aligned(sizeof(unsigned long)))); }; |
|
struct usb_hcd *usb_create_hcd (const struct hc_driver *driver, struct device *dev, const char *bus_name) { ...... hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL); ...... } |
|
unsigned long hcd_priv[0] __attribute__ ((aligned(sizeof(unsigned long)))); 这样的语句是GNUC语言特有的用法,定义一个指向本结构体末尾的指针。 usb_create_hcd在给hcd分配内存是会多分配 driver->hcd_priv_size 大小的内存,按HCD的定义,这段多分配的内存正是unsigned long hcd_priv[0]指向的地址。这段多分配的地址正是用来存放ehci_hcd 对象的,在hc_driver中,我们一定要把ehci_hcd的长度赋给hcd_priv_size,如下: hcd_priv_size = sizeof(struct ehci_hcd), 这样usb_create_hcd 才能正确的创建usb_hcd对象,ehci_hcd才有容身之地。 至于两者这、之间的转化,可以用下面这些函数为工具: static inline struct ehci_hcd *hcd_to_ehci (struct usb_hcd *hcd) { return (struct ehci_hcd *) (hcd->hcd_priv); } static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci) { return container_of ((void *) ehci, struct usb_hcd, hcd_priv); } |
linux中struct hc_driver 这个结构体来描述USB主控制器功能的接口,这算是linux对控制器驱动要求提供的基本接口,无论是EHCI还是OHCI/UHCI的驱动,要想和上层协调工作,就得实现这些接口,我们驱动EHCI控制器的工作就是要实现这些接口。
我们先研究一下这些接口:
struct hc_driver { const char *description; /* "ehci-hcd" etc */ const char *product_desc; /* product/vendor string */ size_t hcd_priv_size; /* size of private data */ /* irq handler */ irqreturn_t (*irq) (struct usb_hcd *hcd); int flags; #define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */ #define HCD_LOCAL_MEM 0x0002 /* HC needs local memory */ #define HCD_USB11 0x0010 /* USB 1.1 */ #define HCD_USB2 0x0020 /* USB 2.0 */ /* called to init HCD and root hub */ int (*reset) (struct usb_hcd *hcd); int (*start) (struct usb_hcd *hcd); /* NOTE: these suspend/resume calls relate to the HC as * a whole, not just the root hub; they're for PCI bus glue. */ /* called after suspending the hub, before entering D3 etc */ int (*pci_suspend) (struct usb_hcd *hcd, pm_message_t message); /* called after entering D0 (etc), before resuming the hub */ int (*pci_resume) (struct usb_hcd *hcd); /* cleanly make HCD stop writing memory and doing I/O */ void (*stop) (struct usb_hcd *hcd); /* shutdown HCD */ void (*shutdown) (struct usb_hcd *hcd); /* return current frame number */ int (*get_frame_number) (struct usb_hcd *hcd); //控制器的关键是下面两个接口的实现,这两个接口负责处理数据的处理。 /* manage i/o requests, device state */ int (*urb_enqueue)(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags); int (*urb_dequeue)(struct usb_hcd *hcd, struct urb *urb, int status); /* hw synch, freeing endpoint resources that urb_dequeue can't */ void (*endpoint_disable)(struct usb_hcd *hcd, struct usb_host_endpoint *ep); /* root hub support */ int (*hub_status_data) (struct usb_hcd *hcd, char *buf); int (*hub_control) (struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength); int (*bus_suspend)(struct usb_hcd *); int (*bus_resume)(struct usb_hcd *); int (*start_port_reset)(struct usb_hcd *, unsigned port_num); int (*disconnect)(struct usb_hcd *); int (*connect)(struct usb_hcd *, struct usb_device*); /* force handover of high-speed port to full-speed companion */ void (*relinquish_port)(struct usb_hcd *, int); /* has a port been handed over to a companion? */ int (*port_handed_over)(struct usb_hcd *, int); }; |
看看omap 中对这些接口的实现
static const struct hc_driver ehci_omap_hc_driver = { .description = hcd_name, .product_desc = "OMAP-EHCI Host Controller", .hcd_priv_size = sizeof(struct ehci_hcd), /* * generic hardware linkage */ .irq = ehci_irq, .flags = HCD_MEMORY | HCD_USB2, /* * basic lifecycle operations */ .reset = ehci_init, .start = ehci_run, .stop = ehci_stop, .shutdown = ehci_shutdown, /* * managing i/o requests and associated device resources */ .urb_enqueue = ehci_urb_enqueue, .urb_dequeue = ehci_urb_dequeue, .endpoint_disable = ehci_endpoint_disable, .endpoint_reset = ehci_endpoint_reset, /* * scheduling support */ .get_frame_number = ehci_get_frame, /* * root hub support */ .hub_status_data = ehci_hub_status_data, .hub_control = ehci_hub_control, .bus_suspend = ehci_bus_suspend, .bus_resume = ehci_bus_resume, .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, }; |
上表中红色标识的部份都是十分复杂的接口,而且实现起来很困难,但幸运的是,EHCI中对寄存器的接口有了标准,所以这我们有许多可以重用的代码,这些代码就在 drivers/usb/host/ehci-hcd.c中,这里己经实现了与ECHI相关的许许多多重要的接口,一般而言,我们可以使用这些实现而不会有任何问题,就像上表中做的一样。
当然,我们首先要传一些参数给 hc_driver,像EHCI寄存器的基址和irq,这些都无法标准化的东西。
如下:
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); if (!hcd->regs) { dev_err(&pdev->dev, "EHCI ioremap failed/n"); ret = -ENOMEM; goto err_ioremap; } /* we know this is the memory we want, no need to ioremap again */ omap->ehci->caps = hcd->regs; omap->ehci_base = hcd->regs; res = platform_get_resource(pdev, IORESOURCE_MEM, 1); omap->uhh_base = ioremap(res->start, resource_size(res)); if (!omap->uhh_base) { dev_err(&pdev->dev, "UHH ioremap failed/n"); ret = -ENOMEM; goto err_uhh_ioremap; } |
上表中的代码很清晰,从此中我们可以看出标准化的威力,只要给内核传少许几个参数,再实现少许的接口,复杂的EHCI就可以工作起来,不用太花心思去理解我上面讲的调度了,因为大部份复杂的工作早就被先驱者完成了。
下面探讨一下先驱者是如何来尊守EHCI规范的。
Isochronous (High-Speed) Transfer Descriptor (iTD)
上图就是EHCI规范中的Isochronous (High-Speed) Transfer Descriptor (iTD),也就是等时传输数据结构,下面看看在linux kernel中如何定义的.
struct ehci_itd { /* first part defined by EHCI spec */ __hc32 hw_next; /* see EHCI 3.3.1 */ __hc32 hw_transaction [8]; /* see EHCI 3.3.2 */ #define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ #define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */ #define EHCI_ISOC_BABBLE (1<<29) /* babble detected */ #define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ #define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff) #define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */ #define ITD_ACTIVE(ehci) cpu_to_hc32(ehci, EHCI_ISOC_ACTIVE) __hc32 hw_bufp [7]; /* see EHCI 3.3.3 */ __hc32 hw_bufp_hi [7]; /* Appendix B */ /* the rest is HCD-private */ dma_addr_t itd_dma; /* for this itd */ union ehci_shadow itd_next; /* ptr to periodic q entry */ struct urb *urb; struct ehci_iso_stream *stream; /* endpoint's queue */ struct list_head itd_list; /* list of stream's itds */ /* any/all hw_transactions here may be used by that urb */ unsigned frame; /* where scheduled */ unsigned pg; unsigned index[8]; /* in urb->iso_frame_desc */ } __attribute__ ((aligned (32))); |
struct ehci_itd的定义和EHCI spec是能一一对应的,而且注释中也标明了各成员在EHCI sepc中对应的章节。
__hc32 hw_next; |
指向下一个调度数据结构的指针。 |
|
__hc32 hw_transaction [8]; #define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ #define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */ #define EHCI_ISOC_BABBLE (1<<29) /* babble detected */ #define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ #define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff) #define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */ #define ITD_ACTIVE(ehci) cpu_to_hc32(ehci, EHCI_ISOC_ACTIVE) |
传输状态和控制字段 设置成1,EHCI会自动传输本itd数据(对此数据进行调度),完成后EHCI置此位为0,此数据以后不参与调度。 有传输错误是置1(overrun/underrun). 检测到 babble时EHCI会将此字段置1。 不能收到有效数据是EHCI置此位为1(超时,CRC错,PID错等) 传输数据长度,OUT传输时表示软件想要传送的数据长度,由软件写入,IN传输表示软件希望接收的数据长度,传输完成EHCI会写入实际的传输长度。 传输完成 使能ITD的宏。 Page Select (PG).Transaction X Offset这两个字段linux中没有使用。 |
|
__hc32 hw_bufp [7]; |
hw_bufp [0]的0-6位是设备地址,8-11位是端点号,12-31是buf指针。 |
|
__hc32 hw_bufp_hi [7]; |
64位时会用到此位,扩展指针位数 |
|
Split Transaction Isochronous Transfer Descriptor (siTD)
struct ehci_sitd { /* first part defined by EHCI spec */ __hc32 hw_next; /* uses bit field macros above - see EHCI 0.95 Table 3-8 */ __hc32 hw_fullspeed_ep; /* EHCI table 3-9 */ __hc32 hw_uframe; /* EHCI table 3-10 */ __hc32 hw_results; /* EHCI table 3-11 */ #define SITD_IOC (1 << 31) /* interrupt on completion */ #define SITD_PAGE (1 << 30) /* buffer 0/1 */ #define SITD_LENGTH(x) (0x3ff & ((x)>>16)) #define SITD_STS_ACTIVE (1 << 7) /* HC may execute this */ #define SITD_STS_ERR (1 << 6) /* error from TT */ #define SITD_STS_DBE (1 << 5) /* data buffer error (in HC) */ #define SITD_STS_BABBLE (1 << 4) /* device was babbling */ #define SITD_STS_XACT (1 << 3) /* illegal IN response */ #define SITD_STS_MMF (1 << 2) /* incomplete split transaction */ #define SITD_STS_STS (1 << 1) /* split transaction state */ #define SITD_ACTIVE(ehci) cpu_to_hc32(ehci, SITD_STS_ACTIVE) __hc32 hw_buf [2]; /* EHCI table 3-12 */ __hc32 hw_backpointer; /* EHCI table 3-13 */ __hc32 hw_buf_hi [2]; /* Appendix B */ /* the rest is HCD-private */ dma_addr_t sitd_dma; union ehci_shadow sitd_next; /* ptr to periodic q entry */ struct urb *urb; struct ehci_iso_stream *stream; /* endpoint's queue */ struct list_head sitd_list; /* list of stream's sitds */ unsigned frame; unsigned index; } __attribute__ ((aligned (32))); |
__hc32 hw_next; |
指向下一sitd结构的指针,第1-2位表示传输类型(itd/sitd/qt),5-31位表示指针。 |
|
__hc32 hw_fullspeed_ep; |
31位位表示方向,24-30表示端口数,16-22表示hub地址,8-11位表端点号,0-6位表设备地址。 |
|
__hc32 hw_uframe; |
||
__hc32 hw_results; #define SITD_IOC (1 << 31) /* interrupt on completion */ #define SITD_PAGE (1 << 30) /* buffer 0/1 */ #define SITD_LENGTH(x) (0x3ff & ((x)>>16)) #define SITD_STS_ACTIVE (1 << 7) /* HC may execute this */ #define SITD_STS_ERR (1 << 6) /* error from TT */ #define SITD_STS_DBE (1 << 5) /* data buffer error (in HC) */ #define SITD_STS_BABBLE (1 << 4) /* device was babbling */ #define SITD_STS_XACT (1 << 3) /* illegal IN response */ #define SITD_STS_MMF (1 << 2) /* incomplete split transaction */ #define SITD_STS_STS (1 << 1) /* split transaction state */ |
||
__hc32 hw_buf [2]; |
12-31位表示buf地址, |
|
__hc32 hw_backpointer; |
||
__hc32 hw_buf_hi [2]; |
一般置0或指向一个sitd数据。 |
struct ehci_sitd中有hub的信息,当ehci配合hub支持低全速设备时,sitd就派上用场了,hub会把低速数据翻译成高速的sitd数据与ehci通讯。这样ehci在hub的帮助下也可以支持低全速设 备,而不需要兼容控制器(ohci/uhci)的参与了。
Queue Element Transfer Descriptor
linux中的定义
struct ehci_qtd { /* first part defined by EHCI spec */ __hc32 hw_next; /* see EHCI 3.5.1 */ __hc32 hw_alt_next; /* see EHCI 3.5.2 */ __hc32 hw_token; /* see EHCI 3.5.3 */ #define QTD_TOGGLE (1 << 31) /* data toggle */ #define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) #define QTD_IOC (1 << 15) /* interrupt on complete */ #define QTD_CERR(tok) (((tok)>>10) & 0x3) #define QTD_PID(tok) (((tok)>>8) & 0x3) #define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ #define QTD_STS_HALT (1 << 6) /* halted on error */ #define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ #define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ #define QTD_STS_XACT (1 << 3) /* device gave illegal response */ #define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ #define QTD_STS_STS (1 << 1) /* split transaction state */ #define QTD_STS_PING (1 << 0) /* issue PING? */ #define ACTIVE_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_ACTIVE) #define HALT_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_HALT) #define STATUS_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_STS) __hc32 hw_buf [5]; /* see EHCI 3.5.4 */ __hc32 hw_buf_hi [5]; /* Appendix B */ /* the rest is HCD-private */ dma_addr_t qtd_dma; /* qtd address */ struct list_head qtd_list; /* sw qtd list */ struct urb *urb; /* qtd's urb */ size_t length; /* length of buffer */ } __attribute__ ((aligned (32))); |
hw_next |
指向下一传输单元 |
|
hw_alt_next |
也是指向一个传输单元,当处理当前传输时遇到一个短包此字段会有用。 |
|
hw_token #define QTD_TOGGLE (1 << 31) /* data toggle */ #define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) #define QTD_IOC (1 << 15) /* interrupt on complete */ #define QTD_CERR(tok) (((tok)>>10) & 0x3) #define QTD_PID(tok) (((tok)>>8) & 0x3) #define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ #define QTD_STS_HALT (1 << 6) /* halted on error */ #define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ #define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ #define QTD_STS_XACT (1 << 3) /* device gave illegal response */ #define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ #define QTD_STS_STS (1 << 1) /* split transaction state */ #define QTD_STS_PING (1 << 0) /* issue PING? */ |
||
hw_buf [5] |
buf指针 |
|
hw_buf_hi [5]; |
兼容64位的指针 |
Queue Head
队列头传输结构在linux中的定义
struct ehci_qh {
/* first part defined by EHCI spec */
__hc32 hw_next; /* see EHCI 3.6.1 */
__hc32 hw_info1; /* see EHCI 3.6.2 */
#define QH_HEAD 0x00008000
__hc32 hw_info2; /* see EHCI 3.6.2 */
#define QH_SMASK 0x000000ff
#define QH_CMASK 0x0000ff00
#define QH_HUBADDR 0x007f0000
#define QH_HUBPORT 0x3f800000
#define QH_MULT 0xc0000000
__hc32 hw_current; /* qtd list - see EHCI 3.6.4 */
/* qtd overlay (hardware parts of a struct ehci_qtd) */
__hc32 hw_qtd_next;
__hc32 hw_alt_next;
__hc32 hw_token;
__hc32 hw_buf [5];
__hc32 hw_buf_hi [5];
/* the rest is HCD-private */
dma_addr_t qh_dma; /* address of qh */
union ehci_shadow qh_next; /* ptr to qh; or periodic */
struct list_head qtd_list; /* sw qtd list */
struct ehci_qtd *dummy;
struct ehci_qh *reclaim; /* next to reclaim */
struct ehci_hcd *ehci;
/*
* Do NOT use atomic operations for QH refcounting. On some CPUs
* (PPC7448 for example), atomic operations cannot be performed on
* memory that is cache-inhibited (i.e. being used for DMA).
* Spinlocks are used to protect all QH fields.
*/
u32 refcount;
unsigned stamp;
u8 qh_state;
#define QH_STATE_LINKED 1 /* HC sees this */
#define QH_STATE_UNLINK 2 /* HC may still see this */
#define QH_STATE_IDLE 3 /* HC doesn't see this */
#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on reclaim q */
#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */
/* periodic schedule info */
u8 usecs; /* intr bandwidth */
u8 gap_uf; /* uframes split/csplit gap */
u8 c_usecs; /* ... split completion bw */
u16 tt_usecs; /* tt downstream bandwidth */
unsigned short period; /* polling interval */
unsigned short start; /* where polling starts */
#define NO_FRAME ((unsigned short)~0) /* pick new start */
struct usb_device *dev; /* access to TT */
} __attribute__ ((aligned (32)));
hw_next |
队列头水平指向的指针 |
|
hw_info1 |
端点的特性 |
|
hw_current |
当前指针 |
|
hw_qtd_next |
下一传输块指针 |
|
hw_alt_next |
||
hw_buf |
buf指针 |
|
hw_buf_hi |
兼容64位的指针 |
Periodic Frame Span Traversal Node
FSTN结构用来管理跨越几个主机时间帧( Host-frame boundary)的全速和低速传输,当 HCIVERSION寄存器指示的ehci版本低于0.96时,此结构不能使用,FSTN有点像CPU中的管理中断的栈,保存两个地址,一个指要处理的数据地址,一个指向返回地址。
struct ehci_fstn { __hc32 hw_next; /* any periodic q entry */ __hc32 hw_prev; /* qh or EHCI_LIST_END */ /* the rest is HCD-private */ dma_addr_t fstn_dma; union ehci_shadow fstn_next; /* ptr to periodic q entry */ } __attribute__ ((aligned (32))); |
我们发现,linux中itd/sitd/qtd/qh 的描述除了有ehci规范中要求的字段外,还定义了其它的字段,这些字段与linux中的usb核心系统有密切的关系。
dma_addr_t |
一个符合DMA访问要求的地址 |
|
urb * |
指向URB的指针 |
|
ehci_iso_stream * |
管理所有的sitd和itd传输单元中的信息 |
|
list_head* |
链表指针 |
|
上面列举的几个数据结构就是EHCI的核心,软件要做的事就是把上层设备驱动传下来的数转换成一个个的符合itd/sitd/qh/qtd/fstn要求的数据并在内存中放好,利用itd/sitd/qh/qtd/fstn的中定义的指针把这些数据链成链表,这些数据就相当于CPU中指令的集合,CPU的运行要求把指令在内存中放好,然后在PC寄存器的帮助下按的指令执行,这就是程序。而在EHCI中,FRINDEX寄存器和AsyncListAddr寄存器就扮演 了PC寄存器的角色。
1、 Linux中的USB设备驱动
当然这只是理想的状态,所谓的理想就是永远也无法实现的现实,当调试USB时我们还是需要从头到尾的把USB协议研究一遍。
只有站在USB核心层上,我们才能清晰的看到一般USB书籍中提到的端点,接口,通道和四种传输内型(控制传输,中断传输,批量传输,等时传输)等这些逻辑上的概念。
在linux的架构中,有一种数据数据叫URB封装了所有了这些概念,作为USB设备驱动,要使用USB通路来传递数据,我们只要操控URB就可以了。
typedef struct urb //USB Request Block,包含了建立任何 USB传输所需的所有信息,并贯穿于USB协议栈对数据处理的整个过程 { spinlock_t lock; & nbsp; // lock for the URB void *hcpriv; // private data for host controller与主机控制器相关数据,对USB内核层是透明 struct list_head urb_list; // list pointer to all active urbs双向指针,用于将此URB连接到处于活动的URB双向链表中 struct urb *next; // pointer to next URB 指向下一个URB的指针 struct usb_device *dev; // pointer to associated USB device 接受此URB的USB设备指针 unsigned int pipe;// pipe information表示设备的某个端点和客户端驱动程序之间的管道 int status; ; ; // returned status 返回状态 unsigned int transfer_flags; // USB_DISABLE_SPD | USB_ISO_ASAP | etc. USB_DISABLE_SPD //拒绝短数据包,即比最大传输包长度小的数据包 USB_ISO_ASAP //用于实时传输,告诉主机控制器立即进行此请求的数据传输。如果没有置位,则需要给start_frame赋值,用来通知主机控制器该在哪个帧上开始此请求的数据传输 USB_ASYNC_UNLINK //告诉USBD采用异步方式取消请求 USB_QUEUE_BULK //表示批量请求可以排队,一般一个设备的批量请求端点只有一个URB USB_NO_FSBR //表示全速传输站用的带宽不要回收 USB_ZERO_PACKET //表示批量传输的数据长度等于端点的包最大长度时,主机控制器在发送完数据后,再发送一个零长度的包表示数据结束 USB_TIMEOUT_KILLED //本位只被HCD设置,表示发生了超时。客户驱动可以给URB的处理设置一个超时时间,如果处理超时,则要求USBD结束对此URB的处理,URB的返回信息中会反映该此状态。 void *transfer_buffer ; ; // associated data buffer传输数据缓存区指针,接收或发送设备的数据,它必须是物理连续的,不可换页的内存块,用kmalloc(,GFP_KERNEL)分配 int actual_length; // actual data buffer length 实际数据长度 int bandwidth; //此请求每次占用一帧的带宽,只适用实时/中断传输 int start_frame; //此请求所开始传输的帧号,只适用实时/中断传输。中断传输时,表示返回启动此请求的第一次中断传输的帧号。实时传输时,指明处理第一个实时请求数据报包的帧号,如果设置了USB_ISO_ASAP,此变量表示返回启动第一次实时传输的帧号。 int number_of_packets; // number of packets in this request (iso)此请求所包含的数据包数,只适合实时传输 int interval; // polling interval (irq only) 中断传输的周期,1〈= interval〈=255 int error_count; // number of errors in this transfer (iso only)发生传输错误次数的累加值,只适用实时传输 int timeout; // timeout (in jiffies) void *context; // context for completion routine回调函数中的参数 usb_complete_t complete; // pointer to completion routine 指向回调函数的指针。当数据传输完成后,主机控制器驱动会回调该函数。 iso_packet_descriptor_t iso_frame_desc[0]; 要进行实时传输的结构数组,每个结构表示一次数据传输 } |
上表中就是URB的具体内容,URB对USB协议解析得己经很清楚了,但是还是很复杂,我们需要更要更有利的工具,内核己经提供了这类操作URB的工具:
usb_fill_control_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, unsigned char *setup_packet, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context) |
初始化控制URB结构,pipe是端点通道,setup_packet指向setup_packet数据,transfer_buffer指向transfer数据, |
|
usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context) |
初始化块传输的URB |
|
usb_fill_int_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context, int interval) |
初始化中断传输的URB |
|
usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout); |
||
usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout); |
||
usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout); |
||
extern void usb_init_urb(struct urb *urb); extern struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags); extern void usb_free_urb(struct urb *urb); extern struct urb *usb_get_urb(struct urb *urb); extern int usb_submit_urb(struct urb *urb, gfp_t mem_flags); extern int usb_unlink_urb(struct urb *urb); extern void usb_kill_urb(struct urb *urb); extern void usb_poison_urb(struct urb *urb); extern void usb_unpoison_urb(struct urb *urb); extern void usb_kill_anchored_urbs(struct usb_anchor *anchor); usb_poison_anchored_urbs(struct usb_anchor *anchor); extern void usb_unpoison_anchored_urbs(struct usb_anchor *anchor); extern void usb_unlink_anchored_urbs(struct usb_anchor *anchor); extern void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor); extern void usb_unanchor_urb(struct urb *urb); extern int usb_wait_anchor_empty_timeout(struct usb_anchor *anchor, unsigned int timeout); extern struct urb *usb_get_from_anchor(struct usb_anchor *anchor); extern void usb_scuttle_anchored_urbs(struct usb_anchor *anchor); extern int usb_anchor_empty(struct usb_anchor *anchor); |
#define PIPE_ISOCHRONOUS 0 #define PIPE_INTERRUPT 1 #define PIPE_CONTROL 2 #define PIPE_BULK 3 #define usb_pipein(pipe) #define usb_pipeout(pipe) #define usb_pipedevice(pipe) #define usb_pipeendpoint(pipe) #define usb_pipetype(pipe) #define usb_pipeisoc(pipe) #define usb_pipeint(pipe) #define usb_pipecontrol(pipe) #define usb_pipebulk(pipe) #define usb_gettoggle(dev, ep, out) #define usb_dotoggle(dev, ep, out) #define usb_settoggle(dev, ep, out, bit) #define usb_sndctrlpipe(dev,endpoint) #define usb_rcvctrlpipe(dev,endpoint) #define usb_sndisocpipe(dev,endpoint) #define usb_rcvisocpipe(dev,endpoint) #define usb_sndbulkpipe(dev,endpoint) #define usb_rcvbulkpipe(dev,endpoint) #define usb_sndintpipe(dev,endpoint) #define usb_rcvintpipe(dev,endpoint) |
设备驱动想要使用usb总线和设备通信,一般先要初始化urb结构,把你所想要传送的数据用系统提供的urb操作工具填入urb中,然后用 usb_submit_urb向usb核心提交。
我们想要了解usb设备驱动层的数据是如何到达USB主控制器并发送到总线上去的,usb_submit_urb是一个很好的突破口。
static int ehci_urb_enqueue ( struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags ) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); struct list_head qtd_list; INIT_LIST_HEAD (&qtd_list); switch (usb_pipetype (urb->pipe)) { case PIPE_CONTROL: /* qh_completions() code doesn't handle all the fault cases * in multi-TD control transfers. Even 1KB is rare anyway. */ if (urb->transfer_buffer_length > (16 * 1024)) return -EMSGSIZE; /* FALLTHROUGH */ /* case PIPE_BULK: */ default: if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) return -ENOMEM; return submit_async(ehci, urb, &qtd_list, mem_flags); case PIPE_INTERRUPT: if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) return -ENOMEM; return intr_submit(ehci, urb, &qtd_list, mem_flags); case PIPE_ISOCHRONOUS: if (urb->dev->speed == USB_SPEED_HIGH) return itd_submit (ehci, urb, mem_flags); else return sitd_submit (ehci, urb, mem_flags); } } |
USB设备驱动URB数据直到流到了 ehci_urb_enqueue才各回各家,各找各妈。
控制传输数据由submit_async处理进入了qtd队列;
中断传输数据由intr_submit处理被打包成qtd挂在itd队列上。
等时数据则由itd_submit处理,打包成itd队列。如果是支持低/全速设备,还有一个sitd_submit的处理,生成sitd队列。
列表准备好了后,乘下的就是要对寄存器操作了,以submit_async为例,这个关键的动作由qh_link_async()这个函数完成:
static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) { __hc32 dma = QH_NEXT(ehci, qh->qh_dma); struct ehci_qh *head; /* (re)start the async schedule? */ head = ehci->async; timer_action_done (ehci, TIMER_ASYNC_OFF); if (!head->qh_next.qh) { u32 cmd = ehci_readl(ehci, &ehci->regs->command); if (!(cmd & CMD_ASE)) { /* in case a clear of CMD_ASE didn't take yet */ (void)handshake(ehci, &ehci->regs->status, STS_ASS, 0, 150); cmd |= CMD_ASE | CMD_RUN; ehci_writel(ehci, cmd, &ehci->regs->command); ehci_to_hcd(ehci)->state = HC_STATE_RUNNING; /* posted write need not be known to HC yet ... */ } } /* clear halt and/or toggle; and maybe recover from silicon quirk */ if (qh->qh_state == QH_STATE_IDLE) qh_refresh (ehci, qh); /* splice right after start */ qh->qh_next = head->qh_next; qh->hw_next = head->hw_next; wmb (); head->qh_next.qh = qh; head->hw_next = dma; qh->qh_state = QH_STATE_LINKED; /* qtd completions reported later by interrupt */ } |
在组织完qtd队列后,我们就把ehci的控制COMMAND寄存器的 CMD_ASE 和 CMD_RUN字段使能,ehci就开始调度(运行)了。
这里我们并没有看见让ASYNCLISTADDR指向qh队列头,这件事其实早就做好了,看下面的函数:
usb/host/ehci_hcd.c |
|
/* start HC running; it's halted, ehci_init() has been run (once) */ static int ehci_run (struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); ...... if ((retval = ehci_reset(ehci)) != 0) { ehci_mem_cleanup(ehci); return retval; } ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next); |
至此,看到对ASYNCLISTADDR和FRINDEX两个寄存器的操作,所有与EHCI控制器有关的疑问都应该解决了。我们可以放心的在设备驱动中使用usb总线了。
从设备中往往会有endpoint和pipe的概念,USB主机设备驱动层也使用endpoint和pipe的概念,这样主从之间就对上话了,通讯就没有问题了。
一般而言,数据通路被协议还可以理解,但USB规范中,设备驱动也被协议了。
1-audio:表示一个音频设 备。 |
|
2-communication device:通讯设备,如电话,moden等等。 |
|
3-HID:人机交互设备,如键盘,鼠标等。 |
|
6-image图象设备,如扫描仪,摄像头等,有时数码相 机也可归到这一类。 |
|
7-打印机类。如单向,双向打印机等。 |
|
8-mass storage海量存储类。所有带有一定存储功能的都可以归到这一类。如数码相机大多数都归这一类。 |
|
9-hub类。 |
|
11-chip card/smart card。 |
|
13 --Content Security |
|
14--Video (Interface) |
|
15--Personal Healthcare |
|
220--Diagnostic Device |
|
224--Wireless Controller (Interface) |
|
239--Miscellaneous |
|
254--Application Specific (Interface) |
|
255-vendor specific.厂家的自定义类,主要用于一些特殊的设备。如接口转接卡等。 |
上表中的设备和接口在USB规范中都有规定,这可能是USB相关的东西调试起来很复杂吧,不像I2C/PCI那样好理解,所以干脆USB社区连设备的规范都定了下来,这样就可以生产出许多免驱动的设备了。
struct usb_device { ; //代表一个USB设备 enum { USB_SPEED_UNKNOWN = 0, /* enumerating */ USB_SPEED_LOW, USB_SPEED_FULL, /* usb 1.1 */ USB_SPEED_HIGH /* usb 2.0 */ } speed; //设备速度,低速/全速/高速 struct usb_device *tt; /* usb1.1 device on usb2.0 bus */,事务处理解释器 int ttport; ; /* device/hub port on that tt */设备所连接的具有事务处理解释器功能的集线器端口 atomic_t refcnt; ; /* Reference count */引用计数 struct semaphore serialize; //用于同步 unsigned int toggle[2] ; ; /* one bit for each endpoint ([0] = IN, [1] = OUT) */用于同步切换的位图,每个端点占用1位,[0]表示输入,[1]输出 unsigned int halted[2]; /* endpoint halts; one bit per endpoint # & direction; [0] = IN, [1] = OUT */表示端点是否处于停止状态的位图 int epmaxpacketin[16] ; ;/* INput endpoint specific maximums */输入端点的最大包长 int epmaxpacketout[16] ; ; /* OUTput endpoint specific maximums */输出端点的最大包长 struct usb_device *parent; //表示设备所连的上游集线器指针 struct usb_bus *bus; /* Bus we're part of */设备所属的USB总线系统 struct usb_device_descriptor descriptor;/* Descriptor */ 设备描述符 struct usb_config_descriptor *config; /* All of the configs */指向设备的配置描述符和其所包含的接口描述符,端点描述符的指针 struct usb_config_descriptor *actconfig;/* the active configuration */当前的配置描述符指针 char **rawdescriptors; ; ;/* Raw descriptors for each config */ int have_langid; /* whether string_langid is valid yet *// 是否有string_langid int string_langid; /* language ID for strings */和字符描述符相关的语言ID void *hcpriv; ; ; /* Host Controller private data */设备在HCD层占用的资源指针,对USB内核层是透明的 /* usbdevfs inode list */ 设备在usbdevfs中的inode列表 struct list_head inodes; struct list_head filelist; /* * Child devices - these can be either new devices * (if this is a hub device), or different instances * of this same device. * * Each instance needs its own set of data structures. */只对当前设备是集线器的情况有效 int maxchild; /* Number of ports if hub */ hub的下游端口数 struct usb_device *children[USB_MAXCHILDREN]; hub所连设备指针 }; struct usb_bus { //USB总线系统 int busnum; ; ; /* Bus number (in order of reg) */当前总线系统的序列号,Linux支持多总线系统并为它们编号 #ifdef DEVNUM_ROUND_ROBIN int devnum_next; /* Next open device number in round-robin allocation */ #endif /* DEVNUM_ROUND_ROBIN */给连接到子系统上的设备分配设备号的数据结构 struct usb_devmap devmap; /* Device map */给连接到子系统上的设备分配设备号的数据结构 struct usb_operations *op; /* Operations (specific to the HC) */HCD为USB内核提供的一系统函数集指针 struct usb_device *root_hub; /* Root hub */指向根Hub的指针 struct list_head bus_list; 双向链表指针,USB内核用一个双向链表来维护系统中所有USB总线系统 void *hcpriv; /* Host Controller private data */与主机控制器相关数据,对USB内核层是透明 int bandwidth_allocated; /* on this Host Controller; applies to Int. and Isoc. pipes; measured in microseconds/frame; range is 0..900, where 900 = 90% of a 1-millisecond frame */当前子系统的带宽使用情况,单位是微秒/帧,取值范围[0,900] int bandwidth_int_reqs; ; /* number of Interrupt requesters */子系统中当前的中断传输的数量 int bandwidth_isoc_reqs; /* number of Isoc. requesters */子系统中当前的实时传输的数量 /* usbdevfs inode list */ 在usbdevfs中的inode列表 struct list_head inodes; atomic_t refcnt; }; struct usb_driver { //客户端驱动程序为USB内核提供的调用接口 const char *name; //客户端驱动程序的字符串名称,用于避免重复安装和卸载 void *(*probe)(//给USB内核提供的函数,用于判断驱动程序是否能对设备的某个接口进行驱动,如能则分配资源 struct usb_device *dev, ; ;/* the device */ unsigned intf, ; ; /* what interface */ const struct usb_device_id *id /* from id_table */ ); void (*disconnect)(struct usb_device *, void *);//给USB内核提供的函数,用于释放设备的某个接口所占用的资源 struct list_head driver_list;//对应的双向指针,USB内核通过一个双向指针链表维护USB子系统中所用的客户端驱动程序 struct file_operations *fops; int minor; 驱动的次版本号 struct semaphore serialize; /* ioctl -- userspace apps can talk to drivers through usbdevfs */ int (*ioctl)(struct usb_device *dev, unsigned int code, void *buf); /* support for "new-style" USB hotplugging * binding policy can be driven from user mode too */ const struct usb_device_id *id_table; /* suspend before the bus suspends; * disconnect or resume when the bus resumes */ // void (*suspend)(struct usb_device *dev); // void (*resume)(struct usb_device *dev); }; |
看来,USB设备驱动其实走的也是老套路,重点还是要放在与设备的通讯上。
与设备相关的,usb规范又定义了许多东西,协议中把这些叫描述符,我觉得应该叫配置更合理些。如下图,设备的配置,配置(Configuration)的配置,接口的配置,端点的配置。
![]() |
USB设备驱动中在打通数据通路后,就要理清各种配置了,根据这些配置再与设备作下一步的交流。
USB设备的识别过程
有了上面的准备知识,我们可以看一看当一个USB设备接入后是如何被识别的。
USB系统是一种主从结构,所有的通讯都是host发起的,从设备永远处于被动状态,但在主设备还是需要一个中断来唤醒。
1)如果是OTG设备,接入USB从设备时,USB-ID线被拉低,OTG中断触发,OTG将控制器切换到host状态。
3)主机使用0地址通道来向总线上的新设备进查询,取得设备的一些基本的信息。
4)主机发取得设备的基本信息后发出复位指令,并给设备分配一个地址。设备复位并有了新的地址。
5)主机用新的地址继续查询设备的各种描述符(配置),主机通过设备的这些配置为设备在主机上寻找一个驱动。
6)一旦找到设备的驱动程序,枚举过程就完成了,控制权交到设备驱动的手上,设备在设驱动的控制下工作。
USB设备驱动和USB gadge驱动应该是我们接触得最多的与usb相关的驱动,这也是初学者很容易混淆的两个概念,再看下面这张图。
![]() |
USB设备的调试
USB设备难就难在调试,因为USB的总线频率很高,一般的示波器都抓不到这样的信号,即使高频的示波器抓到USB信号,想用肉眼把他解析出来也不是件容易的事,所以调试USB设备最好能有USB协议分析器,那怕是个逻辑分析仪也不错。
作为一个平民驱动工程师,没有那么豪华的装备,怎么办呢?
如果是调试从调备,WINDOWS下有一个很有名的总线调试工具bushound,如下图。
linux下这样的软件就少得多了,想要在linux下调式USB,可以借助usbmon这个模块,可以在内核编译时选中这一项,也可以编译成模块然后insmod这个模块。
1)insmod usbmon.ko
2) mount -t debugfs none_debugs /sys/kernel/debug
3)查看 /sys/kernel/debug
ls /sys/kernel/debug/usbmon/
0s 0u 1s 1t 1u
4)下面这就是总线上的数据
f4146900 2833361214 S Bo:1:004:2 -115 31 = 55534243 29010000 00000000 00000600 00000000 00000000 00000000 000000 f4146900 2833361268 C Bo:1:004:2 0 31 > f4146900 2833361302 S Bi:1:004:1 -115 13 < f4146900 2833361391 C Bi:1:004:1 0 13 = 55534253 29010000 00000000 00 f4146900 2835361212 S Bo:1:004:2 -115 31 = 55534243 2a010000 00000000 00000600 00000000 00000000 00000000 000000 f4146900 2835361274 C Bo:1:004:2 0 31 > f4146900 2835361298 S Bi:1:004:1 -115 13 < f4146900 2835361397 C Bi:1:004:1 0 13 = 55534253 2a010000 00000000 00 f5d38d00 2835764165 C Ii:1:001:1 0:2048 2 = 4000 f5d38d00 2835764175 S Ii:1:001:1 -115:2048 4 < f46a0280 2835764187 S Ci:1:001:0 s a3 00 0000 0006 0004 4 < f46a0280 2835764191 C Ci:1:001:0 0 4 = 00010100 f46a0280 2835764194 S Co:1:001:0 s 23 01 0010 0006 0000 0 f46a0280 2835764198 C Co:1:001:0 0 0 f46a0280 2835767349 S Ci:1:001:0 s a3 00 0000 0006 0004 4 < f46a0280 2835767355 C Ci:1:001:0 0 4 = 00010000 f46a0300 2835794170 S Ci:1:001:0 s a3 00 0000 0006 0004 4 < f46a0300 2835794223 C Ci:1:001:0 0 4 = 00010000 f46a0b80 2835819162 S Ci:1:001:0 s a3 00 0000 0006 0004 4 < f46a0b80 2835819207 C Ci:1:001:0 0 4 = 00010000 f46a0280 2835847167 S Ci:1:001:0 s a3 00 0000 0006 0004 4 < f46a0280 2835851345 C Ci:1:001:0 0 4 = 00010000 f46a0280 2835878175 S Ci:1:001:0 s a3 00 0000 0006 0004 4 < f46a0280 2835878214 C Ci:1:001:0 0 4 = 00010000 f5d38d00 2836628334 C Ii:1:001:1 0:2048 2 = 4000 f5d38d00 2836628344 S Ii:1:001:1 -115:2048 4 < f46a0980 2836628356 S Ci:1:001:0 s a3 00 0000 0006 0004 4 < f46a0980 2836628360 C Ci:1:001:0 0 4 = 01050100 f46a0980 2836628363 S Co:1:001:0 s 23 01 0010 0006 0000 0 f46a0980 2836628367 C Co:1:001:0 0 0 f46a0980 2836628370 S Ci:1:001:0 s a3 00 0000 0006 0004 4 < f46a0980 2836628372 C Ci:1:001:0 0 4 = 01050000 |
我们需要解这些数据,才能知道总线上传送的是什么,kernel源码下面的Documentation/usb/目录中有一个usbmon.txt的文件详细说明了这些数据的格式,有了说明还不够,如果能根据各类设备的协议将数据作理进一步的解析就更好了。
我们以下面这段数据为例来说明一下
f4146900 2835361212 S Bo:1:004:2 -115 31 = 55534243 2a010000 00000000 00000600 00000000 00000000 00000000 000000
f4146900 |
urb内存地址 |
|
f4146900 |
时间戳 |
|
S |
事件类型(S-submission, C-Callback, E-submission error) |
|
B |
端点类型I(中断),C(控制),B(Bulk)和Z(ISOC) |
|
o |
数据方向(i或者o) |
|
:1 |
bus总线号 |
|
:004 |
该bus总线分配到的设备地址[luther.gliethttp] |
|
:2 |
端点号,对于in端点,为异或^0x80,即去掉第8位1值的数据,比如0x81,那么这里数据为0x81 ^ 0x80 = 1;[luther.gliethttp] |
|
31 |
数据的长度 |
|
=55534243 29010000 00000000 00000600 00000000 00000000 00000000 000000 |
数据 |
另外,还有一些其它的标识符号:
< -- |
< -- 表示有数据要上传,后面有需要接收的数据,后面会有IN动作, |
|
> -- |
> -- 表示数据部分已经成功下发 |
仅从USBMON很难得到对调试驱动有帮助的数据,要想在linux下方便的调试USB设备,还有许多工作需要我们做。
转载 linux usb相关推荐
- linux usb驱动
0.usb协议 usb的版本: 硬件 usb 1.0 OHCI 微软 硬件 > 软件 usb ...
- Linux USB驱动框架分析 【转】
转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ...
- OS相关驱动 Linux USB驱动框架分析
初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结当然不可缺,更何况我决定为嵌入式卖命了.好,言归正传,我说一说这段时间的收获,跟大家分享一下Linux的驱动开发 ...
- [转载]Linux内核中的platfor…
原文地址:[转载]Linux内核中的platform机制作者:joee33 从Linux 2.6起引入了一套新的驱动管理和注册机制:platform_device和platform_drive ...
- Linux USB 驱动开发(五)—— USB驱动程序开发过程简单总结
http://blog.csdn.net/zqixiao_09/article/details/51057086 设备驱动程序是操作系统内核和机器硬件之间的接口,由一组函数和一些私有数据组成,是应用程 ...
- linux usb初始化
linux usb 初始化 谨以此文纪念过往的岁月 一.前言 对于usb的普通驱动,我们了解了不少,但是对于usb的真正核心还是不是太理解.该文中对于usb的初始化进行一定的学习,如有不对之处,请各位 ...
- garmin USB: linux USB host驱动
以garmin usb为例: 1)编译生成garmin_usb.ko文件,使用insmod命令安装模块 2)使用lsmod命令可查看内核已经安装好模块:garmin_usb 3)插入USB设备后,使用 ...
- linux usb声卡 submit urb,linux usb urb详解
linux usb urb详解 谨以此文纪念过往的岁月 一.前言 在前文中看过了hub的驱动以及host的驱动还有usb设备的驱动,在把这些东西关联起来的东东中,一个很重要的urb(usb reque ...
- linux内核配置usb虚拟串口,Linux USB虚拟串口设备
Linux内核中usb设备侧驱动程序分成3个层次:UDC驱动程序.Gadget API和Gadget驱动程序.UDC驱动程序(USB控制器)直接访问硬件,控制USB设备和主机间的底层通信,向上层提供与 ...
最新文章
- MySQL----ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes
- 初识RabbitMQ,附RabbitMQ+PHP演示实例(亲测通过)
- MongoDB集群分片部署指南
- 判断是否为自然数java_java判断输入的是否是自然数
- 关于ecshop中jquery与js冲突解决的方案
- JBookManager v1.00.2008314 (编辑管理您的Jar电子书)
- win10mongodb链接_Windows 10 安装 Mongodb
- 如何为Swift进行宏定义
- 淘宝技术这十年(子柳著)读书笔记
- java uint64_Java 中的 uint64
- ftp怎么用计算机打开 不用浏览器打开,win7 访问ftp站点 不用浏览器显示
- log4j从入门到了解
- Codeforces Round #572(div2)部分题解(A~C,E)
- Geode中使用PDX序列化最佳实践
- 【聚水潭】胜算操作手册
- 层次包围盒和均匀网格
- 4、网络层:三层物理设备:集线器、交换机、路由器【IP、ICMP、BGP、OSPF
- ImportError: DLL load failed while importing win32api
- 【无标题】7-11 sdut-C语言实验- 数列有序! 7-12 sdut-C语言实验- 中位数 7-13 sdut-C语言实验-各位数字之和排序 7-14 sdut-C语言实验- 冒泡排序中数据交换
- android 百度鹰眼集成,ios 百度鹰眼集成
热门文章
- 【系统集成项目管理工程师】2021年5月上午+下午真题及答案解析
- 重新建立博客的一段话
- 世界是平的,中国呢?
- bert中文短文本句向量生成、相似度计算(GPU版、windows、win10、linux、django和flask可用)
- 山东理工大学计算机组成原理试题,山东理工大学成人高等教育计算机组成原理试卷(共3份)...
- 什么是黑链以及黑链的防范技术
- 速达软件开发版使用技巧-销售开单单据打印格式设计
- 《疯狂Android讲义》学习笔记一
- MAPICS系统简单概述(zt)
- Yahoo 日本 api