第二篇:欲善其事,先利其器-USB3.0 Kernel debug extension

从第一份工作,主要从事USB dongle PCTV AVStream/BDA Windows驱动的开发,到现在从事USB3.0 device/xHCI host IP的开发,我的工作内容中,始终离不开一个词:通用串行接口--USB。


所以,我的第一篇技术博文,也从USB开始谈起。


USB3.0 IP的开发包括两大部分.

第一大部分是 遵照<<Universal Serial Bus 3.0 Specification>>协议的USB3.0 device IP:
在开发这一款IP的过程中,作为软件工程师,我们经历了以下一些过程:

第一步,当然是对Spec的Cross Training
由于USB3.0较USB2.0/1.1/1.0有了本质的变化,它主要体现地Link Layer,尤其是其中的LTSSM(Link Training and Status State Machine), Physical Layer中的LFPS(Low Frequency Periodic Signaling), Protocol layer的Burst, Bulk Streaming, 以及Power Management,它在前面三个层面的均有协议设计上实实在在的体现.
这些重点与难点,也是IP开发前期花最多时间的,例如,几位同事花了非常多的时间用来仔细分析USB Analyzer抓下的各种存在可疑点的LFPS.
好多底层软件工程师的观念中根深蒂固地认为,软件工程师,只需要写出好的软件,对于软件工程师去分析USB analyzer, PCIe analyzer抓到的数据,有种不值得一提的态度。
这种想法大错特错,底层软件与应用层软件,有着本质的区别,如何体现你作为底层软件工程师的特点,就在于做到这些应用层软件工程师不能作的事情。


第二步,先将USB device的default control Pipe-EP0的基本功能搞定
EP0承载着USB device的枚举(Enumeration)与配置(Configuration)任务,一个USB device可以没有(Bulk,ISO,Interrupt)类型的EP,但绝对不能没有USB control EP.
在这个过程中,就非常需要一个通用的USB device functional driver. 摆在我们面前,有三个方案可以选择,他们是WinUSB, osrusbfx2,以及usbsamp.经过对三个方案的比较,最终选择了usbsamp,因为,前面两者均不支持ISO传输,对于后期对ISO传输的调试需要再另外再建立一份驱动代码是不能接受的,而且WinUSB不包含源代码,这就阻碍了我们根据需要修改驱动程序的想法.
在这一时间节点(2011.6),我们的工程师第一次参加了位于美国 Portland的USB-IF的workshop,


第三步:增加对Bulk传输的支持
USB3.0目前有几大应用,第一类就是存储,第二类是音视频,至于USB HID,Network目前的USB2.0,1.1,1.0已经够用.
首先,利用USB device functional driver的Bulk Loopback功能对IP的Bulk In/out功能进行验证.
另外,软件工程师需完成一个依托于USB3.0 IP的存储设备(Mass storage),其主要工作就是编写基于ARM的Firmware, 以便使带有USB3.0 xHCI host的PC系统能够将设备枚举为一个USB storage device.
在这一时间节点(2011.9 & 2011.11),我们软件工程师又参加了两次USB-IF的官方测试,其中一次是workshop,另外一次是Lab.


第四步:与第三步类似,但增加的是ISO传输与Bulk Streaming/UAS的支持
软件工程师编写符合USB Audio class,UAS的firmware.
由于有了前三次的经验总结与教训,这一次的USB-IF官方测试(2013.5)比较顺利.


第二部分,便是 遵循<<eXtensible Host Controller Interface for Universal Serial Bus>>协议的 USB3.0 HOST IP.
作为USB subsystem, HOST IP的开发工作负载明显地大于DEVICE IP.对于软件工程师来讲,主要工作在于探寻IP不符合xHCI spec中的逻辑.
在IP不成熟时期(问题太多导致,根本不能在PC Windows驱动上跑),移植Linux的xHCI驱动到ARM平台上.
在IP可以运行在Windows 8/8.1的xHCI驱动上时,我们的工作任务就更加明显地增加了,因为HOST IP与DEVICE IP的测试要求不同,HOST需要与市面上的150个HS DEVICE进步兼容性测试,之后最难的就是以HOST IP为测试目标的Golden tree/InterOp test, 后期也对HOST IP进行了WHQL/WHCK(Windows Hardware Certification)测试.


