linux usb gadget 日志

1,USB 协议入门

几种USB控制器类型:OHCI,UHCI,EHCI,XHCI

遇到过一些关于USB的东西(如下),一直没搞明白什么USB1.0/1.1/2.0/3.0之类的,当然我知道它们的各自传输速度都在提升,本文就做点简单的记录吧,尽量下载看到USB的东西和别人提及USB的术语时不要再迷茫了。
1. 曾经遇到一些Linux系统对USB3.0的支持不完善,从而导致在有USB3.0设备的PC上启动失败(kernelpanic)。
2. 也曾在BIOS中设置为开启EHCI,关闭XHCI”等来坚持使用USB2.0
3.
QEMU代码中看到它支持的各种配置:“CONFIG_USB_OHCI=y”“CONFIG_USB_UHCI=y”“CONFIG_USB_EHCI=y”“CONFIG_USB_XHCI=y”等。
4. 看到Kernelconfig文件中的“CONFIG_USB_OHCI_HCD=m”“CONFIG_USB_UHCI_HCD=m”“CONFIG_USB_EHCI_HCD=m”“CONFIG_USB_XHCI_HCD=m”等。

所以,这里就简单列一下各种xHCI(也包括真正的xHCI)的基本含义吧。
简单地讲,OHCIUHCI都是USB1.1的接口标准,而EHCI是对应USB2.0的接口标准,最新的xHCIUSB3.0的接口标准。
1.OHCIOpen Host Controller Interface)是支持USB1.1的标准,但它不仅仅是针对USB还支持其他的一些接口,比如它还支持Apple的火线(FirewireIEEE1394)接口。与UHCI相比,OHCI的硬件复杂,硬件做的事情更多,所以实现对应的软件驱动的任务,就相对较简单。主要用于非x86USB,如扩展卡、嵌入式开发板的USB主控。
2.UHCIUniversal Host Controller Interface),是Intel主导的对USB1.01.1的接口标准,与OHCI不兼容。UHCI的软件驱动的任务重,需要做得比较复杂,但可以使用较便宜、较简单的硬件的USB控制器。IntelVIA使用UHCI,而其余的硬件提供商使用OHCI
3.EHCIEnhanced Host Controller Interface),是Intel主导的USB2.0的接口标准。EHCI仅提供USB2.0的高速功能,而依靠UHCIOHCI来提供对全速(full-speed)或低速(low-speed)设备的支持。
4.xHCIeXtensible Host Controller Interface),是最新最火的USB3.0的接口标准,它在速度、节能、虚拟化等方面都比前面3中有了较大的提高。xHCI支持所有种类速度的USB设备(USB3.0 SuperSpeed, USB 2.0 Low-, Full-, and High-speed, USB 1.1 Low- andFull-speed)。xHCI的目的是为了替换前面3中(UHCI/OHCI/EHCI)。

好吧,就这样。

参考资料:
http://en.wikipedia.org/wiki/Host_controller_interface_(USB,_Firewire)#USB
如下链接对USB相关的基础知识进行了较好的总结:
http://www.crifan.com/files/doc/docbook/usb_basic/release/webhelp/content/ch02_sw_hw.xml.html

 

1.1 usb的域、包、事务、传输的基本概念

下图是usb2.0协议的物理层连线,其中包括一根地线、一根电源线(在usb2.0中电源线仅支持到5V 500mA,目前随着人们需求的日益膨胀,在后续协议中usb线提供的供电能力正在不断增加)、两根差分数据线DP、DM(使用差分线的好处是增加抗干扰能力,能够更快速的串行传输数据,这个有概念就可以了,在此不做深入探讨)。

 

 

 

包(Packet)是USB系统中信息传输的基本单元,所有数据都是经过打包后在总线上传输的。数据在 USB总线上的传输以包为单位,包只能在帧内传输。高速USB 总线的帧周期为125us,全速以及低速 USB 总线的帧周期为 1ms。帧的起始由一个特定的包(SOF包)表示,帧尾为 EOF。EOF不是一个包,而是一种电平状态,EOF期间不允许有数据传输。 
    注意:虽然高速USB总线和全速/低速USB总线的帧周期不一样,但是SOF包中帧编号的增加速度是一样的,因为在高速USB系统中,SOF包中帧编号实际上取得是计数器的高11位,最低三位作为微帧编号没有使用,因此其帧编号的增加周期也为 1mS。

       USB总线上的情形是怎样的?

包是USB总线上数据传输的最小单位,不能被打断或干扰,否则会引发错误。若干个数据包组成一次事务传输,一次事务传输也不能打断,属于一次事务传输的几个包必须连续,不能跨帧完成。一次传输由一次到多次事务传输构成,可以跨帧完成

 

USB的数据格式

DP、DM数据线上传输的还是程序员熟悉的0和1,然后由若干个二进制数据(不同的域有各自的定义)组在一起叫做‘域’,由若干个‘域’组成‘包’,再由若干个‘包’组成‘事务’,最后若干个‘事务’组成‘传输’。USB的数据格式其实比较简单,只是各种名字比较多,下表梳理了一下各个名词之间的层次关系:

传输(最大单位)(由事务组成)

控制、批量、中断、同步

事务(由包组成)

In、Out、SetUp

包(有域组成)

令牌、数据、握手、特殊

域(最小单位)

SYNC、PID、ADDR、ENDP、FRAM、DATA、CRC

 

下图是一个实际的usb mass-storage读取数据的抓包分析,下面我们结合此图与上表逐个分析一个其中的各个名词:

 

(一)域:

USB包由域组成,一般如下:

域可分为七个类型:

1、同步域(SYNC),八位,值固定为00000001,用于本地时钟与输入同步

 

2、标识域(PID),由四位标识符+四位标识符反码构成,表明包的类型和格式,这是一个很重要的部分,这里可以计算出,USB的标识码有16种。

 

3、地址域(ADDR):七位地址,代表了设备在主机上的地址,地址000 0000被命名为零地址,是任何一个设备第一次连接到主机时,在被主机配置、枚举前的默认地址,由此可以知道为什么一个USB主机只能接127个设备的原因。

 

4、端点域(ENDP),四位,由此可知一个USB设备有的端点数量最大为16个。

 

5、帧号域(FRAM),11位,每一个帧都有一个特定的帧号,帧号域最大容量0x800,对于同步传输有重要意义(同步传输为四种传输类型之一,请看下面)。

 

6、数据域(DATA):长度为0~1023字节,在不同的传输类型中,数据域的长度各不相同,但必须为整数个字节的长度

 

7、校验域(CRC):对令牌包和数据包(对于包的分类请看下面)中非PID域进行校验的一种方法,CRC校验在通讯中应用很泛,是一种很好的校验方法,至于具体的校验方法这里就不多说,请查阅相关资料,只须注意CRC码的除法是模2运算,不同于10进制中的除法。

 

(二)包:

由域构成的包有四种类型,分别是令牌包、数据包、握手包和特殊包,前面三种是重要的包,不同的包的域结构不同,介绍如下

 

1、令牌包(token format):可分为输入包、输出包、设置包和帧起始包(注意这里的输入包是用于设置输入命令的,输出包是用来设置输出命令的,而不是放据数的)

 

其中输入包、输出包和设置包的格式都是一样的:

SYNC+PID+ADDR+ENDP+CRC5(五位的校验码)

     此格式适用于IN、OUT、SETUP、PING。

PID           数据传输方向

IN             Device->Host

OUT        Host->Device

SETUP   Host->Device

PING       Device->Host

 

帧起始(SOF)包:

  SOF包由Host发送给Device。
    1) 对于full-speed总线,每隔1.00 ms ±0.0005 ms发送一次;
    2) 对于high-speed总线,每隔125 μs ±0.0625 μs发送一次;
    SOF包构成如下图所示:

SYNC+PID+11位FRAM+CRC5(五位的校验码)

 

 

 

2、数据包:分为DATA0包和DATA1包,当USB发送数据的时候,当一次发送的数据长度大于相应端点的容量时,就需要把数据包分为好几个包,分批发送,DATA0包和DATA1包交替发送,即如果第一个数据包是DATA0,那第二个数据包就是DATA1。但也有例外情况,在同步传输中(四类传输类型中之一),所有的数据包都是为DATA0,格式如下:

SYNC+PID+0~1023字节+CRC16

3、握手包:结构最为简单的包,格式如下

SYNC+PID

• ACK: 对于IN事务,它将由host发出;对于OUT、SETUP和PING事务,它将由device发出。

• NAK: 在数据阶段,对于IN事务,它将由device发出;在握手阶段,对于OUT和PING事务,它也将由device发出;host从不发送NAK包。

 

(注上面每种包都有不同类型的,USB1.1共定义了十种包)

 

(三)事务:

事务分别有IN事务、OUT事务和SETUP事务三大事务,每一种事务都由令牌包、数据包、握手包三个阶段构成,这里用阶段的意思是因为这些包的发送是有一定的时间先后顺序的,事务的三个阶段如下:

1、令牌包阶段:启动一个输入、输出或设置的事务

2、数据包阶段:按输入、输出发送相应的数据

3、握手包阶段:返回数据接收情况,在同步传输的IN和OUT事务中没有这个阶段,这是比较特殊的。

 

事务的三种类型如下(以下按三个阶段来说明一个事务):

 

1、   IN事务:

输入事务处理:表示USB主机从总线上的某个USB设备接收一个数据包的过程,包括以下几个阶段:

 

1>令牌包阶段——主机发送一个PID为IN的输入包给设备,通知设备要往主机发送数据;

 

2>数据包阶段——设备根据情况会作出三种反应(要注意:数据包阶段也不总是传送数据的,根据传输情况还会提前进入握手包阶段)

 

1) 设备端点正常,设备往入主机里面发出数据包(DATA0与DATA1交替);

 

2) 设备正在忙,无法往主机发出数据包就发送NAK无效包,IN事务提前结束,到了下一个IN事务才继续;

 

3) 相应设备端点被禁止,发送错误包STALL包,事务也就提前结束了,总线进入空闲状态。

 

3>握手包阶段——主机正确接收到数据之后就会向设备发送ACK包。

总结如下图:

       •【正常】的输入事务处理

      •【设备忙】时的输入事务处理    

     •【设备出错】时的输入事务处理

2、 OUT事务:

输出事务处理:表示USB主机把一个数据包输出到总线上的某个USB设备接收的过程, 有以下几个阶段:

1>令牌包阶段——主机发送一个PID为OUT的输出包给设备,通知设备要接收数据;

 

2>数据包阶段——比较简单,就是主机会设备送数据,DATA0与DATA1交替

 

3>握手包阶段——设备根据情况会作出三种反应

 

1)设备端点接收正确,设备往入主机返回ACK,通知主机可以发送新的数据,如果数据包发生了CRC校验错误,将不返回任何握手信息;

 

2) 设备正在忙,无法接收主机发出数据包就发送NAK无效包,通知主机再次发送数据;

 

3) 相应设备端点被禁止,发送错误包STALL包,事务提前结束,总线直接进入空闲状态。

   总结如下图:

   •【正常】的输出事务处理

    •【设备忙时】的输出事务处理

    •【设备出错】的输出事务处理

 

3、SETUT事务:

有以下几个阶段

1>令牌包阶段——主机发送一个PID为SETUP的输出包给设备,通知设备要接收数据;:

 

2>数据包阶段——比较简单,就是主机会设备送数据,注意,这里只有一个固定为8个字节的DATA0包,这8个字节的内容就是标准的USB设备请求命令(共有11条,具体请看问题七)

 

3>握手包阶段——设备接收到主机的命令信息后,返回ACK,此后总线进入空闲状态,并准备下一个传输(在SETUP事务后通常是一个IN或OUT事务构成的传输)

   总结如下图:

   •【正常】的设置事务处理


   •【设备忙时】的设置事务处理


   •【设备出错】的设置事务处理

4、PING事务:

PING事务只有令牌包和握手包。

PING事务是USB2.0高速模式特有的,全速模式和低速模式没有。

PING事务作用:主机探测设备是否有空间接收数据。

PING流程:

1> 主机发送PING令牌包。

2> 设备返回状态。

设备有空间接收数据:设备返回ACK握手包。

设备没有空间接收数据:设备返回NAK握手包。

设备端点挂起:设备返回STALL握手包。

 

(四)传输:

传输由OUT、IN、SETUP事务其中的事务构成,传输有四种类型,中断传输、批量传输、同步传输、控制传输

      在USB的传输中,定义了4种传输类型:

  控制传输(Control Transfer)

  中断传输(Interrupt Transfer)

  批量传输(Bulk Transfer)

  同步传输 (Isochronous)

4.1 控制传输 (Control Transfer)

     控制传输由2~3个阶段组成:

     1) 建立阶段(Setup)

     2) 数据阶段(无数据控制没有此阶段)(DATA)

     3) 状态阶段(Status)

     每个阶段都由一次或多次(数据阶段)事务传输组成(Transaction)。

      控制数据由USB系统软件用于配置设备(在枚举时),其它的驱动软件可以选择使用control transfer实现具体的功能,数据传输是不可丢失的。

4.1.1 建立阶段

    主机从USB设备获取配置信息,并设置设备的配置值。建立阶段的数据交换包含了SETUP令牌封包、紧随其后的DATA0数据封包以及ACK握手封包。它的作用是执行一个设置(概念含糊)的数据交换,并定义此控制传输的内容(即:在Data Stage中IN或OUT的data包个数,及发送方向,在Setup Stage已经被设定)。

 

      上图中从上往下连起来看是一个 传输过程, 此传输只包含一个SETUP事务,此SETUP事务包含三种包阶端,

      在建立阶段的Data包阶段是USB host发起 usb请求,usb请求的介绍如下:

控制传输是最重要和最复杂的一种传输类型,其中使用了一个8字节大小的DATA0数据包,这8个字节的数据包是主机用来发送控制阶段中的请求命令,而这些请求命令是主机配置USB设备的关键。

1) bmRequestType(向谁请求)

    D7:传输方向
            0=
主机至设备;1=设备至主机
    D6..5:命令类型

          D6D5=00:标准请求命令; D6D5=01:类请求命令;

          D6D5=10:用户定义命令; D6D5=11:保留。

    D4..0:接受者类型

         0=设备;1=接口 ;
          2=端点;3=其它
          4..31 保留

       这个域表明此请求的特性。特别地,这个域表明了第二阶段控制传输方向。如果wLength域被设作0的话,表明没有数据传送阶段,那Direction位就会被忽略.
       USB说明定义了一系列所有设备必须支持的标准请求。这些请求见下面的表<Standard Device Requests>。另外,一个设备类可定义更多的请求。设备厂商也可定义设备支持的请求.
       请求可被导引到设备,设备接口,或某一个设备端结点(endpoint)上。这个请求域也指定了接收者。当指定的是接口或端结点(endpoint)时,wIndex域指出那个接口或端节点

 

