WinCE6.0 USB Host驱动加载流程详解(二)

    无语,编辑了好多遍了,仍是显示不正常,就这样吧。
    今天分析USB HOST Class部分的驱动内容。
        CLASS 录实现的 Client 层驱动程序,经过调用 USBD 提供的接口函数来完成,文件夹下面包含的目录以下:


    其中CLIENTCMNCOMMON包含的是公共代码,另外四个分别是为了实现HID设备、打印机、大容量存储器和usb串口支持的驱动代码。

    1STORAGE驱动
       STORAGE目录结构以下:

    INC文件夹存放的是头文件,CLASSUSB存储设备的驱动程序,DISK是磁盘驱动程序。
    这里为何有两个驱动?这里引用参考资料的解释:驱动程序工做在硬件与操做系统之间,它有两个功能,一个是将操做系统转发来的操做以符合指定硬件设备的形式控制硬件设备,另外一个是向操做系统提供这个访问接口。好比说U盘,一方面驱动程序要把操做系统对U盘的识别、读、写等操做转换成U盘的动做,另外一方面又告诉操做系统这是个U盘,能够当成一个文件夹或文件系统来用,可以接受标准的文件操做命令。因此此处存在两个驱动。
      这两个驱动的函数导出文件内容以下:
CLASS部分:

LIBRARY USBMSC
EXPORTS
;
;     USB Storage Interface
;
                GetDWORD
                SetDWORD
                SetWORD
                UsbsGetContextFromReg
                UsbsDataTransfer
;
;     USBDI Interface
;
                USBInstallDriver
                USBDeviceAttach
                USBUnInstallDriver
DISK部分:

LIBRARY                 USBDISK6
EXPORTS
                                UsbDiskAttach
                                UsbDiskDetach
                                DSK_Init
                                DSK_Deinit
                                DSK_Open
                                DSK_Close
                                DSK_Read
                                DSK_Write
                                DSK_Seek
                                DSK_IOControl
                                DSK_PowerUp
                                DSK_PowerDown
    从上面能够看出Storage Client驱动的总接口应该在CLASS部分,这里包含了Client层驱动必须实现的三个接口函数,而DISK驱动是被CLASS驱动调用的,具体如何调用,下面会讲到。
       USBInstallDriver()函数主要是完成Storage驱动相关注册表信息的设置,源码这里就不分析了,主要看一下相关的注册表内容。因为这部分的驱动代码是由微软提供的,因此其注册表的信息在文件WINCE600\PUBLIC\COMMON\OAK\FILES\ common.reg中。
; USB - Mass Storage Class Driver
[HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\Default\Default\8\Mass_Storage_Class]
     "DLL"="USBMSC.DLL"
     "Prefix"="DSK"

[HKEY_LOCAL_MACHINE\Drivers\USB\ClientDrivers\Mass_Storage_Class]
     "DLL"="USBMSC.DLL"
     "Prefix"="DSK"

