linux usb系统
发布日期:2021-06-29 02:35:06 浏览次数:2 分类:技术文章

本文共 54142 字,大约阅读时间需要 180 分钟。

1、   简述:

USB 出自豪门,一问世便有 IBM,Microsoft,compaq 等前呼后拥,不红实在是没有道理,以致于连三岁小毛孩都知道买游戏手柄要买 USB 的。

USB 名气这么大,但 USB 是什么呢?要是想找份写驱动的活谋生,常会被问到这样的要求: “ 给我讲讲 USB 。 ”

无论你是谁,遇到这样的问题一定要扭头就跑,不然一定被雷死。

USB 使用方便,硬件设计也简单,但开发人员还是谈 USB 色变。为什么呢,因为 USB 简单方便的外表下面是一个十分复杂的 USB 系统。简单方便和成本低廉的代价就是逻辑上的复杂,这里的逻辑指的就是 USB 协议。 USB 的协议之多之杂在 IT 界内绝对是史无前例的,数据传输的协议,控制协议,主控制器协议,设备相关的协议,硬件接口的协议,这些都要和 USB 扯上关系,都称自己是 USB 协议,以至于有很多人雄心勃勃的研读了几年的 USB 协议才发现所研读的内容和自己要想了解的东西扯不上关系。

在我看来, USB 的成功主得益于两点 :

首先是集成电路技术的发展,使得控制器能集成更复杂的逻辑( USB 协议)的同进价格还能降下来。一个产品要想推广开,首先是要足够便宜,要让大众能买得起用得起,其次是功能强大,集成电路技术的发展给了 usb 这两个条件。

但集成电路的发展带来的好处也被 USB 同时代的其它接口标准(如 1394 )所共享, USB 绝对并成本最低,性格比最高,协议设计的最完美的接口,但 USB 为什么能独霸天下呢?是中国人的都知道,爹妈很重要。这就是 USB 之所以能成名的第二点,出生好。

有了 Intel,IBM,Microsoft,compaq 这群好爹妈的支持,就算是阿斗也能坐上皇位,向况 USB 不是阿斗。这群好爹妈运用自己强大的影响力,给 USB 制定了一系列的标准,要想跟他们混就得尊守这个标准,其它团体也是敢怒不敢言,屁踮屁踮的就奔着 USB 去了。

 

 

USB 系统概述

 

首先,我们来回答一下面试官的问题, USB 是什么, usb 就是 Universal Serial Bus (通用串行总线)。什么是 Universal Serial Bus 呢。这个用语言就难以表达了,请看下面两张图:                

 
   

 

 
   

 

 

少 许的艺术细胞加上平时的使用经验就能理解第一张图的含义,一个 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 连接的基本知识

 

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 能做些什么。

在 OTG 中,我们一般不把设备叫做主设备或从设备,而称作 A-DEVICE 和 B-DEVICE 。一般而言, A-DEVICE 作主, B-DEVICE 作从,但也不能这样绑定, A-DEVICE 也可以作从,这时 A-DEVICE 仍要为总线提供电力。

OTG 设备使用插头中的 ID 引脚来区分 A/B Device , ID 接地被称作为 A-Device, 为连接时候的 USB Host , A-Device 始终为总线提供电力。 ID 悬空被称作为 B-Device, 为连接时候的 USB Device( 作从 ) ,设备的 USB Host/USB Device 角色可以通过 HNP 切换。

OTG Device :使用 Micro AB 插座,可以在运行时切换 Host/Device 。 

仅外设 B-Device :仅仅能作为外设的 B-Device (分为插头一体和插头线缆分离的)。

可见, OTG 主要是负责控制器状态的切换,这种切换要根据接入的设备来判断。 OTG 主要使用在嵌入式设备中,说到嵌入式不能不提降低功耗了,所以仅有 ID 线的检测还是不够的。

 

OTG 中的三大协议

SRP ( Session Request Protocol ): 

B-Device 使用。通过数据线上的脉冲,请求 A-Device 打开 VBUS 并且开始-个 Session 。 Session 为从 VBUS 打开到关闭这一段时间。

支持: A-Device 允许回应 SRP , B-Device (包括仅能作为外设的 B-Device ),允许发起 SRP 。一个能够支持 HNP 的 B- Device 应该能够发起 SRP 。当 A 插头插入时关闭 VBus 的 Host 必须支持回应 SRP , VBus 总是打开的 Host 不必响应 SRP 。

 

ADP ( Attach Detection Protocol ):

提供设备检测是否有对端设备插入。

 

HNP ( Host Negotiation Protocol ):

OTG 设备通过 HNP 来切换 Host/Device 角色。

 

OTG 不同连接方式的不同过程

 

OTG Device /Embedded Host 与 仅作为外设的 B-device (带 A 插头型)

Host 端检测到 A 插头插入,停止 ADP ,打开 VBus ,因为 B-Device 的 A 插头与设备作为一体,此时 B-Device 必定与 A 插头连接, Host 检测到外设连接,开始枚举。 

 

OTG Device/Embedded Host 与 仅作为外设的 B-device ( A 插头为线缆连接)