2) bmRequest(什么请求)

       这个域标识特别的请求。bmRequestType域的Type字段可修改此域的含义。本文仅定义Type 字段为0即标准设备请求时bRequest域值的含义。

 

3) wValue 
   此域用来传送当前请求的参数,随请求不同而变。
4) wIndex
域 
    当bmRequestType的Recipient字段为接口或端点时,wIndex域用来表明是哪一个接口或端结。
5) wLength域

    这个域表明第二阶段的数据传输长度。传输方向由bmRequstType域的Direction位指出。wLength域为0则表明无数据传输。在输入请求下,设备返回的数据长度不应多于wLength,但可以少于。在输出请求下,wLength指出主机发出的确切数据量。如果主机发送多于wLength的数据,设备做出的响应是无定义的。

标准设备请求

标准设备请求

标准请求码(bRequest的值)

 

 

描述符类型

GET_DESCRIPTOR(读取描述符)

     这个请求返回存在的描述符.

        wValue域:

        高一字节:标识描述表类型(Descriptor Types)

        低一字节:表示描述符号的索引,将几个类型相同的描述符应用到设备中时,描述符索引用于选择一个特定的描述符(只能是配置和字符串描述符)。

        例如:设备可以用几个配置描述符。对于其他可以通过GetDescriptor()请求取得的标准描述符来说,描述符的索引须为0。描述符索引值的范围从0开始到设备使用该类型描述符的数量减1。
       wIndex域:

       标识字串描述表的语言(Language ID),如果是其它语言的话就设为0。

       wLength域:

      表示要返回多少字节。如果描述表长度大于wLength域值,那么只有描述表的初始部分被返回。如果描述表比wLength域值小,则发送一个短包来标志传输的结束。一个短包被定义成一个长度短于最大负载长度或一个空(NULL)包。

       这个标准请求包括3种描述符:设备(也就是设备的限定符)、配置(也就是其他速度配置)、及字符串。能进行高速操作的设备支持设备限定描述符返回有关设备不支持的速度信息(包括默认端点的wMaxPacketSize和其他速度配置的数量)。

       其他速度配置用与配置描述符相同的结构返回信息,但如果设备在其他速度下操作则返回配置信息。请求配置描述符将返回配置描述符,所有接口描述符和在这个请求中所有接口的端点描述符。

       第一个接口描述符的设备请求会一次返回配置描述表,所有的接口描述表和所有接口的端节点的描述表。第一个接口描述符紧跟着配置描述符号,第一个接口的端节点的描述符号随后。如果有其它的接口与端节点,它们的描述符欲跟在第一个接口与端节点描述符之后。与类有关的描述符,和/或厂商定义的描述符跟在标准描述符之后.
        所有的设备必须提供一个设备描述符并且至少一个配置描述符,如果一个设备不支持一个请求的描述符,则返回请求错误。
    •缺省状态:此请求合法。
    •地址状态:此请求合法。
    •配置状态:此请求合法。

 

GET_INTERFACE(取得接口)

      这个请求向指定接口返回选中的备用设备。
      
一些USB设备有接口设置互斥的配置。这个请求允许主机确定当前选定的备用设置。如果wValue或者wLength的值与上面指定的不一致,那么设备的行为没有定义;如果指定的接口不存在,那么设备将用请求错误响应。
     •默认状态:当设备处于默认状态时接收到这个请求,设备的行为没 有定义
     •地址状态:设备给出请求错误
     •配置状态:当设备处于配置状态时,这是一个有效的请求
 

4.1.2数据阶段

     根据数据阶段的数据传输的方向,控制传输又可分为3种类型:

     1) 控制读取(读取USB描述符)

     2) 控制写入(配置USB设备)

     3) 无数据控制

 

     数据传输阶段:用来传输主机与设备之间的数据。

     • 控制读取

     是将数据从设备读到主机上,读取的数据USB设备描述符。该过程如下图的【ControlRead】所示。对每一个数据信息包而言,首先,主机会发送一个IN令牌信息包,表示要读数据进来。然后,设备将数据通过DATA1/DATA0数据信息包回传给主机。最后,主机将以下列的方式加以响应:当数据已经正确接收时,主机送出ACK令牌信息包;当主机正在忙碌时,发出NAK握手信息包;当发生了错误时,主机发出STALL握手信息包。

     • 控制写入

      是将数据从主机传到设备上,所传的数据即为对USB设备的配置信息,该过程如下的图【ControlWirte】所示。对每一个数据信息包而言,主机将会送出一个OUT令牌信息包,表示数据要送出去。紧接着,主机将数据通过DATA1/DATA0数据信息包传递至设备。最后,设备将以下列方式加以响应:当数据已经正确接收时,设备送出ACK令牌信息包;当设备正在忙碌时,设备发出NAK握手信息包;当发生了错误时,设备发出STALL握手信息包。

      图中的一个方框代表一个事物。

4.1.3 状态阶段

       状态阶段:用来表示整个传输的过程已完全结束。
      状态阶段传输的方向必须与数据阶段的方向相反,即原来是IN令牌封包,这个阶段应为OUT令牌封包;反之,原来是OUT令牌封包,这个阶段应为IN令牌封包。

      对于【控制读取】而言,主机会送出OUT令牌封包,其后再跟着0长度的DATA1封包。而此时,设备也会做出相对应的动作,送ACK握手封包、NAK握手封包或STALL握手封包。

     相对地对于【控制写入】传输,主机会送出IN令牌封包,然后设备送出表示完成状态阶段的0长度的DATA1封包,主机再做出相对应的动作:送ACK握手封包、NAK握手封包或STALL握手封包。
 

 4.2 批量传输 (Bulk Transfer)

      •用于传输大量数据,要求传输不能出错,但对时间没有要求,适用于打印机、存储设备等。

      •批量传输是可靠的传输,需要握手包来表明传输的结果。若数据量比较大,将采用多次批量事务传输来完成全部数据的传输,传输过程中数据包的PID 按照 DATA0-DATA1-DATA0-…的方式翻转,以保证发送端和接收端的同步。
      • USB 允许连续 3次以下的传输错误,会重试该传输,若成功则将错误次数计数器清零,否则累加该计数器。超过三次后,HOST 认为该端点功能错误(STALL),放弃该端点的传输任务。
      • 一次批量传输(Transfer)由 1 次到多次批量事务传输(Transaction)组成。
      • 翻转同步:发送端按照 DATA0-DATA1-DATA0-…的顺序发送数据包,只有成功的事务传输才会导致PID 翻转,也就是说发送端只有在接收到 ACK 后才会翻转PID,发送下一个数据包,否则会重试本次事务传输。同样,若在接收端发现接收到到的数据包不是按照此顺序翻转的,比如连续收到两个 DATA0,那么接收端认为第二个 DATA0 是前一个 DATA0 的重传。

      它通过在硬件级执行“错误检测”和“重传”来确保host与device之间“准确无误”地传输数据,即可靠传输。它由三种包组成(即IN事务或OUT事务):

       1) token

       2) data

       3) handshake

1) For IN Token (即:INTransaction)

     • ACK: 表示host正确无误地接收到数据

     • NAK: 指示设备暂时不能返回或接收数据  (如:设备忙)

     • STALL:指示设备永远停止,需要host软件的干预 (如:设备出错) 

2) For OUT Token (即:OUTTransaction)

    如果接收到的数据包有误,如:CRC错误,Device不发送任何handshake包

     • ACK: Device已经正确无误地接收到数据包,且通知Host可以按顺序发送下一个数据包

        • NAK: Device 已经正确无误地接收到数据包,且通知Host重传数据,由于Device临时状况(如buffer满)

        • STALL: 指示Device endpoint已经停止,且通知Host不再重传

3) Bulk读写序列

     即由一系统IN事务或OUT事务组成。

4.3 中断传输(Interrupt Transfer)

   中断传输由IN或OUT事务组成。 

   中断传输在流程上除不支持PING 之外,其他的跟批量传输是一样的。他们之间的区别也仅在于事务传输发生的端点不一样、支持的最大包长度不一样、优先级不一样等这样一些对用户来说透明的东西。
     主机在排定中断传输任务时,会根据对应中断端点描述符中指定的查询间隔发起中断传输。中断传输有较高的优先级,仅次于同步传输。
     同样中断传输也采用PID翻转的机制来保证收发端数据同步。下图为中断传输的流程图。

    中断传输方式总是用于对设备的查询,以确定是否有数据需要传输。因此中断传输的方向总是从USB设备到主机。

    

    DATA0或DATA1中的包含的是中断信息,而不是中断数据。

4.4 同步传输(Isochronous Transfer)

1) 它由两种包组成:

       1) token

       2) data

      同步传输不支持“handshake”和“重传能力”,所以它是不可靠传输。

      同步传输是不可靠的传输,所以它没有握手包,也不支持PID翻转。主机在排定事务传输时,同步传输有最高的优先级。

      同步传输适用于必须以固定速率抵达或在指定时刻抵达,可以容忍偶尔错误的数据上。实时传输一般用于麦
克风、喇叭、UVC Camera等设备。实时传输只需令牌与数据两个信息包阶段,没有握手包,故数据传错时不会重传。

 

       先看下上面的图,相信这个图很好理解吧?恩,看着挺熟悉的吧?下面还是介绍下吧。公司是有USB分析仪,不过看过,没用过,这东西特贵。至于用,也是很简单的,插好线后,设置一些功能,点一下运行就OK,然后就有上面的图了。上图是用国嵌资料中的,公司里的东西,不好拿来用。

USB数据是由二进制数字串构成的,首先数字串构成域(有七种),域再构成包,包再构成事务(IN、OUT、SETUP),事务最后构成传输(中断传输、并行传输、批量传输和控制传输)。

 

1、传输(中断、批量、同步、控制)

这里的Transfer是传输方式,这个是控制传输GET,获取描述符。

这个也是Transfer,她是控制传输SET,设置地址。

 

2、事务(IN、 OUT、 SETUP)

这个是setup事务。

       这个是IN事务。

       这个就是OUT事务了。

 

3、包(令牌包(SETUP)、数据包(DATA)、握手包(ACK)和特殊包)

这个是setup事务中的包,看Dir可以知道,有hostàdevice的,也有deviceàhost的。

 

4、域(同步域(Sync)、标识域(PID)、地址域(ADDR)、端点域(ENDP)、

帧号域(FRAM)、数据域(DATA)、校验域(CRC))

       还是上面这个图,看着有Sync,ADDR,CRC5,DATA等,这些域。

 

       所以,域组成了包,包又组成了事务,事务组成了一次传输。相信,这样的解释,对于USB协议来说,可以理解得很多很多了吧。

1.2 usb枚举过程

枚举资料1:

1. 枚举是什么?

      枚举就是从设备读取一些信息,知道设备是什么样的设备,如何进行通信,这样主机就可以根据这些信息来加载合适的驱动程序。调试USB设备,很重要的一点就是USB的枚举过程,只要枚举成功了,那么就已经成功大半了。

       USB架构中, hub负责检测设备的连接和断开,利用其中断IN端点(InterruptIN Endpoint)来向主机(Host)报告。在系统启动时,主机轮询它的根hub(Root Hub)的状态看是否有设备(包括子hub和子hub上的设备)连接。USB总线拓扑结构见下图(最顶端为主机的Root Hub):

                                              USB总线拓扑结构 

       一旦获悉有新设备连接上来,主机就会发送一系列的请求(Resqusts)给设备所挂载到的hub,再由hub建立起一条连接主机(Host)和设备(Device)之间的通信通道。然后主机以控制传输(Control Transfer)的方式,通过端点0(Endpoint 0)对设备发送各种请求,设备收到主机发来的请求后回复相应的信息,进行枚举(Enumerate)操作。所有的USB设备必须支持标准请求(StandardRequests),控制传输方式(Control Transfer)和端点0(Endpoint 0)。

      在讲解枚举之前,先大概说说USB的一种传输模式——控制传输。这种传输在USB中是非常重要的,它要保证数据的正确性,在设备的枚举过程中都是使用控制传输的。控制传输分为三个阶段:①建立阶段。②数据阶段。③确认阶段。

       建立(setup)阶段:都是由USB主机发起,它是一个setup数据包,里面包含一些数据请求的命令以及一些数据。如果建立阶段是输入请求,那么数据阶段就要输入数据;如果建立阶段是输出请求,那么数据阶段就要输出数据。如果在数据阶段,即便不需要传送数据,也要发一个0长度的数据包。数据阶段过后就是确认阶段。确认阶段刚好跟数据阶段相反,如果是输入请求,则它是一个输出数据包;如果是输出请求,则它是一个输入数据包。确认阶段用来确认数据的正确传输。

1.1 通信传输流

1.2 设备状态图

 

 1.3状态详解

       1) 连接(Attached)
       设备可以连接到USB或者从USB上拔出.USB设备从总线上拨出后的状态在规范没定义,只说明一旦USB连到总线要求的操作以及属性.


       2) 上电(Powered)
       USB设备的电源可来自外部电源,也可从USB接口的集线器而来。电源来自外部电源的USB设备被称作自给电源式的(self-powered)。尽管自给电源式的USB设备可能在连接上USB接口以前可能已经带电,但它们直到连线上USB接口后才能被看作是加电状态(Powered state)。而这时候VBUS已经对设备产生作用了.