就是在这个Host InterOP/Golden tree Test过程中,在Windows 8/8.1系统出现了一系列的BSOD,需要分析.
我拿到其中一个BSOD DUMP FILE就是我的这篇博文将要展现给大家的对它的分析过程:


使用!analyze -v命令,如下(部分输出内容省略)

0: kd> !analyze -v DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1) Arguments: Arg1: ac6e2f68, memory referenced Arg2: 00000002, IRQL Arg3: 00000000, value 0 = read operation, 1 = write operation Arg4: 8ddcbcbf, address which referenced memory Debugging Details: ------------------ READ_ADDRESS: ac6e2f68 Special pool CURRENT_IRQL: 2 FAULTING_IP: USBXHCI!TransferRing_TransferEventHandler+403 8ddcbcbf 8b00 mov eax,dword ptr [eax]

TRAP_FRAME: 810f38c4 -- (.trap 0xffffffff810f38c4) ErrCode = 00000000 eax=ac6e2f68 ebx=00000000 ecx=0f048005 edx=ac6e2f68 esi=00000000 edi=ad625d10 eip=8ddcbcbf esp=810f3938 ebp=810f3990 iopl=0 nv up ei pl zr na pe nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246 USBXHCI!TransferRing_TransferEventHandler+0x403: 8ddcbcbf 8b00 mov eax,dword ptr [eax] ds:0023:ac6e2f68=???????? Resetting default scope STACK_TEXT: 810f38a4 81f9dd9b 0000000a ac6e2f68 00000002 nt!KiBugCheck2 810f38a4 8ddcbcbf 0000000a ac6e2f68 00000002 nt!KiTrap0E+0x1b3 810f3990 8ddc303e ad625d10 00000000 8b8c6ee8 USBXHCI!TransferRing_TransferEventHandler+0x403 810f39fc 8589b49a 4e5871a0 71563188 00000000 USBXHCI!Interrupter_WdfEvtInterruptDpc+0x31e ---------

从!analyze -v的输出,可以看出:

CPU在读访问属于special pool区域的 ac6e2f68 这一内存虚拟地址的时候,产生了Bugcheck, 当时的的IRQL为2:DPC_LEVEL.
通过 ac6e2f68 =????????的 这一串?,初步判断,系统在DPC_LEVEL访问了已经被换出(paged out)的分页内存.
使用!pool !pte对这一地址进行确认,也确实如此:

0: kd> !pool ac6e2f68 Pool page ac6e2f68 region is Special pool Address ac6e2000 does not belong to any pool ac6e2000: Unable to get contents of special pool block

0: kd> !pte ac6e2f68 VA ac6e2f68 PDE at C0602B18 PTE at C0563710 contains 000000002F2EA863 contains 0008204600000000 pfn 2f2ea ---DA--KWEV not valid PageFile: 0 Offset: 82046 Protect: 0


现在的问题就是,为什么CPU会在DPC_LEVEL去访问一块已经被换出的内存地址呢?是Windows 8的native xHCI USB3.0 host driver存在问题,还是我们的xHCI host IP存在问题呢?
最后归结为,这一地址,是谁给出来的,为什么要去访问这一地址?
这个时候,我看了一下stack trace,想从中得出一些启发,  
USBXHCI!TransferRing_TransferEventHandler+0x403
这个STACK中的FRAME,让我自然而然地联想到,这个问题是不是发生在Transfer Event产生之时,DRIVER处理之季?




说到这里,需要简单介绍一下,xHCI的工作流程:
xHC有三条类型的操作队列,一条是整个HOST的命令队列(command ring),一条是每一个EP对应的传输队列(transfer ring),还有一条是整个HOST的事件队列(event ring). 与这个问题相关的是后面两条.
软件作为生产者(这里就是Windows8 xHCI HOST DRIVER)将需要传输的内容以TRB(transfer request block)的形式放入transfer ring. xHC host作为消费者,取出这些TRB,进行相应的数据传输操作.
在xHC host执行完相应的操作,无论成功还是失败,它将作为生产者,向event ring中放入一个TRB, 作为向软件对一笔传输事务的结果报告,软件在相应中断产生后的DPC处理中,作为消费者从event ring中取出该TRB,进行相应的处理.
USBXHCI! TransferRing_TransferEventHandler 就是 DPC阶段所调用的event ring/TRB的处理函数.
接下来,如何办呢?
正好前阵子在微软Dev Center-Hardware中看到Windbg中对USB3.0调试的扩展功能:
http://msdn.microsoft.com/en-US/library/windows/hardware/hh869258(v=vs.85).aspx
迅速把相关内容过了一篇,之后马上使用.
第一个命令是使用!usb_tree,显示了当前系统中的USB Topology结构,但是没有我需要的event ring的内容.
第二个命令,!xhci_dumpall
非常好,输入内容中,列出了当前系统,所有xHC下的event ring.