Host 段检测到 A 插头插入,停止 ADP ,打开 VBus ,如果 B-Device 是线缆连接完毕在将 A 插头插入则整个连接过程与上面无异,因为此 时 B-Device 可能还没有插入插头,则设备连接超时, VBus 再次关闭,等待下一次 ADP 的改变(线缆连接完毕),再次打开 VBus ,此时开始正常总 线枚举。

 

OTG Device 与 OTG Device

Host 端检测到插头插入,则打开 VBus ,如果没有外设检测到,则关闭 VBus ,打开 ADP Probing , Device 端检测到插头插入,则打开 SRP ,如果线缆没有插入,则 SRP 超时, Device 端开始进行 ADP Probing ,当线缆连接完毕, Device 端侦测到 ADP 变化,发送 SRP 请求 Host 打开 VBus , Host 回应 SRP 并且打开 VBus ,完成设备 连接。

从上面的过程可以看出, ADP 和 SRP 的目的都是为了节能,平时 VBus 上的供电是并没有打开的,而且在空闲一段时间后 VBus 也会自动关闭。在 VBus 不供电的情 况下就需要靠 ADP 和 SRP 来检测外设并打开 VBus 的供电。如果 VBus 上一直有电流供应, ADP 和 SRP 就无用武之地了。可见,在嵌入式的世界里,确实要精打细算, ADP 和 SRP 是何等的复杂,这么复杂的设计仅是为了省几毫安的电流。

回来我们前面的问题, OTG 和 EHCI/UHCI/OHCI 是同一类的概念吗?

OTG 的作用只是负责切换主从状态的切换,同时,在功耗方面也有要求, OTG 要求 vbus 不能像 PC 那样永远供电,所以这样就需要有一套设备发现机制,这就是 SRP 和 ADP 了。 OTG 也允许同进充许同类设备进行数据传输( A TO A ) , 这就是 HNP 发挥作用的时候了。

当 OTG 己经发现设备, VBUS 供电开启,系统处于 HOST 或 DEVICE 状态,这时 OTG 的使命就完成了,接下来的工作就交给主控制器( EHCI/OHCI/UHIC )或从控制器了。

所以 OTG 的主要是在设备接入的那一刻起作用,此后 USB 还是要按传统的 HOST/DEVICE 的方式来通讯。当然,有些 OTG 控制器会集成一些 HOST 控制器的功能,这些控制器并不符合任何一种标准,一旦遇到这种 OTG 控制器,驱动开发人员就要倒霉了。 USB 系统中最复杂的就是主控制器了,如果与主控制器与 USB 标准有出入,就无法重用大量成熟的控制代码,驱动开发人员就需要重新去研究整个 USB 协义,这无疑是一份耗时耗力不讨好的活。

 

                   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设备驱动

我们再看看下面的图,我们基本了解了一下EHCI和如何将EHCI驱动起来,上EHCI驱动上面是USB核心,这一块是USB中最复杂的一块了,所幸他是与硬件无关的,作为一个普普通通的驱动工程师,只需要知道他提供了什么样的接口,以及如何使用这些接口,我们甚至不需要知道USB是如何工作的,只要放心大胆的使用这些USB核心层的API,把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 transfer_buffer_length;     // data buffer length缓存区长度

       int actual_length;      // actual data buffer length 实际数据长度

       int bandwidth;                  //此请求每次占用一帧的带宽,只适用实时/中断传输 

       unsigned char *setup_packet;      //用于指向控制传输中控制命令的指针,只适用控制传输

       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);

   

对于pipe的创建及操作,内核中也有定义:

#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核心层提供给我们的,我们只需在逻辑层上把USB看作一个个的pipe就可以了,USB从设备中也会有这样的一些概念,我们其实不是与从设备的硬件直接打交道,而是和从设备中的USB固件(usb从控制器的驱动)打交道。

设备驱动想要使用usb总线和设备通信,一般先要初始化urb结构,把你所想要传送的数据用系统提供的urb操作工具填入urb中,然后用 usb_submit_urb向usb核心提交。

我们想要了解usb设备驱动层的数据是如何到达USB主控制器并发送到总线上去的,usb_submit_urb是一个很好的突破口。

usb_submit_urb中全调用usb_hcd_submit_urb,usb_hcd_submit_urb会找到预先指定的控制器驱动,即调用hcd->driver->urb_enqueue(),对ehci控制器而言, urb_enqueue就是ehci_hcd.c中的ehci_urb_enqueue(),数据走到ehci_urb_enqueue(),接下来的事情我们就能很清楚了,我们前介绍过itd/sitd/qh/qtd/fstn这几种在ehci sepc规范中定义的数据模型,也介绍了这几种数据模型在linux kernel中的表示,一句话,ehci_urb_enqueue()要作的事就是把设备驱动层交给urb的数据填充到itd/sitd/qh/qtd/fstn这几种数据结构中,并将其链成调度表。