一个设备可能有既支持自给电源的,同时也支持总线电源式的配置。有一些支持其中的一种,而另一些设备配置可能只有在自给电源下才能被使用。设备对电源支持的能力是通过配置描述表(configuration descriptor)来反映的。当前的电源供给形式被作为设备状态的一部分被反映出来。设备可在任何时候改变它们的供电来源,比如说:从自给式向总线式改变,如果一个配置同时支持两种模式,那此状态的最大电源需求就是指设备在两种模式下从VBUS上获取电能的最大值。设备必须以此最大电源作为参照,而究竟处于何状态是不考虑的。如果有一配置仅支持一种电源模式,那么电源模式的改变会使得设备失去当前配置与地址,返回加电状态。如果一个设备是自给电源式,并且当前配置需要大于100mA电流,那么如果此设备转到了总线电源式,它必须返回地址状态(Addressstate)。自给电源式集线器使用VBUS来为集线控制器(Hubcontroller)提供电源,因而可以仍然保持配置状态(Configured state),尽管自给电源停止提供电源。


      3)默认状态(Default)

     设备上电后,它不响应任何总线处理,直到总线接收到复位信号为止.接收到复位信号后,用默认的地址可以对设备寻址.
       当用复位过程完成后,USB设备在正确的速度下操作(即低速/全速/高速).低速和全速的数据选择由设备的终端电阻决定.能进行高速操作的设备决定它是否在复位的过程的一部分执行高速操作.
        能进行高速操作的设备在全速的电气环境中操作时,必须能以全速成功复位.设备成功复位后,设备必须成功响应设备和配置描述符请求,并且返回适当的信息.当在全速下工作时,设备可能或者不能支持预定义的功能.


       4) 地址(Address) 
       所有的USB设备在加电复位以后都使用缺省地址。每一设备在连接或复位后由主机分配一个唯一的地址。当USB设备处于挂起状态时,它保持这个地址不变。
        USB设备只对缺省通道(Pipe)请求发生响应,而不管设备是否已经被分配地址或在使用缺省地址。


        5) 配置状态( Configured )
        在USB设备正常工作以前,设备必须被正确配置。从设备的角度来看,配置包括一个将非零值写入设备配置寄存器的操作。配置一个设备或改变一个可变的设备设置会使得与这个相关接口的终端结点的所有的状态与配置值被设成缺省值。这包括将正在使用(date toggle)的结点(end point)的 (Date toggle)被设置成DATA0。


        6) 挂起状态
        为节省电源,USB设备在探测不到总线传输时自动进入中止状态。当中止时,USB设备保持本身的内部状态,包括它的地址及配置。
        所有的设备在一段特定的时间内探测不到总线活动时必须进入中止态。不管设备是被分配了非缺省的地址或者是被配置了,已经连接的设备必须在任何加电的时刻随时准备中止。总线活动的中止可能是因为主机本身进入了中止状态。另外,USB设备必须在所连接的集线器端口失效时进入中止态。这就是所指的选择性中止(Selectivesuspend)。
       USB设备在总线活动来到时结束中止态。USB设备也可以远程唤醒的电流信号来请求主机退出中止态或选择性中止态。具体设备具有的远程唤醒的能力是可选的,也就是说,如果一个设备有远程唤醒的能力,此设备必须能让主机控制此能力的有效与否。当设备复位时,远程唤醒能力必须被禁止。

 

2. 枚举步骤

      USB协议定义了设备的6种状态,仅在枚举过程中,设备就经历了4个状态的迁移:上电状态(Powered),默认状态(Default),地址状态(Address)和配置状态(Configured)(其他两种是连接状态和挂起状态(Suspend))。

2.1 用户把USB设备插入USB端口或给系统启动时设备上电

     这里指的USB端口指的是主机下的根hub或主机下行端口上的hub端口。Hub给端口供电,连接着的设备处于上电状态。此时,USB设备处于加电状态,它所连接的端口是无效的。
 

2.2 Hub监测它各个端口数据线上(D+/D-)的电压

     在hub端,数据线D+和D-都有一个阻值在14.25k到24.8k的下拉电阻Rpd,而在设备端,D+(全速,高速)和D-(低速)上有一个1.5k的上拉电阻Rpu。当设备插入到hub端口时,有上拉电阻的一根数据线被拉高到幅值的90%的电压(大致是3V)。hub检测到它的一根数据线是高电平,就认为是有设备插入,并能根据是D+还是D-被拉高来判断到底是什么设备(全速/低速)插入端口(全速、高速设备的区分在我将来的文章中描述)。如下图。

                                USB全速/高速设备上电连接

       检测到设备后,hub继续给设备供电,但并不急于与设备进行USB传输。

USB接口定义如下图所示:

2.3 Host了解连接的设备

     每个hub利用它自己的中断端点向主机报告它的各个端口的状态(对于这个过程,设备是看不到的,也不必关心),报告的内容只是hub端口的设备连接/断开的事件。如果有连接/断开事件发生,那么host会发送一个 Get_Port_Status请求(request)给hub以了解此次状态改变的确切含义。Get_Port_Status等请求属于所有hub都要求支持的hub类标准请求(standardhub-class requests)。

2.4 Hub检测所插入的设备是高速还是低速设备

   hub通过检测USB总线空闲(Idle)时差分线的高低电压来判断所连接设备的速度类型,当host发来Get_Port_Status请求时,hub就可以将此设备的速度类型信息回复给host。USB 2.0规范要求速度检测要先于复位(Reset)操作。

2.5 hub复位设备

     主机一旦得知新设备已连上以后,它至少等待100ms以使得插入操作的完成以及设备电源稳定工作。然后主机控制器就向hub发出一个 Set_Port_Feature请求让hub复位其管理的端口(刚才设备插上的端口)。hub通过驱动数据线到复位状态(D+和D-全为低电平 ),并持续至少10ms当然,hub不会把这样的复位信号发送给其他已有设备连接的端口,所以其他连在该hub上的设备自然看不到复位信号,不受影响。

2.6 Host检测所连接的全速设备是否是支持高速模式

      因为根据USB 2.0协议,高速(High Speed)设备在初始时是默认全速(Full Speed )状态运行,所以对于一个支持USB 2.0的高速hub,当它发现它的端口连接的是一个全速设备时,会进行高速检测,看看目前这个设备是否还支持高速传输,如果是,那就切到高速信号模式,否则就一直在全速状态下工作。
       同样的,从设备的角度来看,如果是一个高速设备,在刚连接bub或上电时只能用全速信号模式运行(根据USB 2.0协议,高速设备必须向下兼容USB 1.1的全速模式)。随后hub会进行高速检测,之后这个设备才会切换到高速模式下工作。假如所连接的hub不支持USB 2.0,即不是高速hub,不能进行高速检测,设备将一直以全速工作。

2.7 Hub建立设备和主机之间的信息通道

       主机不停地向hub发送Get_Port_Status请求,以查询设备是否复位成功。Hub返回的报告信息中有专门的一位用来标志设备的复位状态。
       当hub撤销了复位信号,设备就处于默认/空闲状态(Default state),准备接收主机发来的请求。设备和主机之间的通信通过控制传输,默认地址0,端点号0进行。此时,设备能从总线上得到的最大电流是100mA。(所有的USB设备在总线复位后其地址都为0,这样主机就可以跟那些刚刚插入的设备通过地址0通信。)

2.8 主机发送Get_Descriptor请求获取默认管道的最大包长度

      默认管道(Default Pipe)在设备一端来看就是端点0。主机此时发送的请求是默认地址0,端点0,虽然所有未分配地址的设备都是通过地址0来获取主机发来的请求,但由于枚举过程不是多个设备并行处理,而是一次枚举一个设备的方式进行,所以不会发生多个设备同时响应主机发来的请求。
      设备描述符的第8字节代表设备端点0的最大包大小。虽然说设备所返回的设备描述符(Device Descriptor)长度只有18字节,但系统也不在乎,此时,描述符的长度信息对它来说是最重要的,其他的瞄一眼就过了。当完成第一次的控制传输后,也就是完成控制传输的状态阶段,系统会要求hub对设备进行再一次的复位操作(USB规范里面可没这要求)。再次复位的目的是使设备进入一个确定的状态。

2.9 主机给设备分配一个地址

      主机控制器通过Set_Address请求向设备分配一个唯一的地址。在完成这次传输之后,设备进入地址状态(Address state),之后就启用新地址继续与主机通信。这个地址对于设备来说是终生制的,设备在,地址在;设备消失(被拔出,复位,系统重启),地址被收回。同一个设备当再次被枚举后得到的地址不一定是上次那个了。

2.10 主机获取设备的信息

      主机发送 Get_Descriptor请求到新地址读取设备描述符,这次主机发送Get_Descriptor请求可算是诚心,它会认真解析设备描述符的内容。设备描述符内信息包括端点0的最大包长度,设备所支持的配置(Configuration)个数,设备类型,VID(Vendor ID,由USB-IF分配), PID(Product ID,由厂商自己定制)等信息。Get_Descriptor请求(Device type)和设备描述符(已抹去VID,PID等信息)见下图:

  标准Get_Descriptor请求

                        设备描述符(Device Descriptor)
      之后主机发送Get_Descriptor请求,读取配置描述符(ConfigurationDescriptor),字符串等,逐一了解设备更详细的信息。事实上,对于配置描述符的标准请求中,有时wLength一项会大于实际配置描述符的长度(9字节),比如255。这样的效果便是:主机发送了一个Get_Descriptor_Configuration 的请求,设备会把接口描述符,端点描述符等后续描述符一并回给主机,主机则根据描述符头部的标志判断送上来的具体是何种描述符。
      接下来,主机就会获取配置描述符。配置描述符总共为9字节。主机在获取到配置描述符后,根据里面的配置集合总长度,再获取配置集合。配置集合包括配置描述符,接口描述符,端点描符等等。
     如果有字符串描述符的话,还要获取字符串描述符。另外HID设备还有HID描述符等。

2.11  主机给设备挂载驱动(复合设备除外)

     主机通过解析描述符后对设备有了足够的了解,会选择一个最合适的驱动给设备。  然后tell the world(announce_device)说明设备已经找到了,最后调用设备模型提供的接口device_add将设备添加到 usb 总线的设备列表里,然后 usb总线会遍历驱动列表里的每个驱动,调用自己的 match(usb_device_match) 函数看它们和你的设备或接口是否匹配,匹配的话调用device_bind_driver函数,现在就将控制权交到设备驱动了。   

    对于复合设备,通常应该是不同的接口(Interface)配置给不同的驱动,因此,需要等到当设备被配置并把接口使能后才可以把驱动挂载上去。

     设备-配置-接口-端点关系见下图:

                 USB 设备-配置-接口-端点关系
    实际情况没有上述关系复杂。一般来说,一个设备就一个配置,一个接口,如果设备是多功能符合设备,则有多个接口。端点一般都有好几个,比如Mass Storage设备一般就有两个端点(控制端点0除外)。
 

2.12 设备驱动选择一个配置

    驱动(注意,这里是驱动,之后的事情都是有驱动来接管负责与设备的通信)根据前面设备回复的信息,发送Set_Configuration请求来正式确定选择设备的哪个配置(Configuration)作为工作配置(对于大多数设备来说,一般只有一个配置被定义)。至此,设备处于配置状态(Configured),当然,设备也应该使能它的各个接口(Interface)。
    对于复合设备,主机会在这个时候根据设备接口信息,给它们挂载驱动。    

 

枚举资料2:

枚举就是从设备读取一些信息,知道设备是什么样的设备,如何进行通信,这样主机就可以根据这些信息来加载合适的驱动程序。这部分内容同上一篇文章一样,是一些接近物理层的过程,而且其中大部分细节都由硬件模块完成,对于linux驱动工程师来说只需要了解,并不用深陷其中无法自拔。(这部分PHY层细节还是留给数字逻辑工程师去研究吧!驱动工程师只需要有这方面概念,出现问题能够思路全面就可以了)

下图是一个usb全速设备的枚举过程抓包,下面具体分析一下:

 

第一步:区分设备类型

usb设备分为低速设备(1.5Mbps)、全速设备(12Mbps)、高速设备(480Mbps),它们的区分就是在设备上电第一瞬间区分的:

 

1,  低速设备与全速设备在设备端的区别

 

设备端一般有900~1575Ω的上拉电阻,全速设备该电阻在D+上,低速设备在D-上。上图Speed一列中的FS就代表全速设备了,即FullSpeed。

 

2,  设备是否支持高速模式

 

因为usb只有两根数据线,没有办法识别出低速、全速之外的高速模式。检测设备是否支持高速模式是在reset信号后由host发送一串KJKJKJ的序列,如设备支持高速状态则做出相应的调整,切换到高速模式。最初高速设备同全速设备一样,D+信号上存在1.5KΩ的上拉电阻,在切换到高速模式后D+的上拉电阻才会断开(USB设备中的控制器能够控制信号线上拉电阻的开关)连接D+/D-上的高速终端电阻(high-speed termination)。上图中Reset信号后的High speed Detection Handshake这就是检测设备是否支持高速模式的检测。如果支持高速设备在speed一列中后面的符号会变成HS,即HighSpeed。(其中KJ信号是指在D+、D-线上出现的约0.8V的中间电平信号,如想更详细了解这部分的细节可以参考一篇网上的描述http://www.cnblogs.com/AlwaysOnLines/p/3842886.html。)

 

第二步:获取设备描述符的前8个字节

 usb设备在上电后默认的地址为0,而且usb设备是逐一进行枚举的,即同一时刻usb总线上也只有一个设备地址为0。host端对0地址设备发出请求来获取设备描述符的前8个字节。(枚举过程使用的是控制传输,对应的断点是0)这是要干什么呢?下面是此次获取的8个字节的抓包图,其中包括了usb协议版本,图中为1.1;设备类型,图中为Hub;设备协议,图中为None;这些都不重要!之所以只获取8个字节当然是为了得到第八个字节的数据。图中绿框已经圈出重点,这代表此设备端点0支持的的最大包大小。只有知道了这个数据才能在之后的数据传输中合理的控制包大小,大量传输数据,不然使用了设备不支持包大小就会被设备丢弃,发生传输错误了。

 

 

第三步:设置地址

主机通过发送一个Set_Address请求来分配一个唯一的地址给设备。设备读取这个请求,返回一个确认,并保存新的地址。从此开始所有通信都使用这个新地址。从上图可以看到host给当前device分配的地址为1,之后的通信地址都有0改为1了。

 

第四步:获取所有描述符

确认了地址之后通信就进入正轨了,终于可以肆无忌惮的发送数据了。主机向新地址重新发送Get_Device_Descriptor命令,此次读取其设备描述符的全部字段,以了解该设备的总体信息,如VID,PID。之后host还会发出Get_Configuration_Description获取设备的配置描述符;发送Get_Device_String命令,获得字符集描述(unicode),比如产商、产品描述、型号等等。

 

自此usb枚举过程完成了,之后就可以按照不同设备进行相应数据的传输。

2,linux usbgadget 框架

 

知识点(需要梳理):

Usb gadget驱动框架:

usb控制器驱动(如usb_dwc_otg驱动 在linux_kernel_topdir/drivers/usb/目录下)

usb gadget driver驱动 (composite.c,构造出了usb composite dev 在linux_kernel_topdir/drivers/usb/gadget/目录)

PS:2.6.32内核调用usb_gadget_register_driver(&composite_driver)进行注册,2.6.37内核以后统一改为usb_gadget_probe_driver(&composite_driver);】

 

usb composite driver 驱动(如 uvc gadget  driver : webcam.c f_uvc.c (因为uvc gadget driver是视频类驱动所以又涉及到V4L2框架源码为uvc_v4l2.c)uvc_video.c,  都在linux_kernel_topdir/drivers/usb/gadget/目录)

【PS:2.6.32内核在init函数后面调用usb_composite_register(&android_usb_driver)进行注册,2.6.37内核以后统一改为usb_composite_probe。】

 

及以上三者结合起来涉及到usb 枚举过程(usb控制器驱动实现相关枚举逻辑,usb composite driver 驱动 提供一些枚举所需的一些数据,及与USB从设备特性相关的枚举的某些过程)

 

Linux dma 框架

 

list_for_each_entry,  list_add_tail 等使用 structlist_head 实现链表及queue的用法。

 

也可了解一下usb_dwc_otg驱动 中实现queue的方法与使用structlist_head 类似:

DWC_CIRCLEQ_INIT_ENTRY, DWC_CIRCLEQ_INSERT_TAIL的实现

 

Linux v4L2框架?

 

Camera 的基本知识?

 

Linux INITRAMFS 和 INITRD ?

 

3, dwc_otg_driver

struct dwc_otg_device

 

 

4, linux DMA

dma_set_coherent_mask函数,  

dma_alloc_coherent函数,

  dma_map_single函数

资料1:

dma_alloc_coherent (建立一致性 DMA 映射函数)

1、函数申明

/**
 * dma_alloc_coherent - allocate consistent memory for DMA
 * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
 * @size: required memory size
 * @handle: bus-specific DMA address
 *
 * Allocate some uncached, unbuffered memory for a device for
 * performing DMA.  This function allocates pages, and will
 * return the CPU-viewed address, and sets @handle to be the
 * device-viewed address.
 */
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int flag);