[HKEY_LOCAL_MACHINE\Drivers\USB\ClientDrivers\Mass_Storage_Class\6]
     "DLL"="USBDISK6.DLL"
     "Prefix"="DSK"
     "Folder"="USB Disk"
     "MediaPollInterval"=dword:432     ; Media poll interval (1250 ms)
     "ReadSectorTimeout"=dword:2710    ; Read sector timeout (10 s)
     "WriteSectorTimeout"=dword:2710 ; Read sector timeout (10 s)
     "ScsiCommandTimeout"=dword:1388 ; Command timeout (5 s)
     "UnitAttnRepeat"=dword:A                ; TEST UNIT READY repeat (reduce to 1 for large USB disk keys)
     "IOCTL"=dword:4
     "IClass"="{A4E7EDDA-E575-4252-9D6B-4195D48BB865}"
        上面注册表中的第二部分表明的大容量存储设备的驱动,第三部分则为具体某一类接口的 Storage 设备的驱动,好比上面的 6 表明表明的就是 SCSI 接口的大容量存储设备。
    在完成了注册表的相关设置以后,来看一下USBDeviceAttach()函数,这里注意下该函数的参数LPCUSB_FUNCS UsbFuncs,这个就是以前讲到的USBD一些接口函数封装起来的函数列表结构体,该参数是由USBD驱动传入的。这里就不列出函数的所有源码了,只分析一下总体的流程。
    首先调用函数ParseUsbDescriptors解析该设备,包括设备的接口描述信息和协议描述信息。以后为该设备分配空间并进行赋值,设备结构体为USBMSC_DEVICE,该结构体包含了USB设备的interfacepipe以及registry等,将做为参数传递给其余一些函数。下来经过注册表查找到对应的Disk的驱动,好比USBDISK6.DLL,而后调用LoadDrvier()函数来将Disk驱动加载到本身的虚拟地址空间中,同时得到Disk驱动中UsbDiskAttach函数及UsbDiskDetach函数的地址,接着便调用UsbFuncs->lpRegisterNotificationRoutine函数注册事件通知处理函数,这里采用的是回调函数的方法。这里须要说明的是该处理函数必须实现USB_CLOSE_DEVICE消息的响应,这个微软要求任何USB设备都应该实现的,在Close的消息响应中会调用UsbDiskDetach函数释放驱动程序资源,若是再也不有设备引用此驱动程序,则FreeLibrary()释放该驱动库。最后调用UsbDiskAttach函数。
    到这里并无结束,Disk驱动尚未真正完成设备驱动初始化。为何尚未初始化完?由于上面调用的LoadDrvier()函数仅仅是将驱动DLL加载到本身的虚拟空间中,这样才能够得到Disk驱动的入口UsbDiskDetach函数。在进入UsbDiskDetach函数后,仍然是进行一系列设备描述符等信息的存储、设置等,最后调用关键的ActivateDevice()函数,将注册表USB\ClientDrivers\Mass_Storage_Class\6下面的驱动USBDISK6.DLL进行加载,此时便会进入流驱动的DSK_Init函数,到这里才算真正的加载完毕。
    当加载了Disk驱动以后,上层应用程序即可以按照流接口的操做方法来操做Mass Storage设备,包括初始化设备、获取设备信息、读写数据等,操做的接口即是DSK_IOControl,这里再也不介绍,能够直接查看相关代码了解。
    2HID驱动
        HID目录结构为:
       HIDCLASS 文件夹里面的是支持全部 HID 设备的驱动程序,包括 Mdd Pdd 两部分, CLIENTS 文件夹是只支持某一种 HID 设备的驱动程序,包括 USB 键盘、 USB 鼠标。各个部分的 def 导出文件的内容以下,为何键盘的驱动和鼠标驱动有区别呢?后面给出解释。
;HIDCLASS部分:
LIBRARY                 USBHID
EXPORTS                
                                USBInstallDriver
    USBUnInstallDriver
                                USBDeviceAttach
                                HID_Init
                                HID_Deinit
                                HID_Open
                                HID_Close
                                HID_IOControl
    Init
    Deinit
;CLIENTS\KBDHID部分:
LIBRARY                 KBDHID
EXPORTS                
                                HIDDeviceAttach
    HIDDeviceNotifications

    KBD_Init
    KBD_PreDeinit
                                KBD_Deinit
                                KBD_Open
                                KBD_Close
                                KBD_IOControl
;CLIENTS\CONSHID部分
LIBRARY                 CONSHID
EXPORTS                
                                HIDDeviceAttach
    HIDDeviceNotifications
;CLIENTS\MOUHID部分
LIBRARY                 MOUHID
EXPORTS
  HIDDeviceAttach
  HIDDeviceNotifications
    USBHID驱动中包含USB Client驱动必须具有的三个接口,那么系统加载HID设备驱动的入口应该就在USBHID驱动中,其余键盘、鼠标等设备是由USBHID来加载的。
       首先来看USBInstallDriver()函数,一样的是完成设备驱动的相关注册表设置。涉及到的注册表内容为:
[HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\Default\Default\3\Hid_Class]
     "DLL"="USBHID.DLL"

[HKEY_LOCAL_MACHINE\Drivers\USB\ClientDrivers\Hid\Instance]
     "DLL"="USBHID.DLL"

