作者:Walzer
日期:2005.3.30
前阵子调试平台的mass-storage function, 想借机把USB Initialization过程了解清楚. 以前的理解都是基于代码的, PC机那边会怎么响应完全是黑盒, 不可见的. 而在初始化过程中不能加入break point, 否则请求超时后, 即使单步调试最后把请求的数据发回, 也被主机认为初始化失败. 因此只能在endpoint的XmitData和ReceiveData函数里添加调试信息把数据都打印出来, 然后根据USB协议还原成REQUEST和PACKET.
在设备Attach后, 果如所料, client产生了一个EP0 out packet completed的中断, 从device controller的IST分流到EP0 IST中处理, EP0 IST在确定是setup packet后, 从data register读了32bit进来, 并根据协议分解了该request:
Endpoint Zero Setup bmRequestType = 0x80, bRequest=0x6, wValue=0x100,wIndex=0x0,wLength=0x40.
其中0x80=设备到主机/标准请求类型/设备接受, 0x6=Get_Descriptor, 0x100=Device_Descriptor.
下面EP0就XmitData了, 每次写32bit, 分5次写完0x12byte. 注意由于EP0的MaxPacketSize=16byte, 所以第一次IST中写了16byte, 第二次再写2byte.
msb 0020 0112 lsb
1000 0000
FFFF 0453
0201 0000
03F4 0100
按照协议翻译出来就是bLength=0x12, bDescriptorType=0x1(device descriptor), bcdUSB=0x0200(protocol 2.0), bDeviceClass=0, bDeviceSubClass=0, bDeviceProtocol=0x0, bMaxPacketSize=0x10(16byte), idVendor=0x0453(Microsoft), idProduct=0xffff(OEM), bcdDevice=0, iManufacturer=1, iProduct=2, iSerialNumber=0, bNumConfigurations=1. 后面的就没有用了. 注意iManufacturer, iProduct, iSerialNumber, 这个决定了后面GetString请求里wValue掉低字节的值和所获得string的关系. 如果发送的device descriptor不足18byte的话, host会再次发送get device descriptor的request直至host收全了这18byte.
第二步host就发过来Get Config Descriptor的请求了, 此时client需将该config下的所有interface和endpoint描述符全部发送给host. 但这其中又分了两步. 首先是纯粹地get config descriptor:
Endpoint Zero Setup bmRequestType = 0x80, bRequest=0x6, wValue=0x200,wIndex=0x0,wLength=0x9
0x80=设备到主机/标准请求/设备接受, 0x6=GetDescriptor, 0x200=Config_Descriptor. 注意到Length=9正好为config descriptor的长度.
这时client返回数据如下:
0020 0209
C000 0101
C000 0100
翻译出来bLength=9, bDescriptorType=2(config descriptor), wTotalLength=0x20(该配置返回的数据总长度,包括其下interface, endpoint descriptor的总长度), 后面的就先不看了,没有意义. host主要就是想索取这个wTotalLength以决定下一次需要获取多少长度.
马上host再发了一个get config descriptor,
Endpoint Zero Setup bmRequestType = 0xx80, bRequest=0x6, wValue=0x200,wIndex=0x0,wLength=0xff
它倒聪明, 对wTotalLength看都不看, 直接给了个0xff长度, 有多少要多少.
client就老老实实填满0x20byte发过去了
0020 0209 CONFIG: bLength=9, bDescriptorType=2(config), wTotalLength=0x20,
C000 0101 bNumInterface=1, bConfigurationValue=1, iConfiguration=0, bmAttributes=0xC0(self-powered)
0004 0900 bMaxPower=0 // INTERFACE: bLength=9, bDescriptorType=4(interface), bInterfaceNumber=0
0608 0200 bNumEndpoints=2(except ep0, bulk-only transport), bInterfaceClass=8(mass storage class), bInterfaceSubClass=6(SCSI)
0507 0050 bInterfaceProtocol=0x50(bulk-only transport), iInterface=0. // ENDPOINT1: bLength=7, bDescriptorType=5(endpoint)
0040 0281 bEndpointAddress=0x81(ep1, in), bmAttributes=2(bulk, non-sync, data), wMaxPacketSize=0x40(64byte),
0205 0700 bInterval=0(ignore for bulk ep). // ENDPOINT2: bLength=7, bDescriptorType=5, bEndpointAddress=2(ep2, out)
0000 4002 bmAttributes=2(bulk, non-sync, data), wMaxPacketSize=0x40, bInterval=0
最后还得SendControlStatusHandshake.
接下来是Endpoint Zero Setup bmRequestType = 0xx80, bRequest=0x6, wValue=0x300,wIndex=0x0,wLength=0xff, host想要get string descriptor(wValue高八位=0x3)了,这回wValue低八位=0, 对应SupportedLanguage. CLIENT返回了04090304, 翻译出来是bLength=4, bDescriptorType=3(string), wLANGID=0409(english-only).
下面这个要发的就多了 Endpoint Zero Setup bmRequestType = 0xx80, bRequest=0x6, wValue=0x302,wIndex=0x409,wLength=0xff.
这里wValue低八位=2, 对应Product String. 只见CLIENT发了N多数据过去, 把这些数据按照ASCII编码翻译过来,就是体现在host端的device name了.
理论上还应该有get manufacturer string descriptor和get serial number string descriptor的过程, 但在这次调试中没有看到. 最终mass storage client由于没有store objet而在OpenStore函数的CreateFile处失败了.