第二十四篇:SuperSpeed/HighSpeed USB的ISO传输

WDK 7600.16385.1中关于USB ISO传输的驱动例子, 是针对于USB2.0与USB1.1的, 即HS与FS.

这个例子没有非常特别之处, 但对于USB2.0的ISO传输的数据分割算法, 还是挺有意思的.


根据MSDN参考文章:

http://msdn.microsoft.com/en-us/library/windows/hardware/hh406225.aspx

<<How to transfer data to USB isochronous endpoints>>

USB2.0 ISO传输需要根据iso ep的interval值的不同而变化,
第一, 如下表
Interval Polling period (2Interval-1)
1 1; Data is transferred every bus interval.
2 2; Data is transferred every second bus interval.
3 4; Data is transferred every fourth bus interval.
4 8; Data is transferred every eighth bus interval.
对于FULL SPEED, polling period一直为一个FRAME一次.

第二, 如下表, 对于不同的polling period, 传输包的个数也有限制:
Polling period Number of Packets for high speed/SuperSpeed
1 Multiple of 8
2 Multiple of 4
3 Multiple of 2
4 Any

第三, 对于一个URB, iso packet的限制
full speed: 255
high/super speed: 1024

于是, 就是催生的WDK 7600.16385.1中USB2.0 ISO传输的一个算法:
不过在看这个算法前, 提及几个概念:
1. bus interval
 Full speed: 1ms, frame, High/Super speed: 125us, microframe

2. Windows中USB packet的概念
packet表示在一个bus interval中传输数据的量.
它的决定因素, 是usb设备端的ep描述符决定的.
对于full speed, 每个bus interval(frame)传输一个packet, 该packet对应于一个iso transaction
high speed, 每个bus interval(micro frame)最多可以传输 3*1024 的packet
super speed, 这个能力的表达由ep描述符与ep companion描述符来描述: 最多可以达到3*16*1024/microframe

如何得到某个ep packet size:
MaximumPacketSize 成员存在于两个数据结构中:USBD_PIPE_INFORMATION与WDF_USB_PIPE_INFORMATION.


MaximumPacketSize的来源:
对于FULL SPEEC EP来讲, MaximumPacketSize来自于EP描述符的wMaxPacketSize低11位
HIGH SPEED: EP描述符的wMaxPacketSize的第11,12位表示,"附加"的transaction的个数, 可以为0, 1, 2
SUPER SPEED: USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR中成员起了作用:
Mult: 表示附加的busrt的个数, 可以为0,1,2
bMaxBurst : burst的数量, 这是针对于USB3.0的新概念, 可以为0-15 (表示1-16个burst)
wBytesPerInterval : 必须小于或等于(bMaxBurst+1) * (Mult+1) *wMaxPacketSize
  • wBytesPerInterval indicates the total number of bytes that the host can send or receive in a bus interval. Even though the maximum number of bytes per bus interval can be calculated as (bMaxBurst+1) * (Mult+1) *wMaxPacketSize, the USB 3.0 specification recommends using the wBytesPerInterval value instead. 
  • ThewBytesPerInterval value must be less than or equal to that calculated value.
事实上, SUPER SPEED的 MaximumPacketSize就是wBytesPerInterval.

由于WINDOWS USB STACK已经帮上层USB客户驱动设置好了 MaximumPacketSize, 所以, 它会提出:
Important   
For a client driver the values described in the preceding is for information only. 
The driver must always use the MaximumPacketSize value of the endpoint descriptor to determine the layout of the transfer buffer.
此处所谓的layout of the transfer buffer, 我的理解, 就是如何将buffer拆分成一个一个的iso packet.

文章中出现: 请注意: iso packet是可以小于MaximumPacketSize的, 但事实上, 如果iso packet小于MaximumPacketSize, 就会出现后面列出的问题.
The MaximumPacketSize value indicates the maximum permitted size of the isochronous packet. The client driver can set the size of each isochronous packet to any value less than the MaximumPacketSize value.

现在来看看这个算法:
首先, Windows Driver Kit (WDK) 8.1 Samples 已经将USBSamp例子中的这个算法给去掉了, 新的例子代码, 只要发现不符合Number of Packets for high speed/SuperSpeed的要求, 就退出了.

所以, 从某种意义上来讲, 微软最新的代码, 并非比早期版本的代码更"好".

1.  得到包的大小
packetSize = pipeInfo.MaximumPacketSize;

2. 未圆整前包的个数
    numberOfPackets = (TotalLength + packetSize - 1) / packetSize;