该函数实际获得两个地址, 
1、函数的返回值是一个 void *,代表缓冲区的内核虚拟地址 
2、相关的总线地址(物理地址),保存在dma_handle中

2、调用

A =dma_alloc_coherent(B,C,D,GFP_KERNEL);
含义:
A: 内存的虚拟起始地址,在内核要用此地址来操作所分配的内存
B: struct device指针,可以平台初始化里指定,主要是dma_mask之类,可参考framebuffer
C: 实际分配大小,传入dma_map_size即可
D: 返回的内存物理地址,dma就可以用。
所以,AD是一一对应的,只不过,A是虚拟地址,而D是物理地址。对
任意一个操作都将改变缓冲区内容。当然要注意操作环境。

注size最好以页为单位分配。

 dma_map_single(建立流式 DMA 映射)

资料二:

***************************************************************************************************************************
作者:EasyWave                                                                             时间:2012.02.11

类别:linux驱动开发                                                                       声明:转载,请保留链接

***************************************************************************************************************************

一:dma_mask与coherent_dma_mask的定义

在linux内核中,引入了platform_device与platform_driver,这样就很方便了平台的设备与驱动。在include\linux\platform_device.h下:

struct platform_device {
 const char * name;
 int  id;
 structdevice dev;
 u32  num_resources;
 struct resource * resource;

 const struct platform_device_id *id_entry;

 /* arch specific additions */
 struct pdev_archdata archdata;
};

而struct device dev,在include\linux\device.h中:

struct device {
 struct device  *parent;

 struct device_private *p;

 struct kobject kobj;
 const char  *init_name; /* initial name of the device */
 struct device_type *type;

 struct mutex  mutex; /* mutex tosynchronize calls to
      * its driver.
      */

 struct bus_type *bus;  /* type of busdevice is on */
 struct device_driver *driver; /* which driver has allocated this
        device */
 void  *platform_data; /* Platform specific data, device
        core doesn't touch it */
 struct dev_pm_info power;

#ifdef CONFIG_NUMA
 int  numa_node; /* NUMA node this device is close to */
#endif
 u64  *dma_mask; /*dma mask (if dma'able device) */
 u64  coherent_dma_mask;/*Like dma_mask, but for
         alloc_coherent mappings as
         not all hardware supports
         64 bit addresses for consistent
         allocations such descriptors. */

 struct device_dma_parameters *dma_parms;

 struct list_head dma_pools; /* dma pools (ifdma'ble) */

 struct dma_coherent_mem *dma_mem; /* internal forcoherent mem
          override */
 /* arch specific additions */
 struct dev_archdata archdata;
#ifdef CONFIG_OF
 struct device_node *of_node;
#endif

 dev_t   devt; /* dev_t, creates thesysfs "dev" */

 spinlock_t  devres_lock;
 struct list_head devres_head;

 struct klist_node knode_class;
 struct class  *class;
 const struct attribute_group **groups; /* optional groups */

 void (*release)(struct device *dev);
};
dma_mask与coherent_dma_mask这两个参数表示它能寻址的物理地址的范围,内核通过这两个参数分配合适的物理内存给 device。其中dma_coherent_mask则作用于申请一致性DMA缓冲区。因为不是所有的硬件都能够支持64bit的地址宽度。如果 addr_phy 是一个物理地址,且 (u64)addr_phy <=*dev->dma_mask,那么 该 device 就可以寻址该物理地址。如果 device 只能寻址32位地址,那么mask 应为 0xffffffff。依此类推。

例如,在linux2.6.38中:

/* USB EHCI Host Controller */

static u64nuc900_device_usb_ehci_dmamask = 0xffffffffUL;

static struct platform_device nuc900_device_ehci = {
        .name    = "nuc900-ehci",
        .id    = -1,
        .num_resources   =ARRAY_SIZE(nuc900_ehci_resource),
        .resource   =nuc900_ehci_resource,
       .dev             = {
               .dma_mask = &nuc900_device_usb_ehci_dmamask,
                .coherent_dma_mask = 0xffffffffUL
        }
};

二:sample code


这段代码摘录自 arch\arm\mm\dma-mapping.c

u64 limit;
    /*
     * Sanity check the allocation size.
     */
    size = PAGE_ALIGN(size);
    //这个 limit 就是通过 mask 计算得到的设备最大寻址范围。
    limit = (mask + 1) & ~mask;
    //当 size 超出limit 时,说明分配的地址超出了设备的最大寻址能力,这时返回错误。
    if ((limit && size >= limit) ||
        size >= (CONSISTENT_END -CONSISTENT_BASE)) {
        printk(KERN_WARNING "coherentallocation too big "
              "(requested %#x mask %#llx)\n", size, mask);
        goto no_page;
    }

资料三:

说起DMA我们并不陌生,但是实际编程中去用的人不多吧,最多就是网卡驱动里的环形buffer,再有就是设备的dma,下面我们就分析分析.

   DMA用来在设备内存和内存之间直接数据交互。而无需cpu干预

  
内核为了方便驱动的开发,已经提供了几个dma 函数接口。
dma跟硬件架构相关,所以linux关于硬件部分已经给屏蔽了,有兴趣的可以深入跟踪学习.

 

 

按照linux内核对dma层的架构设计,各平台dma缓冲区映射之间的差异由内核定义的一个dma操作集

 

 

include/linux/dma-mapping.h:点击(此处)折叠或打开

1. struct dma_map_ops {
 

2.     void* (*alloc)(struct device *dev, size_t size,

3.                 dma_addr_t *dma_handle, gfp_t gfp,

4.                 structdma_attrs *attrs);

5.     void (*free)(struct device *dev, size_t size,

6.             void *vaddr, dma_addr_t dma_handle,

7.             struct dma_attrs *attrs);

8.     int (*mmap)(struct device *, structvm_area_struct *,

9.             void *, dma_addr_t, size_t, struct dma_attrs *attrs);

10. 

11.    int (*get_sgtable)(struct device *dev, structsg_table *sgt, void *,

12.            dma_addr_t, size_t, struct dma_attrs *attrs);

13. 

14.    dma_addr_t (*map_page)(struct device *dev, struct page *page,

15.            unsigned long offset, size_t size,

16.            enum dma_data_direction dir,

17.            struct dma_attrs *attrs);

18.    void (*unmap_page)(struct device *dev, dma_addr_tdma_handle,

19.            size_t size, enum dma_data_direction dir,

20.            struct dma_attrs *attrs);

21.    int (*map_sg)(struct device *dev, structscatterlist *sg,

22.         int nents, enum dma_data_direction dir,

23.        struct dma_attrs *attrs);

24.    void (*unmap_sg)(struct device *dev,

25.            struct scatterlist *sg, int nents,

26.            enum dma_data_direction dir,

27.            struct dma_attrs *attrs);

28.    void (*sync_single_for_cpu)(struct device *dev,

29.                dma_addr_t dma_handle, size_t size,

30.                enum dma_data_direction dir);

31.    void (*sync_single_for_device)(struct device *dev,

32.                dma_addr_t dma_handle, size_t size,

33.                enum dma_data_direction dir);

34.    void (*sync_sg_for_cpu)(struct device *dev,

35.                structscatterlist *sg, int nents,

36.                enumdma_data_direction dir);

37.    void (*sync_sg_for_device)(struct device *dev,

38.                struct scatterlist *sg, int nents,

39.                enum dma_data_direction dir);

40.    int (*mapping_error)(struct device *dev, dma_addr_tdma_addr);

41.    int (*dma_supported)(struct device *dev, u64 mask);

42.    int (*set_dma_mask)(struct device *dev, u64 mask);

43.#ifdefARCH_HAS_DMA_GET_REQUIRED_MASK

44.    u64 (*get_required_mask)(struct device *dev);

45.#endif

46.    int is_phys;

47.}

来统一屏蔽实现的差异. 
不同差异主要来来自cache的问题

Cache与dma同步问题,这里不深入讨论.

另外一个常用的函数是Dma_set_mask,  为了通知内核设备能够寻址的范围,很多时候设备能够寻址的范围有限。
 

Dma映射可以分为三类:

1.       一致性dma映射 dma_alloc_coherent (问题:驱动使用的buffer不是自身申请的,而是其他模块)

当驱动模块主动分配一个Dma缓冲区并且dma生存期和模块一样时

参数说明:

(1)这个函数的返回值是缓冲的一个内核虚拟地址, 它可被驱动使用

(2)第三个参数dma_handle:

其间相关的物理地址在 dma_handle 中返回

 

2.流式dma映射 dma_map_single 
通常用于把内核一段buffer映射,返回物理地址.

如果驱动模块需要使用从别的模块传进来的虚拟地址空间作为dma缓冲区,保证地址的线性  cache一致性

一致性api接口:sync_single_for_cpu

3.分散/聚集映射(scatter/gathermap)  Dma_map_sgs


有时候我们还需要
 

1. 回弹缓冲区 bounce  buffer:当cpu侧物理地址不适合设备的dma操作的时候

2.

DmA内存池:一般dma映射都是单个page的整数倍,如果驱动程序需要更小的一致性映射的dma缓冲区,可以使用。类似于slab机制,

Dma_pool_create

下面我们就那网卡驱动的例子说说dma的具体应用,参考linux kernel e1000网卡
drivers/net/ethernet/intel/e1000/*
 

Ring buffer

Dma不能为高端内存,一般为32,默认低端内存,由于设备能够访问的地址范围有限。

设备使用物理地址,而代码使用虚拟地址。

就看看如何发送数据包:e1000_main.c:
e1000_xmit_frame:关于帧的发送流程这里不多说.

点击(此处)折叠或打开

1. static netdev_tx_te1000_xmit_frame(struct sk_buff *skb,
 

2.                 struct net_device *netdev)

3. {

4.     structe1000_adapter *adapter = netdev_priv(netdev);

5.     structe1000_hw *hw = &adapter->hw;

6.     structe1000_tx_ring *tx_ring;

7.     unsigned int first, max_per_txd = E1000_MAX_DATA_PER_TXD;

8.     unsigned int max_txd_pwr = E1000_MAX_TXD_PWR;

9.     unsigned int tx_flags = 0;

10.    unsigned int len = skb_headlen(skb);

11.    unsigned int nr_frags;

12.    unsigned int mss;

13.    int count = 0;

14.    int tso;

15.    unsigned int f;

16. 

17.    /* This goesback to the question of how to logically map atx queue

18.     * to a flow. Right now, performance is impacted slightlynegatively

19.     * if using multiple txqueues. If the stack breaks away from a

20.     * single qdiscimplementation, we can look at this again. */

21.    tx_ring = adapter->tx_ring;

22. 

23.    if (unlikely(skb->len <= 0)) {

24.        dev_kfree_skb_any(skb);

25.        returnNETDEV_TX_OK;

26.    }

27. 

28.    /* On PCI/PCI-X HW, if packet size is less thanETH_ZLEN,

29.     * packets may get corrupted duringpadding by HW.

30.     * To WA this issue, pad all smallpackets manually.

31.     */

32.    if (skb->len < ETH_ZLEN) {

33.        if (skb_pad(skb, ETH_ZLEN - skb->len))

34.            returnNETDEV_TX_OK;

35.        skb->len = ETH_ZLEN;

36.        skb_set_tail_pointer(skb, ETH_ZLEN);

37.    }

38. 

39.    mss = skb_shinfo(skb)->gso_size;

40.    /* The controllerdoes a simple calculation to

41.     * make surethere is enough room in the FIFO before

42.     * initiating theDMA for each buffer. The calc is:

43.     * 4 = ceil(buffer len/mss). To make sure we don't

44.     * overrun the FIFO, adjust the maxbuffer len if mss

45.     * drops. */

46.    if (mss) {

47.        u8hdr_len;

48.        max_per_txd = min(mss << 2, max_per_txd);

49.        max_txd_pwr = fls(max_per_txd) - 1;

50. 

51.        hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb);

52.        if (skb->data_len && hdr_len == len) {

53.            switch (hw->mac_type) {

54.                unsigned int pull_size;

55.            case e1000_82544:

56.                /* Make sure we haveroom to chop off 4 bytes,

57.                 * and that the end alignment willwork out to

58.                 * this hardware's requirements

59.                 * NOTE: this is a TSO onlyworkaround

60.                 * if end bytealignment not correct move us

61.                 * into the next dword */

62.                if ((unsigned long)(skb_tail_pointer(skb) - 1) & 4)

63.                    break;

64.                /* fallthrough */

65.                pull_size = min((unsigned int)4, skb->data_len);

66.                if (!__pskb_pull_tail(skb, pull_size)) {

67.                    e_err(drv, "__pskb_pull_tail "

68.                     "failed.\n");

69.                    dev_kfree_skb_any(skb);

70.                    returnNETDEV_TX_OK;

71.                }

72.                len = skb_headlen(skb);

73.                break;

74.            default:

75.                /* do nothing */

76.                break;

77.            }

78.        }

79.    }

80. 