根据PCI: VendorId,找到我们开发的xHCI host IP.
使用DML,直接输出event ring中的内容:
是不是可以看到,!analyze -v所输出的一些关键数字与event ring中的是相同的?


eax= ac6e2f68  <---> 207,209 TRANSFER_EVENT
edx= ac6e2f68  <---> 207,209 TRANSFER_EVENT
edi= ad625d10  <---> 209 TRANSFER_EVENT


0: kd> !usb3kd.xhci_eventring 0x8ea9cff0

Dumping dt _PRIMARY_INTERRUPTER_DATA b8668fc0
-----------------------------------------------------

[0] Interrupter : dt _INTERRUPTER_DATA 0xb1a78f68 !rcdrlogdump USBXHCI -a 0x8e126008
------------------------------------------------------------------------------------------------------
DequeueSegment: 3 DequeueIndex: 210 TotalEventRingSegments: 4 TRBsPerSegment: 256
CurrentBufferData : VA 0xad625000 LA 0xd7daf000 Size 4096
EventRingTableBufferData : VA 0xad631000 LA 0xd7dac000 Size 512

[0] VA 0xad61c000 LA 0xd7dbe000 Size 4096
[1] VA 0xad61f000 LA 0xd7dbb000 Size 4096
[2] VA 0xad622000 LA 0xd7db2000 Size 4096
[3] VA 0xad625000 LA 0xd7daf000 Size 4096

Event Ring TRBs:
[200] TRANSFER_EVENT 0xad625c80 CycleBit 1 SlotId 24 EndpointID 3 ED 1 Data 0x007f7e80007b0001 PacketId 123 Frame 0x7f7e80 CC_SHORT_PACKET
[201] TRANSFER_EVENT 0xad625c90 CycleBit 1 SlotId 24 EndpointID 3 ED 1 Data 0x007f7e80007c0001 PacketId 124 Frame 0x7f7e80 CC_SHORT_PACKET
[202] TRANSFER_EVENT 0xad625ca0 CycleBit 1 SlotId 24 EndpointID 3 ED 1 Data 0x007f7e80007d0001 PacketId 125 Frame 0x7f7e80 CC_SUCCESS
[203] TRANSFER_EVENT 0xad625cb0 CycleBit 1 SlotId 24 EndpointID 3 ED 1 Data 0x007f7e80007e0001 PacketId 126 Frame 0x7f7e80 CC_SHORT_PACKET
[204] TRANSFER_EVENT 0xad625cc0 CycleBit 1 SlotId 13 EndpointID 18 ED 1 Data 0x007f7e8d00030001 PacketId 3 Frame 0x7f7e8d CC_SUCCESS
[205] TRANSFER_EVENT 0xad625cd0 CycleBit 1 SlotId 24 EndpointID 3 ED 1 Data 0x007f7e80007f0001 PacketId 127 Frame 0x7f7e80 CC_SHORT_PACKET
[206] TRANSFER_EVENT 0xad625ce0 CycleBit 1 SlotId 13 EndpointID 4 ED 1 Data 0x00000000b5310f68 BytesTransferred 65536 CC_SUCCESS
[207] TRANSFER_EVENT 0xad625cf0 CycleBit 1 SlotId 15 EndpointID 4 ED 1 Data 0x00000000ac6e2f68 BytesTransferred 31 CC_SUCCESS
[208] TRANSFER_EVENT 0xad625d00 CycleBit 1 SlotId 13 EndpointID 3 ED 1 Data 0x007f7e9000000001 PacketId 0 Frame 0x7f7e90 CC_SHORT_PACKET
[209] TRANSFER_EVENT 0xad625d10 CycleBit 1 SlotId 15 EndpointID 4 ED 1 Data 0x00000000ac6e2f68 BytesTransferred 31 CC_SUCCESS
>>>>[210] TRANSFER_EVENT 0xad625d20 CycleBit 1 SlotId 24 EndpointID 3 ED 1 Data 0x007f7e9000000001 PacketId 0 Frame 0x7f7e90 CC_SUCCESS
[211] TRANSFER_EVENT 0xad625d30 CycleBit 1 SlotId 18 EndpointID 13 ED 1 Data 0x007f7e8a00060001 PacketId 6 Frame 0x7f7e8a CC_SHORT_PACKET
[212] TRANSFER_EVENT 0xad625d40 CycleBit 1 SlotId 24 EndpointID 5 ED 1 Data 0x007f7e9000000001 PacketId 0 Frame 0x7f7e90 CC_SHORT_PACKET