3. 将包的个数, 向上圆整到符合Number of Packets for high speed/SuperSpeed的要求, 这里以interval 1, 8 packets/bus interval为例

    if(0 == (numberOfPackets % 8)) {


        actualPackets = numberOfPackets;
    }
    else {


        //
        // we need multiple of 8 packets only.
        //
        actualPackets = numberOfPackets +
                        (8 - (numberOfPackets % 8));
    }

4. 得到实际平均每个包中的数据量
    minDataInEachPacket = TotalLength / actualPackets;


5. 如果实际平均每个包的数据量 == packetSize, 说明实际包的个数, 并没有因为第3步, 而有所增加
所以, numberOfPacketsFilledToBrim变量(表示包中数据量等于packetsize的包的个数), 就等于实际包的数量, 即所有包都是满包.
dataLeftToBeDistributed数据也为0, 因为正好所有的包都是满包.

    if(minDataInEachPacket == packetSize) {


        numberOfPacketsFilledToBrim = actualPackets;
        dataLeftToBeDistributed     = 0;

    }
    else {

6. 由于第三步, 实际包的个数比第二步算出来的个数, 有所增加, 所以, minDataInEachPacket小于packetSize.
numberOfPacketsFilledToBrim还是满包的个数
dataLeftToBeDistributed表示, 不能达到满包的那个包, 比minDataInEachPacket多出的数据量

        dataLeftToBeDistributed = TotalLength -
                              (minDataInEachPacket * actualPackets);


        numberOfPacketsFilledToBrim = dataLeftToBeDistributed /
                                  (packetSize - minDataInEachPacket);


        dataLeftToBeDistributed -= (numberOfPacketsFilledToBrim *
                                (packetSize - minDataInEachPacket));

    }

之后, 在计算每一个stage中的数据量的时候: (每一个irp/urb pair 小于1024个iso packet):

if(nPackets > numberOfPacketsFilledToBrim) {

1. 包的个数nPackets中, 满包的有numberOfPacketsFilledToBrim个, 而且, 有一个包, 是minDataInEachPacket+dataLeftToBeDistributed, 剩余包的数据量为minDataInEachPacket

            stageSize =  packetSize * numberOfPacketsFilledToBrim;
            stageSize += (minDataInEachPacket *
                          (nPackets - numberOfPacketsFilledToBrim));
            stageSize += dataLeftToBeDistributed;
        }
        else {

2. 所有包均为满包:
            stageSize = packetSize * nPackets;
        }

实际的例子:

Algorithm at play:


    TotalLength  = 8193
    packetSize   = 8
    Step 1


    Step 2
    numberOfPackets = (8193 + 8 - 1) / 8 = 1025


    Step 3
    actualPackets = 1025 + 7 = 1032


    Step 4
    minDataInEachPacket = 8193 / 1032 = 7 bytes


    Step 5
    dataLeftToBeDistributed = 8193 - (7 * 1032) = 969.


    Step 6
    numberOfPacketsFilledToBrim = 969 / (8 - 7) = 969.


    Step 7
    dataLeftToBeDistributed = 969 - (969 * 1) = 0.



最后, 说一下这个算法的问题:

实际运行过程中, 会导致 babble error, 所以, 最后, 笔者将USBSamp的算法改成另外一种.

比如, 如果一个ISO IN EP的MAX PACKET SIZE = 768, 且其extra transaction 是2, 刚其MaximumPacketSize为768*3 = 2304.

为了符合interval =1, 包的个数为8的倍数的要求, 刚以18432为读写请求:

结果如下:

1. request 18432, actual read 18432 

8 packets, each 2304 bytes, 每个ISO PAKCET的OFFSET也均为2304的整数倍

2. request 18431, actual read 17664 

最后一个transaction的767 bytes没有收到, 最后一个PACKET收到1536 bytes

7 packets 2304, one packet 2303,每个ISO PAKCET的OFFSET也均为2304的整数倍

最后一个iso packet USBD_STATUS = USBD_STATUS_BABBLE_DETECTED 0xC0000012

这里的BABBLE ERROR, 应该就是buffer只有767 bytes, 但DEVICE回了768 bytes所导致的.


IsoPacket[0].offset = 0 IsoPacket[0].Length = 2304 IsoPacket[0].Status = 0
UsbSamp: 
IsoPacket[1].offset = 2304 IsoPacket[1].Length = 2304 IsoPacket[1].Status = 0
UsbSamp: 
IsoPacket[2].offset = 4608 IsoPacket[2].Length = 2304 IsoPacket[2].Status = 0
UsbSamp: 
IsoPacket[3].offset = 6912 IsoPacket[3].Length = 2304 IsoPacket[3].Status = 0
UsbSamp: 
IsoPacket[4].offset = 9216 IsoPacket[4].Length = 2304 IsoPacket[4].Status = 0
UsbSamp: 
IsoPacket[5].offset = 11520 IsoPacket[5].Length = 2304 IsoPacket[5].Status = 0
UsbSamp: 
IsoPacket[6].offset = 13824 IsoPacket[6].Length = 2304 IsoPacket[6].Status = 0
UsbSamp: 
IsoPacket[7].offset = 16128 IsoPacket[7].Length = 1536 IsoPacket[7].Status = c0000012