我们来看看这个函数的代码:

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的人都知道USB的传输分为中断传输,控制传输,批量传输,等时传输。基本上所有的人都知道这几种传输的概念上的区别,但却没几个人能了解这种区别的具体实现,以及形成区别的原因,等时传输和中断传输为什么会有时效性,批量传输和等时传输在实现在有什么区别呢。

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社区连设备的规范都定了下来,这样就可以生产出许多免驱动的设备了。

我们先看看linux中的usb设备的描述

struct usb_device {           ;  //代表一个USB设备 

      int devnum;   ;           ;  //分配的设备地址,1-127

      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规范把设备分成了许多类,特定类(class)的设备又可划分成子类描述符(subclass),划分子类的后软件就可以搜索总线并选择所有它可以支持的设备,一个设备只有一个(Device)描述符,它指明了设备所属的类,

每个设备可以有一个或多个配置描述符(Configuration),配置描述符用于定义设备的功能。如果某个设备有几种不同的功能,则每个功能都需要一个配置描述符。配置描述符(configuration)是接口描述符(interface)的集合。接口指定设备中的哪些硬件与USB交换数据。每一个与USB交换数据的硬件就叫做一个端点描述符(endpoint)。因此,接口是端点的集合。USB的设备类别定义(USB Device Class Definitions)定义特定类或子类中的设备需要提供的缺省配置、接口和端点.

USB设备驱动中在打通数据通路后,就要理清各种配置了,根据这些配置再与设备作下一步的交流。

USB设备的识别过程

有了上面的准备知识,我们可以看一看当一个USB设备接入后是如何被识别的。

USB系统是一种主从结构,所有的通讯都是host发起的,从设备永远处于被动状态,但在主设备还是需要一个中断来唤醒。

1)如果是OTG设备,接入USB从设备时,USB-ID线被拉低,OTG中断触发,OTG将控制器切换到host状态。

2)当控制器处于host状态开处于待机时(otg的ADP,ARP完成),因为设备的D+和D-线上有特殊的处理,低速设备上的D-上有下拉电阻,高速和全速设备的D+上有上拉电阻,集线器检测到这个变化上报中断给探制器。

3)主机使用0地址通道来向总线上的新设备进查询,取得设备的一些基本的信息。

4)主机发取得设备的基本信息后发出复位指令,并给设备分配一个地址。设备复位并有了新的地址。

5)主机用新的地址继续查询设备的各种描述符(配置),主机通过设备的这些配置为设备在主机上寻找一个驱动。

6)一旦找到设备的驱动程序,枚举过程就完成了,控制权交到设备驱动的手上,设备在设驱动的控制下工作。

上面的交互过程其实很复杂,还可以细分。我们需要注意的是在主机给设备分配地址前设备使用的是0号地址,这是个公共地址,任何设备在刚接入的时该都会使用这个地址,并且一次只能有一个设备在使用0地址,主要发出复位指令时才会给设备分配一个新的地址。设备枚举的过程主要是对设备的各种描述符进行解析,最后找到设备驱动程序。

USB设备驱动和USB gadge驱动应该是我们接触得最多的与usb相关的驱动,这也是初学者很容易混淆的两个概念,再看下面这张图。

 
 

从图中可以看出,USB设备驱动和USB gadge驱动处在平等对话的位置,usb主机侧的usb核心层和USB设备侧的gadge层都是为了给这两者能够对上话提供服务。我们完全可以把USB核 心层和gadge层以下的部份当作一个黑盒子,这样USB设备驱动就和其它的设备驱动没有什么区别了,只是数据通路走的是USB总线。

在linux中,使用usb总线作为数据通路最重要的一点就是控制urb,在驱动层,对usb的控制就是操控urb,控制urb的api前面己经列举过,有两点需要特别注意,一是等时传输的urb要手动初始化。二是中断传输的urb中有一个回调函数,这个回调函数会在有中断传输数据到达时被usb核心调用。内核中有一个drivers/usb/usb-skeleton.c的例子,是一个很好的参照。

usb-gadge就要稍稍复杂一点,除了通路的问题,你可能还要准备好各种配置(设备描述符,配置描述符,端点描述符等)。最后不要忘了,USB设备驱动和gadge驱动有本质的区别,USB设备驱动是运行在主机侧,向用户层提供设备接口,而usb-gadge驱动则是作为一个设备的固件运行在设备侧。这是最基本的,如果这一点不清楚最好先不要碰USB。

usb-gadge驱动的技术和usb设备固件技术有很多相同之处,这也是中国研发人员投入精力最多的地方,市面上的很多USB的资料都会讲这一块,也只会讲这一块,很多资料中提到的USB控制器其实都是从控制器,用来设计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动作, 

表示in类型,后面还有In读取操作需要读取数据,
同时为S-submission或者E-submission error

 

> --

> -- 表示数据部分已经成功下发 

表示out类型,同时为C-Callback

 

仅从USBMON很难得到对调试驱动有帮助的数据,要想在linux下方便的调试USB设备,还有许多工作需要我们做。

 

0

转载地址:https://blog.csdn.net/yyyyyyyyyywwwwwwwwww/article/details/50716154 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:mm编译 问题
下一篇:Iptables入门教程

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年03月31日 18时27分33秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章