最近做了一些关于USB的方案,发现了一个共通的问题。
由于Windows的设备驱动给每个设备一个设备标识(Device ID)。设计的初衷是用来区分每个设备的。
USB设备的DeviceID命名规则很简单。举例来说,如果一个USB设备的VID是0x1234、PID是0x5678、序列号是00001,则它的DeviceID为:
USB\VID_1234&PID_5678\00001
由于VID是USB厂商的唯一标识、PID是厂商定义的产品的唯一标识、序列号理论上应该是同类产品的唯一标识。因此理论上说这样的DeviceID可以区分所有USB设备。
如果没有序列号,Windows则将Device ID的序列号部分用HUB及其端口相关的一串文字来代替,也可以基本保证DeviceID的唯一性。这种Device ID形如:
USB\VID_1234&PID_5678\X&YYYYYYYY&Z
其中X是设备的层次、Z表示设备插到HUB的哪个端口、YYYYYYYY是与HUB有关的某常数。
因此,当USB的设备描述符中指出有序列号时,就必须保证每个产品有一个唯一的序列号。
但不幸的是,现实中序列号存在重号的情况。当一个USB产品的程序是在MaskROM内或写入OTP的话,由于MaskROM和OTP很难做到每个产品都有自己区别于其它相同产品的序列号。因此这种方案如果有序列号,则所有产品都是同一序列号。问题就发生了。
早期Windows驱动完全没有处理Device ID重名,遇到这种情况只能崩溃。例如一直没有更新的HID设备,一插即死。
稍后期的驱动似乎意识到这种情况的严重性(即便不是驱动本身的问题,驱动也不应该在这种情况下使系统崩溃),试图去解决它,但可惜不彻底。例如USBSTOR驱动,当同一个HUB上插多个相同序列号的U盘时,驱动会认为后来者没有序列号,以为这样就解决问题了。但它忘了HUB有层次性。当两个序列号相同的U盘插到不同HUB时(包括有插主机的RootHUB),驱动忘了给后来者改名了。系统还是崩溃。
难道就没有彻底的解决办法吗?有。
事实上,问题的症结不在Class层而在于USB本身,即EP0枚举的处理上。微软似乎已经认识以了这一点,因此并没有打算在Class层彻底解决问题。
解决办法是,在注册表[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\UsbFlags]中添加:
"GlobalDisableSerNumGen"=hex:01
"IgnoreHWSerNum12345678"=hex:01
其中的12345678就是指VID和PID。有多少有问题的VID、PID就要增加多少项。
作这样的处理之后,不管是HID、Mass Storage还是其它设备,都没有问题了。
但毕竟这个问题的根源不在驱动,而在于设备Firmware。因此在写Firmware的时候要留心。Flash方案可以随时升级,问题不大;如果是OTP方案,建议采用烧写时可改写序列号的芯片方案;是MaskROM方案,则建议不要用序列号。