3. request 18433, actual read 0 

16个PACKETS

一个1153 BYTES, 15个1152 BYTES

OFFSET为:

urb header status C0000B00
UsbSamp: 
subReqContext
UsbSamp: 
IsoPacket[0].offset = 0 IsoPacket[0].Length = 768 IsoPacket[0].Status = c0000011
UsbSamp: 
IsoPacket[1].offset = 1153 IsoPacket[1].Length = 768 IsoPacket[1].Status = c0000011
UsbSamp: 
IsoPacket[2].offset = 2305 IsoPacket[2].Length = 768 IsoPacket[2].Status = c0000011
UsbSamp: 
IsoPacket[3].offset = 3457 IsoPacket[3].Length = 768 IsoPacket[3].Status = c0000011
UsbSamp: 
IsoPacket[4].offset = 4609 IsoPacket[4].Length = 768 IsoPacket[4].Status = c0000011
UsbSamp: 
IsoPacket[5].offset = 5761 IsoPacket[5].Length = 768 IsoPacket[5].Status = c0000011
UsbSamp: 
IsoPacket[6].offset = 6913 IsoPacket[6].Length = 768 IsoPacket[6].Status = c0000011
UsbSamp: 
IsoPacket[7].offset = 8065 IsoPacket[7].Length = 768 IsoPacket[7].Status = c0000011
UsbSamp: 
IsoPacket[8].offset = 9217 IsoPacket[8].Length = 768 IsoPacket[8].Status = c0000011
UsbSamp: 
IsoPacket[9].offset = 10369 IsoPacket[9].Length = 768 IsoPacket[9].Status = c0000011
UsbSamp: 
IsoPacket[10].offset = 11521 IsoPacket[10].Length = 768 IsoPacket[10].Status = c0000011
UsbSamp: 
IsoPacket[11].offset = 12673 IsoPacket[11].Length = 768 IsoPacket[11].Status = c0000011
UsbSamp: 
IsoPacket[12].offset = 13825 IsoPacket[12].Length = 768 IsoPacket[12].Status = c0000011
UsbSamp: 
IsoPacket[13].offset = 14977 IsoPacket[13].Length = 768 IsoPacket[13].Status = c0000011
UsbSamp: 
IsoPacket[14].offset = 16129 IsoPacket[14].Length = 768 IsoPacket[14].Status = c0000011
UsbSamp: 
IsoPacket[15].offset = 17281 IsoPacket[15].Length = 768 IsoPacket[15].Status = c0000011


其中c0000011为USBD_STATUS_XACT_ERRO, C0000B00为USBD_STATUS_ISOCH_REQUEST_FAILED


如果, 再改变一下APP的请求长度, 比如7*2304 = 16128

结果如下:

1. request 16128, read 0 bytes

8 packets, each 2016 bytes

urb header status C0000B00

IsoPacket[0].offset = 0 IsoPacket[0].Length = 1536 IsoPacket[0].Status = c0000012
UsbSamp: 
IsoPacket[1].offset = 2016 IsoPacket[1].Length = 1536 IsoPacket[1].Status = c0000012
UsbSamp: 
IsoPacket[2].offset = 4032 IsoPacket[2].Length = 1536 IsoPacket[2].Status = c0000012
UsbSamp: 
IsoPacket[3].offset = 6048 IsoPacket[3].Length = 1536 IsoPacket[3].Status = c0000012
UsbSamp: 
IsoPacket[4].offset = 8064 IsoPacket[4].Length = 1536 IsoPacket[4].Status = c0000012
UsbSamp: 
IsoPacket[5].offset = 10080 IsoPacket[5].Length = 1536 IsoPacket[5].Status = c0000012
UsbSamp: 
IsoPacket[6].offset = 12096 IsoPacket[6].Length = 1536 IsoPacket[6].Status = c0000012
UsbSamp: 
IsoPacket[7].offset = 14112 IsoPacket[7].Length = 1536 IsoPacket[7].Status = c0000012


2. request 16129, read 0 bytes

1 packet 2017, 7 packet 2016