81.    /* reserve adescriptor for the offload context */

82.    if ((mss) || (skb->ip_summed == CHECKSUM_PARTIAL))

83.        count++;

84.    count++;

85. 

86.    /* ControllerErratum workaround */

87.    if (!skb->data_len && tx_ring->last_tx_tso && !skb_is_gso(skb))

88.        count++;

89. 

90.    count += TXD_USE_COUNT(len, max_txd_pwr);

91. 

92.    if (adapter->pcix_82544)

93.        count++;

94. 

95.    /* work-around for errata 10 and it applies to all controllers

96.     * in PCI-X mode, so add one moredescriptor to the count

97.     */

98.    if (unlikely((hw->bus_type == e1000_bus_type_pcix) &&

99.            (len > 2015)))

100.                count++;

101.         

102.            nr_frags = skb_shinfo(skb)->nr_frags;

103.            for (= 0; f < nr_frags; f++)

104.                count += TXD_USE_COUNT(skb_frag_size(&skb_shinfo(skb)->frags[f]),

105.                        max_txd_pwr);

106.            if (adapter->pcix_82544)

107.                count += nr_frags;

108.         

109.            /* need: count + 2 desc gap to keep tail fromtouching

110.             * head, otherwisetry next time */

111.            if (unlikely(e1000_maybe_stop_tx(netdev, tx_ring, count + 2)))

112.                returnNETDEV_TX_BUSY;

113.         

114.            if (unlikely((hw->mac_type == e1000_82547) &&

115.                 (e1000_82547_fifo_workaround(adapter, skb)))) {

116.                netif_stop_queue(netdev);

117.                if (!test_bit(__E1000_DOWN, &adapter->flags))

118.                    schedule_delayed_work(&adapter->fifo_stall_task, 1);

119.                returnNETDEV_TX_BUSY;

120.            }

121.         

122.            if (vlan_tx_tag_present(skb)) {

123.                tx_flags |= E1000_TX_FLAGS_VLAN;

124.                tx_flags |= (vlan_tx_tag_get(skb) << E1000_TX_FLAGS_VLAN_SHIFT);

125.            }

126.         

127.            first = tx_ring->next_to_use;

128.         

129.            tso = e1000_tso(adapter, tx_ring, skb);

130.            if (tso < 0) {

131.                dev_kfree_skb_any(skb);

132.                returnNETDEV_TX_OK;

133.            }

134.         

135.            if (likely(tso)) {

136.                if (likely(hw->mac_type != e1000_82544))

137.                    tx_ring->last_tx_tso = true;

138.                tx_flags |= E1000_TX_FLAGS_TSO;

139.            } else if (likely(e1000_tx_csum(adapter, tx_ring, skb)))

140.                tx_flags |= E1000_TX_FLAGS_CSUM;

141.         

142.            if (likely(skb->protocol == htons(ETH_P_IP)))

143.                tx_flags |= E1000_TX_FLAGS_IPV4;

144.         

145.            if (unlikely(skb->no_fcs))

146.                tx_flags |= E1000_TX_FLAGS_NO_FCS;

147.         

148.            count = e1000_tx_map(adapter, tx_ring, skb, first, max_per_txd,

149.            nr_frags, mss);

150.         

151.            if (count) {

152.                netdev_sent_queue(netdev, skb->len);

153.                skb_tx_timestamp(skb);

154.         

155.                e1000_tx_queue(adapter, tx_ring, tx_flags, count);

156.                /* Make surethere is space in the ring for the next send. */

157.                e1000_maybe_stop_tx(netdev, tx_ring, MAX_SKB_FRAGS + 2);

158.         

159.            } else {

160.                dev_kfree_skb_any(skb);

161.                tx_ring->buffer_info[first].time_stamp = 0;

162.                tx_ring->next_to_use = first;

163.            }

164.         

165.            returnNETDEV_TX_OK;

166.        }

经过上次,邻居子系统后,数据帧已经到达驱动,数据放在skb指定的内存里. 
看代码
tx_ring = adapter->tx_ring;  //  获取发送的ring buffer
接着我们看关键代码:
count = e1000_tx_map(adapter, tx_ring, skb,first, max_per_txd,    nr_frags, mss);
它做了什么呢?

点击(此处)折叠或打开

1. static int e1000_tx_map(structe1000_adapter *adapter,
 

2.             structe1000_tx_ring *tx_ring,

3.             structsk_buff *skb, unsigned int first,

4.             unsigned int max_per_txd, unsigned int nr_frags,

5.             unsigned int mss)

6. {

7.     structe1000_hw *hw = &adapter->hw;

8.     structpci_dev *pdev = adapter->pdev;

9.     structe1000_buffer *buffer_info;

10.    unsigned int len = skb_headlen(skb);

11.    unsigned int offset = 0, size, count = 0, i;

12.    unsigned int f, bytecount, segs;

13. 

14.    i = tx_ring->next_to_use;

15. 

16.    while (len) {

17.        buffer_info = &tx_ring->buffer_info[i];

18.        size = min(len, max_per_txd);

19.        /* Workaround for Controllererratum --

20.         * descriptor for non-tso packet in a linear SKB thatfollows a

21.         * tso gets writtenback prematurely before the data is fully

22.         * DMA'to thecontroller */

23.        if (!skb->data_len && tx_ring->last_tx_tso &&

24.         !skb_is_gso(skb)) {

25.            tx_ring->last_tx_tso = false;

26.            size -= 4;

27.        }

28. 

29.        /* Workaround for premature descwrite-backs

30.         * in TSO mode. Append 4-byte sentineldesc */

31.        if (unlikely(mss && !nr_frags && size == len && size > 8))

32.            size -= 4;

33.        /* work-around for errata 10 and it applies

34.         * to allcontrollers in PCI-X mode

35.         * The fix is to make sure that the first descriptor of a

36.         * packet is smaller than2048 - 16 - 16 (or 2016) bytes

37.         */

38.        if (unlikely((hw->bus_type == e1000_bus_type_pcix) &&

39.         (size > 2015) && count == 0))

40.        size = 2015;

41. 

42.        /* Workaround for potential 82544hang in PCI-X. Avoid

43.         * terminatingbuffers within evenly-aligned dwords. */

44.        if (unlikely(adapter->pcix_82544 &&

45.         !((unsigned long)(skb->data + offset + size - 1) & 4) &&

46.        size > 4))

47.            size -= 4;

48. 

49.        buffer_info->length = size;

50.        /* set time_stamp *before* dma to help avoid apossible race */

51.        buffer_info->time_stamp = jiffies;

52.        buffer_info->mapped_as_page = false;

53.        buffer_info->dma = dma_map_single(&pdev->dev,

54.                        skb->data + offset,

55.                        size,    DMA_TO_DEVICE);

56.        if (dma_mapping_error(&pdev->dev, buffer_info->dma))

57.            gotodma_error;

58.        buffer_info->next_to_watch = i;

59. 

60.        len -= size;

61.        offset += size;

62.        count++;

63.        if (len) {

64.            i++;

65.            if (unlikely(== tx_ring->count))

66.                i = 0;

67.        }

68.    }

69. 

70.    for (= 0; f < nr_frags; f++) {

71.        const structskb_frag_struct *frag;

72. 

73.        frag = &skb_shinfo(skb)->frags[f];

74.        len = skb_frag_size(frag);

75.        offset = 0;

76. 

77.        while (len) {

78.            unsignedlong bufend;

79.            i++;

80.            if (unlikely(== tx_ring->count))

81.                i = 0;

82. 

83.            buffer_info = &tx_ring->buffer_info[i];

84.            size = min(len, max_per_txd);

85.            /* Workaround for premature descwrite-backs

86.             * in TSO mode. Append 4-byte sentineldesc */

87.            if (unlikely(mss && f == (nr_frags-1) && size == len && size > 8))

88.                size -= 4;

89.            /* Workaround for potential 82544hang in PCI-X.

90.             * Avoid terminatingbuffers within evenly-aligned

91.             * dwords. */

92.            bufend = (unsigned long)

93.                page_to_phys(skb_frag_page(frag));

94.            bufend += offset + size - 1;

95.            if (unlikely(adapter->pcix_82544 &&

96.                 !(bufend & 4) &&

97.                size > 4))

98.                size -= 4;

99. 

100.                    buffer_info->length = size;

101.                    buffer_info->time_stamp = jiffies;

102.                    buffer_info->mapped_as_page = true;

103.                    buffer_info->dma = skb_frag_dma_map(&pdev->dev, frag,

104.                                offset, size, DMA_TO_DEVICE);

105.                    if (dma_mapping_error(&pdev->dev, buffer_info->dma))

106.                        gotodma_error;

107.                    buffer_info->next_to_watch = i;

108.         

109.                    len -= size;

110.                    offset += size;

111.                    count++;

112.                }

113.            }

114.         

115.            segs = skb_shinfo(skb)->gso_segs ?: 1;

116.            /* multiply datachunks by size of headers */

117.            bytecount = ((segs - 1) * skb_headlen(skb)) + skb->len;

118.         

119.            tx_ring->buffer_info[i].skb = skb;

120.            tx_ring->buffer_info[i].segs = segs;

121.            tx_ring->buffer_info[i].bytecount = bytecount;

122.            tx_ring->buffer_info[first].next_to_watch = i;

123.         

124.            returncount;

125.         

126.        dma_error:

127.            dev_err(&pdev->dev, "TX DMA map failed\n");

128.            buffer_info->dma = 0;

129.            if (count)

130.                count--;

131.         

132.            while (count--) {

133.                if (i==0)

134.                    i += tx_ring->count;

135.                i--;

136.                buffer_info = &tx_ring->buffer_info[i];

137.                e1000_unmap_and_free_tx_resource(adapter, buffer_info);

138.            }

139.         

140.            return0;

141.        }

默认数据报文没有分片或者碎片什么的。
那么进入第一个while(len)
获取buffer_info = &tx_ring->buffer_info[i];
然后:调用dma_map_single进行流式映射. 即把skb->data(虚拟地址) 和buffer_info->dma(物理地址)对应起来.操作两个地址等于操作同一片区域。

点击(此处)折叠或打开

1. buffer_info->length = size;
 

2.         /* set time_stamp *before* dma to help avoid apossible race */

3.         buffer_info->time_stamp = jiffies;

4.         buffer_info->mapped_as_page = false;

5.         buffer_info->dma = dma_map_single(&pdev->dev,

6.                         skb->data + offset,

7.                         size,    DMA_TO_DEVICE);

回到主发送函数:

点击(此处)折叠或打开

1. if (count) {
 

2.         netdev_sent_queue(netdev, skb->len);

3.         skb_tx_timestamp(skb);

4.  

5.         e1000_tx_queue(adapter, tx_ring, tx_flags, count);

6.         /* Make surethere is space in the ring for the next send. */

7.         e1000_maybe_stop_tx(netdev, tx_ring, MAX_SKB_FRAGS + 2);

8.  

9.     }

调用e1000_tx_queue把数据发送出去:

点击(此处)折叠或打开

1. static voide1000_tx_queue(struct e1000_adapter *adapter,
 

2.             struct e1000_tx_ring *tx_ring, int tx_flags,

3.              int count)

4. {

5.     structe1000_hw *hw = &adapter->hw;

6.     structe1000_tx_desc *tx_desc = NULL;

7.     structe1000_buffer *buffer_info;

8.     u32txd_upper = 0, txd_lower = E1000_TXD_CMD_IFCS;

9.     unsigned int i;

10.    

11.    ...

12. 

13.    i = tx_ring->next_to_use;

14. 

15.    while (count--) {

16.        buffer_info = &tx_ring->buffer_info[i];

17.        tx_desc = E1000_TX_DESC(*tx_ring, i);

18.        tx_desc->buffer_addr = cpu_to_le64(buffer_info->dma);

19.        tx_desc->lower.data =

20.            cpu_to_le32(txd_lower | buffer_info->length);

21.        tx_desc->upper.data = cpu_to_le32(txd_upper);

22.        if (unlikely(++== tx_ring->count)) i = 0;

23.    }

24. 

25.    tx_desc->lower.data |= cpu_to_le32(adapter->txd_cmd);

26. 

27.    /* txd_cmd re-enables FCS, so we'll re-disable it here asdesired. */

28.    if (unlikely(tx_flags & E1000_TX_FLAGS_NO_FCS))

29.        tx_desc->lower.data &= ~(cpu_to_le32(E1000_TXD_CMD_IFCS));

30. 

31.    /* Force memorywrites to complete before letting h/w

32.     * know there arenew descriptors to fetch. (Only

33.     * applicable for weak-ordered memory modelarchs,

34.     * such as IA-64). */

35.    wmb();

36. 

37.    tx_ring->next_to_use = i;

38.    writel(i, hw->hw_addr + tx_ring->tdt);

39.    /* we needthis if more than one processor can write to our tail

40.     * at a time, it syncronizesIO on IA64/Altix systems */

41.    mmiowb();

42.}

我们看到它把刚才dma_map_singe里的映射赋值了:
tx_desc->buffer_addr =cpu_to_le64(buffer_info->dma);
说明发送的时候是根据发送描述符来发送的。
然后操作寄存器:
writel(i, hw->hw_addr + tx_ring->tdt);
那么网卡就会自动读取tx desc 然后把数据发送出去。
总结下流程:
1. linux os会调用网卡的start_xmit()函数。在e1000里,对应的函数是 e1000_xmit_frame,
2.   e1000_xmit_frame又会调用e1000_tx_queue(adapter, tx_ring,tx_flags, count)
这里的tx_queue指的是发送Descriptorqueue
3. e1000_tx_queue 在检查了一些参数后,最终调用 writel(i, hw->hw_addr +tx_ring->tdt)
这里的tx_ring->tdt中的tdt全写为 tx_descriptor_tail。从网卡的开发手册中可以查到,如果写了descriptor tail,那么网卡就会自动读取 descriptor,然后把包发送出去。
descroptor的主要内容是addr pointerlength。前者是要发送的包的起始物理地址。后者是包的长度。有了这些,硬件就可以通过dma来读取包并发出去了。其他网卡也基本会用descriptor的结构。

虽然流程明白了,但是还有几个点,
1. tx_ring在哪初始化?
2. 网卡到底是如何操作映射的dma地址的,把数据发送出去的?

tx ring 在e1000_open 的时候:
调用:

点击(此处)折叠或打开

1. /**
 

2.  * e1000_setup_all_tx_resources - wrapper to allocate Txresources

3.  *                  (Descriptors) for all queues

4.  * @adapter: board private structure

5.  *

6.  * Return 0 on success, negative on failure

7.  **/