问题就在这里,为什么会有两个内容相同的event tring中的TRB?
Why?
应该问xHC hardware IP, 为什么要将两个内容相同的TRB放入到event ring?


有些人会问,为什么不可以?
答案是:
第一:Slot ID为15的usb device是一个USB MASS STORAGE设备,而且符合BOT协议,31个字节就是BULK OUT的CBW.有一个31字节的CBW,就应该有一个13字节的CSW(但是在另外一个BULK IN EP的ring中),而不是再一次31 字节的CBW.


第二:通过ED标志,得出该event TRB是由Transfer ring中event data TRB产生的, Event ring中的TRB会将event data trb中的一个TAG复制过去,往往一个完整的BOT传输,他们的三个event data trb中的TAG值是相同的.
如果说,这第二个31字节不是CBW,而是DATA OUT,倒是可以说通,在这种情况下,确实有可能在EVENT RING当中存在三个相同的EVENT TRB.
那么假设31是DATA OUT, 但为什么CBW(31B)+(EVENT DATA TRB  ac6e2f68 tag)-->DATA OUT( x BYTES)+(EVENT DATA TRB  ac6e2f68 tag )-->CSW(13B) + (EVENT DATA TRB  ac6e2f68 tag),当x不为31的时候,不产生这个问题?只有在x为31的时候才产生?
而且,后面的分析你将看到,该CBW所对应的是一个DATA IN,而非OUT,以假设与不符.


第三:通过检查 Slot ID为15,EP4的TRANSFER RING,EVENT RING中的每一个TRB.
前面的EVENT DATA TRB与EVENT TRB是一一对应的,当到了 0xac6e2f68 存在一对二的时候,(即硬件错误产生两个EVENT TRB)的时候,系统产生了BSOD.

这是论证过程中,最可以证明的一个论点,所以列出详细步骤:
第一:
!xhci_deviceslots 0x8ea9cff0
第二:
通过找到Slot 15, EP4,
[4] : dt USBXHCI!_ENDPOINT_DATA 0xb5088f58 dt _ENDPOINT_CONTEXT32 0xad7d3080 ES_RUNNING
    ------------------------------------------------------------------------------------------
        EndpointType_BulkOut Address: 0x2 PacketSize: 1024 Interval: 0
        !ucx_endpoint 0xb6902dc8 !rcdrlogdump USBXHCI -a 0x88399b98
        !xhci_esm 0xb5088f58 
        [0] dt _TRANSFERRING_DATA 0xb5f76f18 Events: 0x0 TransferRingState_Idle
        ------------------------------------------------------------------------------
            WdfQueue: !wdfqueue 0x4a0892e0 (0 waiting)