[HKEY_LOCAL_MACHINE\Drivers\USB\ClientDrivers\Hid\Hid_Class]
     "DLL"="USBHID.DLL"
     "Prefix"="HID"
     "QueuedTransferCount"=dword:2
    下面来看看USBDeviceAttach()函数,这里主要执行三个函数:   ParseUsbDescriptors负责解析设备,判断是否知足HID设备的interfaceCreateUsbHidDevice负责建立HID设备内容,这个比较重要,后面细谈。UsbFuncs->lpRegisterNotificationRoutine注册设备事件通知处理回调函数。
    CreateUsbHidDevice()内,先为设备分配资源,而后执行下面的语句:ActivateDeviceEx(HID_REGKEY_SZ, NULL, 0, CLIENT_REGKEY_SZ);ActivateDeviceEx函数是用来加载驱动程序,设备管理器加载驱动也是经过调用该函数来实现的,第一个参数为指向注册表中包含驱动信息的键,这里HID_REGKEY_SZDrivers\\USB\\ClientDrivers\\Hid\\Instance,包含的DLL名称为USBHID.DLL,即加载驱动USBHID.DLL,最后一个参数为向驱动程序传递的参数,这里CLIENT_REGKEY_SZDrivers\USB\ClientDrivers\Hid\Hid_Class,这个参数传递给了HID_Init。这里也看出来通常流接口驱动的参数如何从设备管理器传递过来的。再接下来处理设备的描述符和interface相关的信息,最后调用了函数HidMdd_Attach()report descriptor信息传递给MDD层。
     MDD 层的 HidMdd_Attach() 函数这里就不仔细研究,只关注这里调用了一个函数 LoadHidClients() ,该函数也位于 MDD 层的 Hidmdd.cpp 文件中。接下来会用到另外一部分关于 HID 设备的注册表信息,以下:
[HKEY_LOCAL_MACHINE\Drivers\HID\LoadClients\Default\Default\1_6\Keyboard]
     "DLL"="KBDHID.DLL"

[HKEY_LOCAL_MACHINE\Drivers\HID\LoadClients\Default\Default\12_1\Consumer]
     "DLL"="CONSHID.DLL"
     "RemoteWakeup"=dword:1

[HKEY_LOCAL_MACHINE\Drivers\HID\LoadClients\Default\Default\1_2\Mouse]
     "DLL"="MOUHID.DLL"
     "RemoteWakeup"=dword:1
    LoadHidClients()函数里面实现了对Client下面的鼠标、键盘或其余HID设备驱动的加载。具体流程为:首先调用函数FindClientRegKey()在在这个函数里面枚举HID设备,找到相匹配的Client设备的注册表键。查找的方法是在注册表\Drivers\HID\LoadClients\下面,根据前面提到的那三组键值(Default等)比较设备的描述符。结合上面的注册表信息能够看出,从这里能够找到对应的鼠标或者键盘等Client驱动的DLL名称。以后调用LoadDriver()函数将对应驱动的加载到本身的虚拟地址空间中,并得到对应驱动的HIDDeviceAttach函数地址,最后执行该函数,从而进入到具体的Client Hid设备驱动程序中。这里须要注意,HIDDeviceAttach函数是一个统一的接口,即全部的Client Hid设备驱动都必须实现该函数,这一点从上面的def文件内容也能够看出来。
        HID设备驱动的层次目录比较复杂,到这里尚未结束。接下来进入具体的Client Hid设备驱动里面。这里以USB键盘的驱动为例,由于从上面def能够看出,键盘的驱动实现的比较彻底,有标准的流接口。这里还有一个注册表须要用到,以下:
[HKEY_LOCAL_MACHINE\Drivers\HID\ClientDrivers\Keyboard]
     "DLL"="KBDHID.DLL"
     "Prefix"="KBD"
     "IClass"="{CBE6DDF2-F5D4-4e16-9F61-4CCC0B6695F3}"
     "RemoteWakeup"=dword:1
     "Flags"=dword:00010000
    在Kbdhid.cpp中找到HIDDeviceAttach()函数,开始仍是为设备分配资源,设置描述符等信息,以后建立键盘的处理线程函数,接收设备发来的报告并进行处理,最后再次调用函数ActivateDevice()函数将Drivers\HID\ClientDrivers\Keyboard下面的KBDHID.DLL驱动加载。直到此时,才会执行KBD_Init函数,完成键盘设备驱动的初始化。到此,整个的流程才算走完。
    最后回答前面提出的一个,为何USB键盘的驱动提供了标准的流接口驱动,而USB鼠标驱动却没有?这里没有本质的区别,只是微软官方只提供了键盘的流接口驱动而已,你能够本身添加鼠标的驱动。正由于鼠标的驱动没有实现,能够发现WinCE下面控制面板中,鼠标的设置是很简单的,没有左右键调换的设置。这些均可以经过添加驱动来实现的。
    3USBSER驱动
        USBSER驱动用来实现USB转串口的接口驱动,目录结构以下:
    能够看出 UsbSer 驱动部分没有前面那么深的层次结构,加载过程比较简单。 Usbser.def 文件的内容以下:
LIBRARY USBSER
EXPORTS  USBInstallDriver
  USBDeviceAttach
  USBUnInstallDriver
  COM_Init
  COM_Deinit
  COM_PreDeinit
  COM_Open
  COM_Close
  COM_PreClose
  COM_Read
  COM_Write
  COM_Seek
  COM_PowerDown
  COM_PowerUp
  COM_IOControl
    一样实现了那三个必须的函数,但在USBSER驱动中多了不少类结构,USBInstallDriver()函数内容和以前的有些不一样,先建立了一个类USBDriverClass的实例。USBDriverClass类是对USBD提供的接口又一层封转,因此这里经过该类的成员函数间接调用了RegisterClientDriverID()RegisterClientSettings()完成注册表的设置。注册表内容为:
[HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\1118_121\Default\Default\USBSER_CLASS]
     "Prefix"="COM"
     "Dll"="USBSer.DLL"

[HKEY_LOCAL_MACHINE\Drivers\USB\ClientDrivers\USBSER_CLASS]
     "Prefix"="COM"
     "Dll"="USBSer.DLL"
     "DeviceArrayIndex"=dword:1
     "RxBufferSize"=dword:4000
     "IClass"="{CC5195AC-BA49-48a0-BE17-DF6D1B0173DD}"

[HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\1118_206\Default\Default\SERIAL_CLASS]
     "Prefix"="COM"
     "Dll"="USBSer.DLL"

[HKEY_LOCAL_MACHINE\Drivers\USB\ClientDrivers\SERIAL_CLASS]
     "Prefix"="COM"
     "Dll"="USBSer.DLL"
     "RxBufferSize"=dword:4000
     "DeviceArrayIndex"=dword:0
     "IClass"="{CC5195AC-BA49-48a0-BE17-DF6D1B0173DD}"
    从注册表看出,微软提供的USB转串口驱动支持了两种设备,分别为COM0COM1
    USBDeviceAttach()函数中,首先建立了类SerialUsbClientDevice的一个实例,该类继承自类UsbClientDevice,以后调用类成员函数Init()。在Init()函数里经过ActivateDevice()函数加载了Drivers\USB\ClientDrivers\SERIAL_CLASS下面的USBSer.DLL驱动。
    在加载USBSer.DLL驱动以后,便会调用对应的COM_Init函数,该函数位于串口的驱动MDD层,传入的参数是该类的this指针。通常串口驱动的MDD层有微软已经实现了,与硬件无关的CSerialPDD也已经提供,咱们只须要实现与硬件相关的PDD部分就能够了。在USBSer.DLL驱动中,有一个类UsbSerClientDriver即是从CSerialPDD类继承过来的,这样便实现了和串口驱动的接口。
    至于USBSer.DLL是如何USB的数据传输转换为COM口的读写操做等细节,之后再讲。今天主要分析驱动加载的流程。
    4PRITER驱动
       PRITER目录下面包含的文件以下:
    能够看出, Printer 驱动结构较为简单,先来看看定义导出接口的 def 文件按内容:
LIBRARY                 USBPRN
EXPORTS
                                USBInstallDriver
                                USBDeviceAttach
                                USBUnInstallDriver
                                LPT_Init
                                LPT_Deinit
                                LPT_Open
                                LPT_Close
                                LPT_Read
                                LPT_Write
                                LPT_Seek
                                LPT_IOControl
                                LPT_PowerUp
                                LPT_PowerDown
    这里看到了Client驱动必须实现三个接口函数,说明这里就是驱动加载的总入口。USBInstallDriver()函数和以前同样,仍是调用USBD提供的接口RegisterClientDriverID()RegisterClientSettings()完成注册表的设置。注册表的内容以下:
[HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\Default\Default\7\Printer_Class]
     "Prefix"="LPT"
     "Dll"="USBPRN.DLL"

[HKEY_LOCAL_MACHINE\Drivers\USB\ClientDrivers\Printer_Class]
     "Prefix"="LPT"
     "Dll"="USBPRN.DLL"
    接着来看USBDeviceAttach()函数,和以前同样,调用ParseUsbDescriptors()函数解析设备描述符,同时申请设备资源,设置属性、接口和管道,以后便调用ActivateDevice()函数,加载Drivers\USB\ClientDrivers\Printer_Class注册表下面的USBPRN.DLL驱动,最后注册事件通知处理回调函数。在加载了USBPRN.DLL驱动以后,便直接进入了LPT_Init()函数中,完成了流接口的初始化。
 
winceUSB设备驱动程序导读
详解WinCEUSB Host驱动开发
http://blog.csdn.net/selfref/article/details/4830961