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

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


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

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

    INC 文件夹存放的是头文件, CLASS USB 存储设备的驱动程序, 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 设备的 interface pipe 以及 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 ,这里不再介绍,可以直接查看相关代码了解。
    2 HID 驱动
        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 设备的 interface CreateUsbHidDevice 负责创建 HID 设备内容,这个比较重要,后面细谈。 UsbFuncs->lpRegisterNotificationRoutine 注册设备事件通知处理回调函数。
     CreateUsbHidDevice() 内,先为设备分配资源,然后执行下面的语句: ActivateDeviceEx(HID_REGKEY_SZ, NULL, 0, CLIENT_REGKEY_SZ);ActivateDeviceEx 函数是用来加载驱动程序,设备管理器加载驱动也是通过调用该函数来实现的,第一个参数为指向注册表中包含驱动信息的键,这里 HID_REGKEY_SZ Drivers\\USB\\ClientDrivers\\Hid\\Instance ,包含的 DLL 名称为 USBHID.DLL ,即加载驱动 USBHID.DLL ,最后一个参数为向驱动程序传递的参数,这里 CLIENT_REGKEY_SZ Drivers\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 下面控制面板中,鼠标的设置是很简单的,没有左右键调换的设置。这些都可以通过添加驱动来实现的。
    3 USBSER 驱动
        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 转串口驱动支持了两种设备,分别为 COM0 COM1
     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 口的读写操作等细节,以后再讲。今天主要分析驱动加载的流程。
    4 PRITER 驱动
       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() 函数中,完成了流接口的初始化。
 
参考资料:
wince USB 设备驱动程序导读
http://blog.csdn.net/caowenbin/article/details/2030938
详解 WinCE USB Host 驱动开发
http://blog.csdn.net/selfref/article/details/4830961

你可能感兴趣的:(host,休闲,USB驱动,WinCE6.0,驱动加载流程)