第三:
0: kd> dt _TRANSFERRING_DATA 0xb5f76f18
USBXHCI!_TRANSFERRING_DATA
   +0x000 ControllerHandle : 0x8eaeaee0 Void
   +0x004 UsbDeviceHandle  : 0xb4f56eb0 Void
   +0x008 EndpointHandle   : 0xb5088f58 Void
   +0x00c StreamId         : 0
   +0x010 WdfQueue         : 0x4a0892e0 WDFQUEUE__
   +0x014 WdfDpc           : 0x4b921080 WDFDPC__
   +0x018 Attributes       : _TRANSFER_RING_ATTRIBUTES
   +0x030 Lock             : 0
   +0x034 Events           : 0 ( TransferRingEvent_None )
   +0x038 State            : 1 ( TransferRingState_Idle )
   +0x03c DispatchingEvents : 0
   +0x040 TrbProcessedWaitCount : 0
   +0x044 TimerSet         : 0 ''
   +0x045 ResourcesAllocated : 0x1 ''
   +0x046 StopRequested    : 0 ''
   +0x047 IsochOverRunUnderRunEventSurelyExpected : 0 ''
   +0x048 WdfControlTimer  : (null) 
   +0x04c WdfStopTimer     : 0x497950b8 WDFTIMER__
   +0x050 MaxRingIndex     : 0x1f
   +0x054 CurrentRingIndex : 0x18
   +0x058 CycleState       : 1
   +0x05c CurrentRing      : 0xada77000 _TRB
   +0x060 CurrentRingBufferData : 0xae958e7c _BUFFER_DATA
   +0x064 PendingStageCount : 0
   +0x068 PendingCountAfterStopEvent : 0
   +0x06c InterrupterTarget : 0
   +0x06e RingDoorbellOnFirstTD : 0 ''
   +0x06f IsochPure        : 0 ''
   +0x070 FrameOfLastIsochTdInScheduleValid : 0 ''
   +0x074 NextTransferStartFrame : 0
   +0x078 FrameOfLastIsochTdInSchedule : 0
   +0x07c NumberOfPacketsPerFrame : 0
   +0x080 TransferRingList : _LIST_ENTRY [ 0xb9d64f0c - 0xb9d64e7c ]
   +0x088 PendingTransferList : _LIST_ENTRY [ 0xb5f76fa0 - 0xb5f76fa0 ]
   +0x090 DoubleBufferDataList : _LIST_ENTRY [ 0xb1bf0f3c - 0xb1bf0f3c ]
   +0x098 CancelledOnQueueList : _LIST_ENTRY [ 0xb5f76fb0 - 0xb5f76fb0 ]
   +0x0a0 CancelledDriverOwnedList : _LIST_ENTRY [ 0xb5f76fb8 - 0xb5f76fb8 ]
   +0x0a8 AsyncCompletionTransferList : _LIST_ENTRY [ 0xb5f76fc0 - 0xb5f76fc0 ]
   +0x0b0 PendingCancelledList : _LIST_ENTRY [ 0xb5f76fc8 - 0xb5f76fc8 ]
   +0x0b8 Counter          : _TRANSFERRING_COUNTER
   +0x0bc PendingTransferData : (null) 
   +0x0c0 CommonBufferCallbackData : _COMMON_BUFFER_CALLBACK_DATA
   +0x0dc ForwardProgressMdl : (null) 
   +0x0e0 CancelDpcQueued  : 0n0
   +0x0e4 RunCancelTransfersAfterSurpriseRemoval : 0n0
第五:列出相应的SLOT 15, EP4的transfer ring中的所有TRBs
0: kd> !xhci_transferring 0xada77000
        [  0] NORMAL       0xda7e5000 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0
        [  1] EVENT_DATA   0xda7e5010 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data 0xac65ef68 TotalBytes 31
        [  2] NORMAL       0xda7e5020 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0
        [  3] EVENT_DATA   0xda7e5030 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data 0xbd594f68 TotalBytes 31
        [  4] NORMAL       0xda7e5040 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0
        [  5] EVENT_DATA   0xda7e5050 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data 0xbd53cf68 TotalBytes 31
        [  6] NORMAL       0xda7e5060 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0
        [  7] EVENT_DATA   0xda7e5070 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data 0xac6a6f68 TotalBytes 31
        [  8] NORMAL       0xda7e5080 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0
        [  9] EVENT_DATA   0xda7e5090 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data 0xac6a4f68 TotalBytes 31
        [ 10] NORMAL       0xda7e50a0 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0
        [ 11] EVENT_DATA   0xda7e50b0 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data 0xbd490f68 TotalBytes 31
        [ 12] NORMAL       0xda7e50c0 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0
        [ 13] EVENT_DATA   0xda7e50d0 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data 0xbd564f68 TotalBytes 31
        [ 14] NORMAL       0xda7e50e0 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0
        [ 15] EVENT_DATA   0xda7e50f0 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data 0xac752f68 TotalBytes 31
        [ 16] NORMAL       0xda7e5100 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0
        [ 17] EVENT_DATA   0xda7e5110 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data 0x8eab6f68 TotalBytes 31
        [ 18] NORMAL       0xda7e5120 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0
        [ 19] EVENT_DATA   0xda7e5130 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data0xbf4e8f68 TotalBytes 31
        [ 20] NORMAL       0xda7e5140 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0
        [ 21] EVENT_DATA   0xda7e5150 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data0xb67a6f68 TotalBytes 31
        [ 22] NORMAL       0xda7e5160 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0
        [ 23] EVENT_DATA   0xda7e5170 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data0xac6e2f68 TotalBytes 31