8.  

9. int e1000_setup_all_tx_resources(struct e1000_adapter *adapter)

10.{

11.    int i, err = 0;

12. 

13.    for (= 0; i < adapter->num_tx_queues; i++) {

14.        err = e1000_setup_tx_resources(adapter, &adapter->tx_ring[i]);

15.        if (err) {

16.            e_err(probe, "Allocation for Tx Queue %u failed\n", i);

17.            for (i-- ; i >= 0; i--)

18.                e1000_free_tx_resources(adapter,

19.                            &adapter->tx_ring[i]);

20.            break;

21.        }

22.    }

23. 

24.    return err;

25.}

点击(此处)折叠或打开

1. /**
 

2.  * e1000_setup_tx_resources - allocate Tx resources (Descriptors)

3.  * @adapter: board private structure

4.  * @txdr: tx descriptorring (for a specific queue) to setup

5.  *

6.  * Return 0 on success, negative on failure

7.  **/

8.  

9. static int e1000_setup_tx_resources(structe1000_adapter *adapter,

10.                struct e1000_tx_ring *txdr)

11.{

12.    structpci_dev *pdev = adapter->pdev;

13.    int size;

14. 

15.    size = sizeof(struct e1000_buffer) * txdr->count;

16.    txdr->buffer_info = vzalloc(size);

17.    if (!txdr->buffer_info) {

18.        e_err(probe, "Unable to allocate memory for the Tx descriptor "

19.         "ring\n");

20.        return -ENOMEM;

21.    }

22. 

23.    /* round up to nearest 4K */

24. 

25.    txdr->size = txdr->count * sizeof(struct e1000_tx_desc);

26.    txdr->size = ALIGN(txdr->size, 4096);

27. 

28.    txdr->desc = dma_alloc_coherent(&pdev->dev, txdr->size, &txdr->dma,

29.                    GFP_KERNEL);

30.    if (!txdr->desc) {

31.setup_tx_desc_die:

32.        vfree(txdr->buffer_info);

33.        e_err(probe, "Unable to allocate memory for the Tx descriptor "

34.         "ring\n");

35.        return -ENOMEM;

36.    }

37. 

38.    /* Fix for errata 23, can't cross 64kB boundary */

39.    if (!e1000_check_64k_bound(adapter, txdr->desc, txdr->size)) {

40.        void *olddesc = txdr->desc;

41.        dma_addr_tolddma = txdr->dma;

42.        e_err(tx_err, "txdr align check failed: %u bytes at %p\n",

43.        txdr->size, txdr->desc);

44.        /* Try again, without freeingthe previous */

45.        txdr->desc = dma_alloc_coherent(&pdev->dev, txdr->size,

46.                        &txdr->dma, GFP_KERNEL);

47.        /* Failed allocation, criticalfailure */

48.        if (!txdr->desc) {

49.            dma_free_coherent(&pdev->dev, txdr->size, olddesc,

50.                    olddma);

51.            gotosetup_tx_desc_die;

52.        }

53. 

54.        if (!e1000_check_64k_bound(adapter, txdr->desc, txdr->size)) {

55.            /* give up */

56.            dma_free_coherent(&pdev->dev, txdr->size, txdr->desc,

57.                    txdr->dma);

58.            dma_free_coherent(&pdev->dev, txdr->size, olddesc,

59.                    olddma);

60.            e_err(probe, "Unable to allocate aligned memory "

61.             "for the transmit descriptor ring\n");

62.            vfree(txdr->buffer_info);

63.            return -ENOMEM;

64.        } else {

65.            /* Free oldallocation, new allocation was successful */

66.            dma_free_coherent(&pdev->dev, txdr->size, olddesc,

67.                    olddma);

68.        }

69.    }

70.    memset(txdr->desc, 0, txdr->size);

71. 

72.    txdr->next_to_use = 0;

73.    txdr->next_to_clean = 0;

74. 

75.    return0;

76.}

我们看:它建立了一致性dma映射.
 

1.        txdr->desc = dma_alloc_coherent(&pdev->dev, txdr->size,

2.                         &txdr->dma, GFP_KERNEL);

desc是结构指针:它的结构跟网卡寄存器结构有关,e1000_hw.h

点击(此处)折叠或打开

1. /* Transmit Descriptor */
 

2. structe1000_tx_desc {

3.     __le64buffer_addr;    /* Address of thedescriptor's data buffer */

4.     union {

5.         __le32data;

6.         struct {

7.             __le16length;    /* Data bufferlength */

8.             u8cso;    /* Checksumoffset */

9.             u8cmd;    /* Descriptorcontrol */

10.        } flags;

11.    } lower;

12.    union {

13.        __le32data;

14.        struct {

15.            u8status;    /* Descriptorstatus */

16.            u8css;    /* Checksumstart */

17.            __le16special;

18.        } fields;

19.    } upper;

20.}


我们稍微屡一下,
1. skb->data  ---ring->buffer_info->dma
2.ring->dma  ---  ring->desc
3. ring->desc->buffer_addr---ring->buffer_info->dma

那么网卡又是如何和dma地址关联的呢?

点击(此处)折叠或打开

1. /**
 

2.  * e1000_configure_tx - Configure 8254xTransmit Unit after Reset

3.  * @adapter: board private structure

4.  *

5.  * Configure the Txunit of the MAC after a reset.

6.  **/

7.  

8. static voide1000_configure_tx(struct e1000_adapter *adapter)