urb header status C0000B00
UsbSamp: 
subReqContext
UsbSamp: 
IsoPacket[0].offset = 0 IsoPacket[0].Length = 1536 IsoPacket[0].Status = c0000012
UsbSamp: 
IsoPacket[1].offset = 2017 IsoPacket[1].Length = 1536 IsoPacket[1].Status = c0000012
UsbSamp: 
IsoPacket[2].offset = 4033 IsoPacket[2].Length = 1536 IsoPacket[2].Status = c0000012
UsbSamp: 
IsoPacket[3].offset = 6049 IsoPacket[3].Length = 1536 IsoPacket[3].Status = c0000012
UsbSamp: 
IsoPacket[4].offset = 8065 IsoPacket[4].Length = 1536 IsoPacket[4].Status = c0000012
UsbSamp: 
IsoPacket[5].offset = 10081 IsoPacket[5].Length = 1536 IsoPacket[5].Status = c0000012
UsbSamp: 
IsoPacket[6].offset = 12097 IsoPacket[6].Length = 1536 IsoPacket[6].Status = c0000012
UsbSamp: 
IsoPacket[7].offset = 14113 IsoPacket[7].Length = 1536 IsoPacket[7].Status = c0000012


3. 

request 16127, read 0 bytes

1 packet 2022, 7 packet 2015

urb header status C0000B00
UsbSamp: 
subReqContext
UsbSamp: 
IsoPacket[0].offset = 0 IsoPacket[0].Length = 1536 IsoPacket[0].Status = c0000012
UsbSamp: 
IsoPacket[1].offset = 2022 IsoPacket[1].Length = 1536 IsoPacket[1].Status = c0000012
UsbSamp: 
IsoPacket[2].offset = 4037 IsoPacket[2].Length = 1536 IsoPacket[2].Status = c0000012
UsbSamp: 
IsoPacket[3].offset = 6052 IsoPacket[3].Length = 1536 IsoPacket[3].Status = c0000012
UsbSamp: 
IsoPacket[4].offset = 8067 IsoPacket[4].Length = 1536 IsoPacket[4].Status = c0000012
UsbSamp: 
IsoPacket[5].offset = 10082 IsoPacket[5].Length = 1536 IsoPacket[5].Status = c0000012
UsbSamp: 
IsoPacket[6].offset = 12097 IsoPacket[6].Length = 1536 IsoPacket[6].Status = c0000012
UsbSamp: 
IsoPacket[7].offset = 14112 IsoPacket[7].Length = 1536 IsoPacket[7].Status = c0000012


综上实验所示, 只要一个iso packet的buffer size小于ep 的MaximumPacketSize, 则ISO就会产生BABBLE ERROR.

但该结论只适用于ISO IN, 对于ISO OUT, 该规则不适用.



到目前为止, 或许只需要将新的ISO IN OFFSET的LAYOUT办法给出来, 就可以解决该问题.
但在此之前, 我们可以利用USB ANALYZER( Lecory 的 Advisor T3), 将问题看得更加深入一点, 真正地了解, USBD_STATUS_BABBLE_DETECTED与USBD_STATUS_XACT_ERRO分别针对于哪一种现象:
从USB TRACE来看, 所有的 USBD_STATUS_BABBLE_DETECTED, 都是ISO DATA2 768, DATA1 768, DATA0 768, 3 TRANSACTIONS的模式, 也非常容易理解这个BABBLE的情况. 如下图:

第二十四篇:SuperSpeed/HighSpeed USB的ISO传输_第1张图片
USBD_STATUS_XACT_ERRO则不同: 它是规则地以 DATA2 768, DATA 1 768, 与DATA 2 768, DATA 1 0, 2 TRANSACTION进行交替的.


似乎, 从3 TRANSACTIONS的EP来讲, 如果数据达到了2个 TRANSACTIONS, 则为BABBLE, 如果只达到了一个,甚至更少, 则为XACT. 当然, 这只是笔者根据这几个情况得出的结论, 正确性有待考证.


最后, 笔者将该算法去除, 在符合polling period = 1, 2, 4, 8对packets(1, 2, 4, 8)的要求的情况下, 能够接受任意长度的ISO IN传输.

具体实现为:

所有的OFFSET设置为MaximumPacketSize的整数倍, 在SYS空间中申请一块NON PAGED POOL, 这块BUFFER的长度, 向上圆整到MaximumPacketSize的整数倍(如8*n, 4*n, 2*n, 1*n倍), ISO IN 的数据先存放在SYS空间的这块BUFFER中, 安排一个WORK ITEM, 在WORK ITEM中, 最后将数据COPY到用户空间的BUFFER中, COPY长度为用户请求的长度, 这样解决了BABBLE ERROR的问题(事实上, 也同时解决了XACT ERROR的问题). 


你可能感兴趣的:(算法,usb)