第六:
回退所有的EVET TRING:
通!xhci_trb+EVENT TRB的地址,每回退一次,地址减去一个TRB的长度(0x10),最后得到:
0: kd> !xhci_trb 0xad625a10
        [  0] TRANSFER_EVENT      0xad625a10 CycleBit 1 SlotId 15 EndpointID  4 ED 1 Data 0x00000000b67a6f68 BytesTransferred       31 CC_SUCCESS
0: kd> !xhci_trb 0xad625810
        [  0] TRANSFER_EVENT      0xad625810 CycleBit 1 SlotId 15 EndpointID  4 ED 1 Data 0x00000000bf4e8f68 BytesTransferred       31 CC_SUCCESS


Transfer ring:

[ 18] NORMAL       0xda7e5120 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0

[ 19] EVENT_DATA   0xda7e5130 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data 0xbf4e8f68 TotalBytes 31

[ 20] NORMAL       0xda7e5140 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0

[ 21] EVENT_DATA   0xda7e5150 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data 0xb67a6f68 TotalBytes 31

[ 22] NORMAL       0xda7e5160 CycleBit 1 IOC 0 CH 1 BEI 0 InterrupterTarget 0 TransferLength    31 TDSize  0

[ 23] EVENT_DATA   0xda7e5170 CycleBit 1 IOC 1 CH 0 BEI 0 InterrupterTarget 0 Data 0xac6e2f68 TotalBytes 31

0xda7e5180 Empty Transfer Ring

    0xda7e5180 Empty Transfer Ring

Event ring: [ 0]TRANSFER_EVENT 0xad625810 CycleBit 1 SlotId 15 EndpointID 4 ED 1 Data 0x00000000bf4e8f68 BytesTransferred 31 CC_SUCCESS [0 ]TRANSFER_EVENT 0xad625a10 CycleBit 1 SlotId 15 EndpointID 4 ED 1 Data 0x00000000b67a6f68 BytesTransferred 31 CC_SUCCESS [207]TRANSFER_EVENT 0xad625cf0 CycleBit 1 SlotId 15 EndpointID 4 ED 1 Data 0x00000000ac6e2f68 BytesTransferred 31 CC_SUCCESS [209]TRANSFER_EVENT 0xad625d10 CycleBit 1 SlotId 15 EndpointID 4 ED 1 Data 0x00000000ac6e2f68 BytesTransferred 31 CC_SUCCESS


第五,还可以检查这个出了问题的EVENT TRB所对应的Transfer TRB 中CBW中的字段,看他是否是带有DATA OUT传输的BOT。

通过刚才打印出来的SLOT 15, EP4的TRANSFER RING中,出问题的那个TRB物理地址:


0: kd> !dc 0xda7e5000 L200
#da7e5000 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e5010 ac65ef68 00000000 00000000 00001c21 h.e.........!...
#da7e5020 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e5030 bd594f68 00000000 00000000 00001c21 hOY.........!...
#da7e5040 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e5050 bd53cf68 00000000 00000000 00001c21 h.S.........!...
#da7e5060 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e5070 ac6a6f68 00000000 00000000 00001c21 hoj.........!...
#da7e5080 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e5090 ac6a4f68 00000000 00000000 00001c21 hOj.........!...
#da7e50a0 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e50b0 bd490f68 00000000 00000000 00001c21 h.I.........!...
#da7e50c0 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e50d0 bd564f68 00000000 00000000 00001c21 hOV.........!...
#da7e50e0 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e50f0 ac752f68 00000000 00000000 00001c21 h/u.........!...
#da7e5100 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e5110 8eab6f68 00000000 00000000 00001c21 ho..........!...
#da7e5120 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e5130 bf4e8f68 00000000 00000000 00001c21 h.N.........!...
#da7e5140 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e5150 b67a6f68 00000000 00000000 00001c21 hoz.........!...
#da7e5160 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e5170 ac6e2f68 00000000 00000000 00001c21 h/n.........!...