9. {

10.    u64tdba;

11.    structe1000_hw *hw = &adapter->hw;

12.    u32tdlen, tctl, tipg;

13.    u32ipgr1, ipgr2;

14. 

15.    /* Setup the HW TxHead and Tail descriptor pointers */

16. 

17.    switch (adapter->num_tx_queues) {

18.    case 1:

19.    default:

20.        tdba = adapter->tx_ring[0].dma;

21.        tdlen = adapter->tx_ring[0].count *

22.            sizeof(struct e1000_tx_desc);

23.        ew32(TDLEN, tdlen);

24.        ew32(TDBAH, (tdba >> 32));

25.        ew32(TDBAL, (tdba & 0x00000000ffffffffULL));

26.        ew32(TDT, 0);

27.        ew32(TDH, 0);

28.        adapter->tx_ring[0].tdh = ((hw->mac_type >= e1000_82543) ? E1000_TDH : E1000_82542_TDH);

29.        adapter->tx_ring[0].tdt = ((hw->mac_type >= e1000_82543) ? E1000_TDT : E1000_82542_TDT);

30.        break;

31.    }

很明显它把dma地址写入了网卡dma寄存器。所以dma还需要网卡硬件的支持才行.
 

当然e1000这个网卡驱动还是相当的复杂,不过它把一致性映射和流式映射都用上了。

 

资料四:

本文描述DMAAPI。更详细的介绍请参看Documentation/DMA-API-HOWTO.txt

API分为两部分,第一部分描述API,第二部分描述可以支持非一致性内存机器的扩展API。你应该使用第一部分所描述的API,除非你知道你的驱动必须要支持非一致性平台。

第一部分 DMA API

为了可以引用DMA API,你必须 #include <linux/dma-mapping.h>

1-1使用大块DMA一致性缓冲区(dma-coherent buffers

void * 
dma_alloc_coherent(struct device *dev, size_t size,
                   dma_addr_t *dma_handle, gfp_t flag)

一致性内存:设备对一块内存进行写操作,处理器可以立即进行读操作,而无需担心处理器高速缓存(cache)的影响。同样的,处理器对一块内存进行些操作,设备可以立即进行读操作。(在告诉设备读内存时,你可能需要确定刷新处理器的写缓存。)

此函数申请一段大小为size字节的一致性内存,返回两个参数。一个是dma_handle,它可以用作这段内存的物理地址。另一个是指向被分配内存的指针(处理器的虚拟地址)。

注意:由于在某些平台上,使用一致性内存代价很高,比如最小的分配长度为一个页。因此你应该尽可能合并申请一致性内存的请求。最简单的办法是使用dma_pool函数调用(详见下文)。

参数flag(仅存在于dma_alloc_coherent中)运行调用者定义申请内存时的GFP_flags(详见kmalloc)。

void* 
dma_zalloc_coherent(struct device *dev, size_tsize, 
                   dma_addr_t *dma_handle, gfp_t flag)

dma_alloc_coherent()的封装,如果内存分配成功,则返回清零的内存。

void 
dma_free_coherent(struct device *dev, size_t size,void *cpu_addr, 
                   dma_addr_t dma_handle)

释放之前申请的一致性内存。dev, sizedma_handle必须和申请一致性内存的函数参数相同。cpu_addr必须为申请一致性内存函数的返回虚拟地址。

注意:和其他内存分配函数不同,这些函数必须要在中断使能的情况下使用。

1-2使用小块DMA一致性缓冲区

如果要使用这部分DMA API,必须#include <linux/dmapool.h>

许多驱动程序需要为DMA描述符或者I/O内存申请大量小块DMA一致性内存。你可以使用DMA 内存池,而不是申请以页为单位的内存块或者调用dma_alloc_coherent()。这种机制有点像struct kmem_cache,只是它利用了DMA一致性内存分配器,而不是调用 __get_free_pages()。同样地,DMA 内存池知道通用硬件的对齐限制,比如队列头需要N字节对齐。

structdma_pool * 
dma_pool_create(const char *name, struct device*dev, 
               size_t size, size_t align, size_t alloc);

create( )函数为设备初始化DMA一致性内存的内存池。它必须要在可睡眠上下文调用。

name为内存池的名字(就像struct kmem_cache name一样)。devsize就如dma_alloc_coherent()参数一样。align为设备硬件需要的对齐大小(单位为字节,必须为2的幂次方)。如果设备没有边界限制,可以设置该参数为0。如果设置为4096,则表示从内存池分配的内存不能超过4K字节的边界。

void*
dma_pool_alloc(struct dma_pool *pool, gfp_tgfp_flags, 
               dma_addr_t *dma_handle);

从内存池中分配内存。返回的内存同时满足申请的大小及对齐要求。设置GFP_ATOMIC可以确保内存分配被block,设置GFP_KERNEL(不能再中断上下文,不会保持SMP锁)允许内存分配被block。和dma_alloc_coherent()一样,这个函数会返回两个值:一个值是cpu可以使用的虚拟地址,另一个值是内存池设备可以使用的dma物理地址。

void 
dma_pool_free(struct dma_pool *pool, void*vaddr, 
               dma_addr_t addr);

返回内存给内存池。参数pool为传递给dma_pool_alloc()pool,参数vaddraddrdma_pool_alloc()的返回值。

void 
dma_pool_destroy(struct dma_pool *pool);

内存池析构函数用于释放内存池的资源。这个函数在可睡眠上下文调用。请确认在调用此函数时,所有从该内存池申请的内存必须都要归还给内存池。

1-3DMA寻址限制

int 
dma_supported(struct device *dev, u64 mask)

用来检测该设备是否支持掩码所表示的DMA寻址能力。比如mask0x0FFFFFF,则检测该设备是否支持24位寻址。

返回1表示支持,0表示不支持。

注意:该函数很少用于检测是否掩码为可用的,它不会改变当前掩码设置。它是一个内部API而非供驱动者使用的外部API

int 
dma_set_mask(struct device *dev, u64 mask)

检测该掩码是否合法,如果合法,则更新设备参数。即更新设备的寻址能力。

返回0表示成功,返回负值表示失败。

int 
dma_set_coherent_mask(struct device *dev, u64 mask)

检测该掩码是否合法,如果合法,则更新设备参数。即更新设备的寻址能力。

返回0表示成功,返回负值表示失败。

u64 
dma_get_required_mask(struct device *dev)

该函数返回平台可以高效工作的掩码。通常这意味着返回掩码是可以寻址到所有内存的最小值。检查该值可以让DMA描述符的大小尽量的小。

请求平台需要的掩码并不会改变当前掩码。如果你想利用这点,可以利用改返回值通过dma_set_mask()设置当前掩码。

1-4流式DMA映射

dma_addr_t 
dma_map_single(struct device *dev, void *cpu_addr, size_t size,
               enum dma_data_direction direction)

映射一块处理器的虚拟地址,这样可以让外设访问。该函数返回内存的物理地址。

dma_API中强烈建议使用表示DMA传输方向的枚举类型。

DMA_NONE   仅用于调试目的
DMA_TO_DEVICE    数据从内存传输到设备,可认为是写操作。
DMA_FROM_DEVICE    数据从设备传输到内存,可认为是读操作。
DMA_BIDIRECTIONAL    不清楚传输方向则可用该类型。

请注意:并非一台机器上所有的内存区域都可以用这个API映射。进一步说,对于内核连续虚拟地址空间所对应的物理地址并不一定连续(比如这段地址空间由vmalloc申请)。因为这种函数并未提供任何分散/聚集能力,因此用户在企图映射一块非物理连续的内存时,会返回失败。基于此原因,如果想使用该函数,则必须确保缓冲区的物理内存连续(比如使用kmalloc)。

更进一步,所申请内存的物理地址必须要在设备的dma_mask寻址范围内(dma_mask表示与设备寻址能力对应的位)。为了确保由kmalloc申请的内存在dma_mask中,驱动程序需要定义板级相关的标志位来限制分配的物理内存范围(比如在x86上,GFP_DMA用于保证申请的内存在可用物理内存的前16Mb空间,可以由ISA设备使用)。

同时还需注意,如果平台有IOMMU(设备拥有MMU单元,可以进行I/O内存总线和设备的映射,即总线地址和内存物理地址的映射),则上述物理地址连续性及外设寻址能力的限制就不存在了。当然为了方便起见,设备驱动开发者可以假设不存在IOMMU

警告:内存一致性操作基于高速缓存行(cache line)的宽度。为了可以正确操作该API创建的内存映射,该映射区域的起始地址和结束地址都必须是高速缓存行的边界(防止在一个高速缓存行中有两个或多个独立的映射区域)。因为在编译时无法知道高速缓存行的大小,所以该API无法确保该需求。因此建议那些对高速缓存行的大小不特别关注的驱动开发者们,在映射虚拟内存时保证起始地址和结束地址都是页对齐的(页对齐会保证高速缓存行边界对齐的)。

DMA_TO_DEVICE   软件对内存区域做最后一次修改后,且在传输给设备前,需要做一次同步。一旦该使用该原语,内存区域可被视作设备只读缓冲区。如果设备需要对该内存区域进行写操作,则应该使用DMA_BIDIRECTIONAL(如下所示)

DMA_FROM_DEVICE   驱动在访问数据前必须做一次同步,因为数据可能被设备修改了。内存缓冲区应该被当做驱动只读缓冲区。如果驱动需要进行写操作,应该使用DMA_BIDIRECTIONAL(如下所示)。

DMA_BIDIRECTIONAL   需要特别处理:这意味着驱动并不确定内存数据传输到设备前,内存是否被修改了,同时也不确定设备是否会修改内存。因此,你必须需要两次同步双向内存:一次在内存数据传输到设备前(确保所有缓冲区数据改变都从处理器的高速缓存刷新到内存中),另一次是在设备可能访问该缓冲区数据前(确保所有处理器的高速缓存行都得到了更新,设备可能改变了缓冲区数据)。即在处理器写操作完成时,需要做一次刷高速缓存的操作,以确保数据都同步到了内存缓冲区中。在处理器读操作前,需要更新高速缓冲区的行,已确保设备对内存缓冲区的改变都同步到了高速缓冲区中。

void 
dma_unmap_single(struct device *dev, dma_addr_tdma_addr, size_t size,
               enum dma_data_direction direction)

取消先前的内存映射。传入该函数的所有参数必须和映射API函数的传入(包括返回)参数相同。

dma_addr_t 
dma_map_page(struct device *dev, struct page *page,
                   unsigned long offset, size_t size,
                   enum dma_data_direction direction)

void 
dma_unmap_page(struct device *dev, dma_addr_tdma_address, size_t size,
               enum dma_data_direction direction)

对页进行映射/取消映射的API。对其他映射API的注意事项及警告对此都使用。同样的,参数<offset><size>用于部分页映射,如果你对高速缓存行的宽度不清楚的话,建议你不要使用这些参数。

int 
dma_mapping_error(struct device *dev, dma_addr_tdma_addr)

在某些场景下,通过dma_map_singledma_map_page创建映射可能会失败。驱动程序可以通过此函数来检测这些错误。一个非零返回值表示未成功创建映射,驱动程序需要采取适当措施(比如降低当前DMA映射使用率或者等待一段时间再尝试)。

int
dma_map_sg(struct device *dev, struct scatterlist*sg,
       int nents, enum dma_data_direction direction)

返回值:被映射的物理内存块的数量(如果在分散/聚集链表中一些元素是物理地址或虚拟地址相邻的,切IOMMU可以将它们映射成单个内存块,则返回值可能比输入值<nents>小)。

请注意如果sg已经映射过了,其不能再次被映射。再次映射会销毁sg中的信息。

如果返回0,则表示dma_map_sg映射失败,驱动程序需要采取适当措施。驱动程序在此时做一些事情显得格外重要,一个阻塞驱动中断请求或者oopsing都总比什么都不做导致文件系统瘫痪强很多。

下面是个分散/聚集映射的例子,假设scatterlists已经存在。

int i,count = dma_map_sg(dev, sglist, nents, direction);
struct scatterlist *sg;

for_each_sg(sglist, sg, count, i) {
        hw_address[i] = sg_dma_address(sg);
        hw_len[i] = sg_dma_len(sg); 
}

其中nentssglist条目的个数。

这种实现可以很方便将几个连续的sglist条目合并成一个(比如在IOMMU系统中,或者一些页正好是物理连续的)。

然后你就可以循环多次(可能小于nents次)使用sg_dma_address() sg_dma_len()来获取sg的物理地址及长度。

void 
dma_unmap_sg(struct device *dev, structscatterlist *sg,
       int nhwentries, enum dma_data_direction direction)

取消先前分散/聚集链表的映射。所有参数和分散/聚集映射API的参数相同。

注意:<nents>是传入的参数,不一定是实际返回条目的数值。

void dma_sync_single_for_cpu(structdevice *dev, dma_addr_t dma_handle, size_t size,
                               enum dma_data_direction direction)

void dma_sync_single_for_device(structdevice *dev, dma_addr_t dma_handle, size_t size,
                               enum dma_data_direction direction)

void dma_sync_sg_for_cpu(structdevice *dev, struct scatterlist *sg, int nelems,
                           enum dma_data_direction direction)

void dma_sync_sg_for_device(structdevice *dev, struct scatterlist *sg, int nelems,
                           enum dma_data_direction direction)

CPU及外设同步single contiguous或分散/聚集映射。

注意:你必须要做这个工作,

·        在CPU读操作前,此时缓冲区由设备通过DMA写入数据(DMA_FROM_DEVICE)

·        在CPU写操作后,缓冲区数据将通过DMA传输到设备(DMA_TO_DEVICE)

·        在传输数据到设备前后(DMA_BIDIRECTIONAL)

dma_addr_t 
dma_map_single_attrs(struct device *dev, void*cpu_addr, size_t size,
                   enum dma_data_direction dir,
                   struct dma_attrs *attrs)

void 
dma_unmap_single_attrs(struct device *dev, dma_addr_tdma_addr, 
                   size_t size, enum dma_data_direction dir,
                   struct dma_attrs *attrs)

int 
dma_map_sg_attrs(struct device *dev, structscatterlist *sgl, 
               int nents, enum dma_data_direction dir,
               struct dma_attrs *attrs)

void 
dma_unmap_sg_attrs(struct device *dev, structscatterlist *sgl, 
                   int nents, enum dma_data_direction dir,
                   struct dma_attrs *attrs)

这四个函数除了传入可选的struct dma_attrs*之外,其他和不带_attrs后缀的函数一样。

structdma_attrs概述了一组DMA属性。struct dma_attrs详细定义请参见linux/dma-attrs.h

DMA属性的定义是和体系结构相关的,并且Documentation/DMA-attributes.txt有详细描述。

如果struct dma_attrs* 为空,则这些函数可以认为和不带_attrs后缀的函数相同。

下面给出一个如何使用*_attrs 函数的例子,当进行DMA内存映射时,如何传入一个名为DMA_ATTR_FOO的属性:

#include<linux/dma-attrs.h> 
/* DMA_ATTR_FOO should be defined in linux/dma-attrs.h and
* documented in Documentation/DMA-attributes.txt */ 
...
        DEFINE_DMA_ATTRS(attrs);
        dma_set_attr(DMA_ATTR_FOO,&attrs);
        ....
        n = dma_map_sg_attrs(dev, sg, nents,DMA_TO_DEVICE, &attr);
        ....

在映射/取消映射的函数中,可以检查DMA_ATTR_FOO是否存在:

voidwhizco_dma_map_sg_attrs(struct device *dev, dma_addr_t dma_addr,
                           size_t size, enum dma_data_direction dir,
                           struct dma_attrs *attrs) 
{
        ....
        int foo = dma_get_attr(DMA_ATTR_FOO,attrs);
        ....
        if (foo)
            /* twizzlethe frobnozzle */
        ....

第二部分  高级DMA使用方法

警告:下面这些DMA API在大多数情况下不应该被使用。因为它们为一些特殊的需求而准备的,大部分驱动程序并没有这些需求。

如果你不清楚如何确保桥接处理器和I/O设备之间的高速缓存行的一致性,你就根本不应该使用该部分所提到的API

void* 
dma_alloc_noncoherent(struct device *dev, size_t size,
                           dma_addr_t *dma_handle, gfp_t flag)

平台会根据自身适应条件来选择返回一致性或非一致性内存,其他和dma_alloc_coherent()相同。在使用该函数时,你应该确保在驱动程序中对该内存做了正确的和必要的同步操作。

注意,如果返回一致性内存,则它会确保所有同步操作都变成空操作。

警告:处理非一致性内存是件痛苦的事情。如果你确信你的驱动要在非常罕见的平台上(通常是非PCI平台)运行,这些平台无法分配一致性内存时,你才可以使用该API

void 
dma_free_noncoherent(struct device *dev, size_t size, void*cpu_addr,
                           dma_addr_t dma_handle)

释放由非一致性API申请的内存。

int 
dma_get_cache_alignment(void)

返回处理器高速缓存对齐值。应该注意在你打算映射内存或者做局部映射时,该值为最小对齐值。

注意:该API可能返回一个比实际缓存行的大的值。通常为了方便对齐,该值为2的幂次方。

void 
dma_cache_sync(struct device *dev, void *vaddr, size_tsize, 
               enum dma_data_direction direction)

对由dma_alloc_noncoherent()申请的内存做局部映射,其实虚拟地址为vaddr。在做该操作时,请注意缓存行的边界。

int 
dma_declare_coherent_memory(struct device *dev, dma_addr_tbus_addr,
                           dma_addr_t device_addr, size_t size, int flags)

当设备需要一段一致性内存时,申请由dma_alloc_coherent分配的一段内存区域。

flag 可以由下面这些标志位进行或操作。

DMA_MEMORY_MAP   请求由dma_alloc_coherent()申请的内存为直接可写。

DMA_MEMORY_IO   请求由dma_alloc_coherent()申请的内存可以通过read/write/memcpy_toio等函数寻址到。

flag必须包含上述其中一个或者两个标志位。

DMA_MEMORY_INCLUDES_CHILDREN   

DMA_MEMORY_EXCLUSIVE   

为了使操作简单化,每个设备只能申申明一个该内存区域。

处于效率考虑的目的,大多数平台选择页对齐的区域。对于更小的内存分配,可以使用dma_pool() API

void 
dma_release_declared_memory(struct device *dev)

从系统中移除先前申明的内存区域。该函数不会检测当前区域是否在使用。确保该内存区域当前没有被使用这是驱动程序的事情。

void* 
dma_mark_declared_memory_occupied(structdevice *dev, 
               dma_addr_t device_addr, size_t size)

该函数用于覆盖特殊内存区域(dma_alloc_coherent()会分配出第一个可用内存区域)。

返回值为指向该内存的处理器虚拟地址,或者如果其中福分区域被覆盖,则返回一个错误(通过PRT_ERR())。

第三部分  调试驱动程序对DMA-API的使用情况

DMA-API如前文所述有一些限制。在支持硬件IOMMU的系统中,驱动程序不能违反这些限制将变得更加重要。最糟糕的情况是,如果违反了这些限制准则,会导致数据出错知道摧毁文件系统。

为了debug驱动程序及发现使用DMA-API时的bug,检测代码可以编译到kernel中,它们可以告诉开发者那些违规行为。如果你的体系结构支持,你可以选择编译选项“Enabledebugging of DMA-API usage”,使能这个选项会影响系统性能,所以请勿在产品内核中加入该选项。

如果你用使能debug选项的内核启动,那么它会记录哪些设备会使用什么DMA内存。如果检测到错误信息,则会在内核log中打印一些警告信息。下面是一个警告提示的例子:

------------[ cut here]------------
WARNING: at /data2/repos/linux-2.6-iommu/lib/dma-debug.c:448 
        check_unmap+0x203/0x490() 
Hardware name: 
forcedeth 0000:00:08.0: DMA-API: device driver frees DMA memory withwrong 
        function [deviceaddress=0x00000000640444be] [size=66 bytes] [mapped as 
single] [unmapped as page] 
Modules linked in: nfsd exportfs bridge stp llc r8169 
Pid: 0, comm: swapper Tainted: G W 2.6.28-dmatest-09289-g8bb99c0 #1 
Call Trace: 
<IRQ> [<ffffffff80240b22>] warn_slowpath+0xf2/0x130
[<ffffffff80647b70>] _spin_unlock+0x10/0x30
[<ffffffff80537e75>] usb_hcd_link_urb_to_ep+0x75/0xc0
[<ffffffff80647c22>] _spin_unlock_irqrestore+0x12/0x40
[<ffffffff8055347f>] ohci_urb_enqueue+0x19f/0x7c0
[<ffffffff80252f96>] queue_work+0x56/0x60
[<ffffffff80237e10>] enqueue_task_fair+0x20/0x50
[<ffffffff80539279>] usb_hcd_submit_urb+0x379/0xbc0
[<ffffffff803b78c3>] cpumask_next_and+0x23/0x40
[<ffffffff80235177>] find_busiest_group+0x207/0x8a0
[<ffffffff8064784f>] _spin_lock_irqsave+0x1f/0x50
[<ffffffff803c7ea3>] check_unmap+0x203/0x490
[<ffffffff803c8259>] debug_dma_unmap_page+0x49/0x50
[<ffffffff80485f26>] nv_tx_done_optimized+0xc6/0x2c0
[<ffffffff80486c13>] nv_nic_irq_optimized+0x73/0x2b0
[<ffffffff8026df84>] handle_IRQ_event+0x34/0x70
[<ffffffff8026ffe9>] handle_edge_irq+0xc9/0x150
[<ffffffff8020e3ab>] do_IRQ+0xcb/0x1c0
[<ffffffff8020c093>] ret_from_intr+0x0/0xa
<EOI> <4>---[ end trace f6435a98e2a38c0e ]---

驱动开发者可以通过DMA-API的栈回溯信息找出什么导致这些警告。

默认情况下只有第一个错误会打印警告信息,其他错误不会打印警告信息。这种机制保证当前警告打印信息不会冲了你的内核信息。为了debug设备驱动,可以通过debugfs禁止该功能。请看下面详细的defbugfs接口文档。

调试DMA-API代码的debugfs目录叫dma-api/。下列文件存在于该个目录下:

dma-api/all_errors   该文件节点包含一个数值。如果该值不为零,则调试代码会在遇到每个错误的时候都打印警告信息。请注意这个选项会轻易覆盖你的内核信息缓冲区。

dma-api/disabled   只读文件节点,如果禁止调试代码则显示字符“Y”。当系统没有足够内存或者在系统启动时禁止调试功能时,该节点显示“Y”

dma-api/error_count   只读文件节点,显示发现错误的次数。

dma-api/num_errors   该文件节点显示在打印停止前一共打印多少个警告信息。该值在系统启动时初始化为1,通过写该文件节点来设置该值。

dma-api/min_free_entries   只读文件节点,显示分配器记录的可用dma_debug_entries的最小数目。如果该值变为零,则禁止调试代码。

dma-api/num_free_entries   当前分配器可用dma_debug_entries的数目。

dma-api/driver-filter   通过向该文件节点写入驱动的名字来限制特定驱动的调试输出。如果向该节点输入空字符,则可以再次看到全部错误信息。

如果这些代码默认编译到你的内核中,该调试功能被默认打开。如果在启动时你不想使用该功能,则可以设置“dma_debug=off”作为启动参数,该参数会禁止该功能。如果你想在系统启动后再次打开该功能,则必须重启系统。

如果你指向看到特定设备驱动的调试信息,则可以设置“dma_debug_driver=<drivername>”作为参数。它会在系统启动时使能驱动过滤器。调试代码只会打印和该驱动相关的错误信息。过滤器可以通过debugfs来关闭或者改变。

如果该调试功能在系统运行时自动关闭,则可能是超出了dma_debug_entries的最大限制。这些debug条目在启动时就分配好了,条目数量由每个体系结构自己定义。你可以在启动时使用“dma_debug_entries=<your_desired_number>”来重写该值。 

参考文献

[1] documentation/DMA-API.txt

 

5 其它问题

一、切换到hardware-v2分支之后,编译之后无法挂载文件系统

       日前:20180524

       从http://10.10.6.56/zhangji/AlstraliveApp.git仓库,checkout--track分支origian/ feature/hardware-V2 到本地分支并且新创建出了本地分支feature/hardware-V2追踪远程仓库的feature/hardware-V2分支。

       在本地checkout 到feature/hardware-V之后,编译烧录镜像到rv1108开发版,内核启动时报以下无法挂载文件系统的错误:

       经分析对比发现是因为内核的配置.config中多出如下部分:

     

       去掉initrd相关配置即可解决上述问题:方法如下:

 

       去掉上述 InitialRAM filesystem and RAM disk 配置即可。

       为什么配置上 initramfsinitrd 会出现此问题,需要研究Linux INITRAMFS INITRD

二、WIN10禁止usb驱动强制签名

日期:20180525

Win10系统刚出,很多驱动因为签名问题而无法安装,下面就告诉你怎么禁用驱动程序强制签名。

方法/步骤

1.   点击通知,找到并进入“所有设置”。

2.   在所有设置中找到并进入“更新和安全”

3.   找到恢复,点击“高级启动”下的“立即重启”,重启电脑。

4.   重启后选择“疑难解答”。

5.   选择“高级选项”

6.   选择“启动设置”。

7.   点击“重启”。

8.   按提示输入“7”禁用驱动程序强制签名。

   禁止usb驱动强制签名设置之后,每次重启电脑之后此次设置就无效了,需要重新执行上述步骤设置。

三、代码调试信息关闭打开

日期20180525

       一般通过一个宏定义来打开关闭调试信息(打开调试信息的情况下,调试信息有优先级之分)。此宏定义的方式有如下两种方式:

1, 在一个头文件种定义,然后所有的源码文件都包含此头文件。

2,在编译’.c’或者’.cpp’的时候通过编译参数‘-D’ 传递到源文件中。

       在内核中可通过makemenuconfig 配置,结果放在’.config’文件中,编译时会自动的根据’.config’内容生成一个有宏定义的头文件’autoconf.h’。在被相关源码包含此头文件,此方法与上述1方法类似。

四、USB物理协议抓包分析工具

20180528

使用的usb物理层协议分析仪是totalphase的BEAGLE USB 5000 V2

1, 硬件连接框图

 

2,安装total phase usb driver

1>运行TotalPhaseUSB-v2.15.exe

2>

Just saving everyone having to write to support, if it stops at"removing phantom devices", do this: 

1. Download the latest version of the TotalPhase USB driver installer from our website - http://www.totalphase.com/products/usb-drivers-windows/ 
2. Plug in the Total Phase device. 
3. Run the EXE installation fileTotalPhaseUSB-v2.

15.exe 
4. Make sure that the folder “C:\Users[USERNAME]\AppData\Local\Temp” includeswinusbtp.inf, winusbtp.cat, tpdibus.inf, and tpdibus.cat files, and amd64 andi386 directories. Make sure that the i386 and amd64 directories include thefollowing files: tpdibus.sys, tpd2xx.dll, winusbcoinstaller2.dll,wdfcoinstaller01009.dll, 
5. Open Device Manager 
6. Find the entry for the Total Phase Device in Other Devices or in USBControllers. 
7. Right-click on the Total Phase Device and select"Uninstall". 
8. Unplug and plug in the Total Phase device. 
9. Right-click on the Total Phase Device and select "Update Driver Software". 
10. Click Browse My Computer For Driver Software. 
11. Click Browse 
12. Select the folder “C:\Users[USERNAME]\AppData\Local\Temp”. 
13. Click OK. 
14. Click Next 
15. After installation completes, you should see the device show up in the USBControllers section of the Device Manager. Read Less

 

3, total phase 的 Data Center软件的简单实用????

1,打开 data cener软件如下图

2,如下图第一步连接totalphase设备;第二步设置capture

 

 

 

3,如下图第三步选择抓取usb3.0还是usb2.0;第四步设置过滤规则

4,如下图第五步开始抓捕

 

 

 

 

 

 

5,如下图第六步设置触发

6,如下图抓捕到的数据(令牌包,数据包,握手包,等usb物理协议层各种包)

五、Cypress Suite USB 3.4.2

20180528

 

网上资料:

一、Cypress USB驱动程序的几个版本

截至目前,CY网站上至少公布了三个版本的驱动程序,最后那个SuiteUSB也在更新,罗列几个如下:

1,早期EZUSB.SYS,这是给AN2131年代写的驱动程序,90年代公布,目前CY网站上已经删除。

2,后期CYUSB.SYS,这是给FX2和FX2LP写的驱动,是前者的升级版本。随着《CY3684 EZ-USB FX2LP开发套件》一起发布。

3,2009年开始的SuiteUSB 3.4.2,2011年的3.4.4,同时上述2项废止。全称叫“SuiteUSB 3.4 - USB Development tools for Visual Studio”,最新3.4.4公布时间2011年01月12日。

2和3可以统称为CYUSB,它和EZUSB的区别如下:

1,EZUSB的VID ,PID是04B4,1002,具体由ezusbw2k.inf指定。这就是所谓通用驱动GPD( General Purpose Drive),主机控制台叫EZ-USB Control Panel。主机采用WindowsAPI进行开发,使用标准IOCTL函数。它由安装CypressEZ-USBDevelopmentKit开发包得到,版本号是261700。

2,CYUSB的VID,PID是04B4,1004,具体由INF文件指定,对应的驱动应该是cyusb.sys,cyusbpre.inf,控制台使用Cypress USB Console。主机使用CyAPI函数,当然IOCTL也可用(但与老版本有变化,见CYUSB.PDF)。它由安装“CY3684 EZ-USB FX2LP 开发套件”获得。最近一次更新时间是2010 年 01 月 05 日,它的软件资料也指向了SuiteUSB,即下面第三条所列。

CYUSB包含2个文件:

cy3684_ez_usb_fx2lp_development_kit_15.exe,FX2和FX2LP开发板、演示、驱动等,缺省安装在c:\Cypress\USB目录下。 
cy3684_ez_usb_fx2lp_development_kit_17.zip,主要是GPIF工具、演示例程;缺省安装在C:\Program Files\Cypress\GPIF Designer目录下。 
3,SuiteUSB,上个版本是3.4.2,如今更新到3.4.4。它与CYUSB的差别在于,它支持.NET平台,可以使用C++、C#语言开发上层应用。硬件上支持64位操作系统。SuiteUSB的下载地址:http://china.cypress.com/?rID=34870。3.4.2版本缺省安装在C:\Program Files\Cypress,最新的3.4.4版本缺省安装在C:\Cypress\Cypress Suite USB 3.4.4。

64 bit support for Windows XP, Vista and 7 has been incorporated. 
Windows Driver Model (WDM) compliant 
WHQL Certified (not signed) 
Compatible with any USB 2.0 compliant device 
Supports Windows PnP and Power Management level S4 
Supports USB Remote Wake-up 
Supports Control, Bulk, Interrupt and Isochronous endpoints 
Supports multiple USB devices connected at once 
Supports customizable driver GUID without rebuilding the driver 
Supports high bandwidth data transfers passing multiple packets per uframe 
    从目前看,2和3都是可选的,随着Windows 7的推广,3会应用越来越广。如果使用VC6编程,只能选择2;如果使用C++、C#则选择3。我了解的信息是,2可以运行在 Windows 2000, Windows XP、Windows Vista 32位、Windows 7 32位操作系统,但不能运行于Vista 64位、Windows 7 64位版本。

二、Cypress Suite USB3.4.4介绍

cyusb.sys驱动程序

Cypress通用USB驱动程序

C#库:cyusb.dll

用于与cyusb.sys, usbhid.sys, usbstore.sys驱动程序通讯的类库

C++库:cyapi.lib

用于与cyusb.sys通讯的类库

CyControlCenter, CyConsole etc.

C# 与 C++ 演示代码

 可运行与下列操作系统和平台:

Windows 2000(w2K) 
Windows XP (wxp) 
Windows Vista (wlh) 
Windows 7   
CPU 类型:

x86(32bit-i386) 
x64(64bit-amd64) 
 3.4.4包含了C#与C++的例子,即,二者都支持。这就打消了USB驱动升级带来的向下兼容的疑虑。我们可以放心地使用新的CYUSB.SYS,不管是C#开发环境还是C++环境。

三、Cypress Suite USB 3.4.4目录结构

C:\Cypress\Cypress Suite USB 3.4.4目录下共有5个子目录,分别列出:

1,C:\Cypress\Cypress Suite USB 3.4.4\Firmware目录下有5个子目录:

子目录名

文件名

内容

Bin

Hex2bix.exe

Hex格式固件转换成IIC格式

Include

Fx2.h

fx2regs.h

fx2regs.inc

fx2sdly.h

syncdly.h

固件开发时用到的include文件

Lib

EZUSB.LIB

USBJmpTb.a51

USBJmpTb.OBJ

固件开发时用到的库函数

Bulkloop

bulkloop.c等

批量传输的例子

CyStreamer

CyStreamer.c等

Screamer、streamer一类流式程序加载的固件

 这些固件与之前的CYUSB版本的固件(前文第2项)并无太多差别。

2,C:\Cypress\Cypress Suite USB 3.4.4\CyAPI —— Cypress开发的USB支持函数

使用基于.NET 2.0 DLL库,CyAPI,软件开发人员可以快速建立一个与Cypress GPD cyusb.sys通讯的应用。cyusb.sys是通用驱动程序,经Windows logo确认。最初是在Studio 2005 和 .NET 2.0/3.0平台上开发的。

C:\Cypress\Cypress Suite USB 3.4.4\Firmware目录下有5个子目录和2个文件:

子目录名

文件名

内容

Examples\ cybulk

若干

批量传输的C++示例

Examples\cydesc

若干

读取设备描述符的C++示例

Examples\FxEEPROM

若干

BC++示例

Examples\Streamer

若干

测试端点吞吐量的C#示例,支持同步和批量传输

Inc

CyAPI.h

cyioctl.h

usb100.h

usb200.h

头文件

Lib\ BC6

CyAPI.lib

BC++函数库

LIB\x64

CyAPI.lib

64位C函数库

Lib\x86

CyAPI.lib

32位C函数库

 

CyAPI.chm

CyAPI文档

 

CyAPI.pdf

CyAPI文档

 

 

CypressUSB device driver安装??

 

CypressUSB console的使用????

 

六、alstranlive项目的transmission模块无法通过ep0 out大于64字节的数据。

       Alstranlive项目的transmission模块使用rv1108的otg 做usb device。

       根本原因:

1>transmission模块在控制传输 OUT的usb建立连接阶段结束准备数据阶段接收数据的存储空间时:通过v4l2框架调用iotcl的UVCIOC_SEND_RESPONSE命令时,传输的长度不对(异常点: 长度较小固定为10),导致uvc_v4l2.c的uvc_send_response函数调用usb_ep_queue提交usb_request的时候提交的usb_request.len长度不对(异常点: 长度较小固定为10),导致dwc_otg_driver在进行控制传输的OUT的数据阶段,实际传输的字节数远小于预定的字节数。代码如下:

Linux kernel 的uvc_v4l2.c中相关代码如下:

 

       由上图usb_request的length为uvc->event_length与data->length两者字节的较小值。

data->length为transmission模块通过v4l2框架调用iotcl的UVCIOC_SEND_RESPONSE的命令传递下来的,这个长度应该为: ep0控制传输的建立连接阶段的数据包中usb请求的控制传输的数据阶段是OUT(以usb host为参考)的时候,为usb请求OUT的实际长度,既usb_ctrlrequestwlength

            ep0控制传输的建立连接阶段的数据包中usb请求的控制传输的数据阶段是IN(以usb host为参考)的时候,为实际准备好要输入到usb host的数据的长度。

uvc->event_length的值如下:

 

 

     由上图可知当V4L2的事件类型为控制传输的建立阶段收到usb host的usb请求时;uvc->event_length为usb_ctrlrequet的wlength,既为usb 请求的 控制传输的数据阶段要传输的字节数。

2>当transmission模块与主机通过usbep0控制传输的时候,uvc_event是通过v4l2_event在ep0的一次控制传输完成时送到应用层的,代码如下linux kernel的f_uvc.c:

 

 

       但是uvc_event->data.data缓冲与v4l2_event.u.data缓冲只有64Byte,因此需要将这两个缓冲都改大以便满足在控制传输的数据阶段传输的数据长度。因此做如下修改:

linux kernel的uvc.h:

  

   

linux kernel的videodev2.h

因为alstraliv项目的transmission模块在编译的时候包含了

/home/davidliu/56_rv1108_alstralive/AlstraliveApp/platform/repo/prebuilts/toolschain/usr/arm-rkcvr-linux-uclibcgnueabihf/sysroot/usr/include/linux/videodev2.h, 因此也需要对这个videodev2.h做修改,修改点与上图一致。

/home/davidliu/56_rv1108_alstralive/AlstraliveApp/platform/repo/prebuilts/toolschain/usr/arm-rkcvr-linux-uclibcgnueabihf/sysroot/usr/include/ 这个路径为交叉编译工具链的平台头文件路径。

附录

一, 关于循环FIFO与 linux kenel 用struct list_head 实现queue(或者用自定义链表实现的queue)的思考?

   循坏FIFO有个覆盖的问题,但是queue没有此问题(链表上有元素就进行处理,没有就不处理)

   循环FIFO指的是自己在一电的WH20项目解码应用中实现循环FIFO方法,及自己在STM32数传软件中的串口收发部分使用DMA实现的循环FIFO方法(一次fifoout取出FIFO中所有可取的元素)和RF send部分实现的FIFO方法(一次fifo out只取一个FIFO元素)。

       用struct list_head 实现的queue,或者自定义链表实现的queue(自定义链表实现的queue可以参考dwc_otg_driver(linux_kernel_top_dir/drivers/usb/gadget/目录下,也有可能在linux_kernel_top_dir/drivers/usb/目录下)的DWC_CIRCLEQ_INIT_ENTRY, DWC_CIRCLEQ_INSERT_TAIL等关于queue的其他操作的实现, 一次queue只取一个queue元素, 向queue提交元素的时候一次也只提交一个元素)

 

  • 3
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值