#da7e5180 00000000 00000000 00000000 00000000 ................
#da7e5190 00000000 00000000 00000000 00000000 ................

其中:

0: kd> !dc 0xda7e5160 


#da7e5160 da7eb800 00000000 0000001f 00000413 ..~.............
#da7e5170 ac6e2f68 00000000 00000000 00001c21 h/n.........!...


看一下XHCI SPEC关于NORMAL TRANSFER TRB的结构定义:

前8个字节(0到7)是,该TRB所指向的需要传输的数据的物理地址,这处为da7eb800

第8到第11个字节为:这个TRB所承载的传输数据的长度0x1f, 就是31个字节

得出,这个TRANSFER TRB就是CBW所对应的TRB

而TRB中的第一个32BIT地址,就是CBW 31字节的物理地址:

0: kd> !dc da7eb800
#da7eb800 43425355 b6870918 00020000 280a0080 USBC...........(
#da7eb810 2fdb0000 00010058 00000000 00000000 .../X...........
#da7eb820 00000000 00000000 00000000 00000000 ................

我们研究一下CBW 31字节中各字段的含义:

dCBWSignature: 43425355

dCBWTag:    b6870918

dCBWDataTransferLength: 00020000,需要传输数据的长度,
bmCBWFlags:  80 是一个读操作(from device to host)

bCBWLUN:             00, LUN 0

bCBWCBLength:   (0,1010 b) CBW CBR 的长度,10字节

CBWCB:                  28 2f db 00 00 00 01 00 58 00


而这十字节的CBWCB,应该是一个SCSI命令

通过GOOGLE,发现这是一个READ (10 )命令

bit→
↓byte
7 6 5 4 3 2 1 0
0 Operation code = 28h
1 LUN DPO FUA Reserved RelAdr
2–5 LBA
6 Reserved
7–8 Transfer length
9 Control
通过这个命令,确认,这是一个DATA IN,而非DATA OUT,所以, 0xac6e2f68

两次SLOT15,EP4的EVENT TRB就是USB HOST IP产生的错误.



经过以上的分析,对该问题的解决提交给RTL code工程师去处理,把问题解决在源头上.
还Windows xhci driver这个嫌疑对象一个清白.


只是,还是对微软的xHCI driver如何产生这个EVENT DATA TRB中的TAG,以及后期如何检查EVENT TRB中的TAG,产生了好奇,事实上,如果微软对这个TAG的检查不到位,就发现不了这个问题,我们反而应该感谢微软帮助我们找到了一个IP设计中存在的一个问题.




后记:

如果没有WINDBG这个USB3.0 kernel debug extension, 要能分析出这个问题,存在很大的难度,如题所述,欲善其事,先利其器,作为一名软件工程师,除了写高质量的代码,同时还需要学会使用最新的软件工作,以达到事半功倍的效果.


在开发USB device IP的过程中,需要使用USB Analyzer,与其相配套的软件,能很好的将USB BUS上的数据内容以图形化的形式展现给用户,以方便进一步的问题分析.
而USB xHCI IP的开发过程,是作为x86PC平台上的一个PCIe EP设备,必须使用PCIe analyzer.但是,PCIe analyzer相配套的软件,是没有将PCIe BUS上的数据内容以USB包图形化的形式展现给用户的,所以分析PCIe trace的过程,前期是一种痛苦的过程,后期工程师熟悉后,是一种麻木的过程,工程师除了分析存在问题的主要任务,还担当了"人肉译码"的角色,是一种资源与时间上的浪费.
作为PCIe analyzer提供商,他们是没有义务在软件中加入xHCI的支持功能,因为这个针对性太强.如果需要增加这个功能,只能是xHCI IP开发软件工程师,取得analyzer提供商的软件API,在这个基础上增加这个针对性的功能,从而从"人肉译码"的角色中解放出来.

你可能感兴趣的:(windows,IP,host,develop,dbg,usb3.0,BSOD,xHCI,dump分析)