WinCE下驱动开发入门记录 [转载]

来源:互联网 发布:网络数据 编辑:程序博客网 时间:2024/04/30 12:54

http://www.cppblog.com/kerlw/ 

 

这两天时间完全泡在CE的驱动开发中,学习学习,感觉就是资料不好找,难入门难上手,象我这样仅仅只有eVc4的更是没法调试没法跟踪,困难还是挺多的。学习了两天,现在感觉有那么点意思了,记录一下与大家分享。
        首先说明一下,我可能仍然是个门外汉,因为以前基本没做过和硬件相关的东西,这方面知识缺乏的要命,现在公司要求做个驱动出来,也只有硬着头皮上了,学习研究了两天,基本掌握了一些有用的信息,所以认为自己入门了,但仅从自己的感觉出发都可以说我只是窥见了冰山之一角,还有很多很多都不懂。上次的帖子里面介绍了一些当时收集到的比较有用的资料,那个PB我也试着下了,下了一两天没多少,公司网太破,那个ftp又不支持断点续传,可害苦了我了,一个个的文件包100多M,一掉线就得重来,最后我干脆放弃了。那个弟兄有的,干脆打个包把代码发给我好了,谢谢了先。
         好,怎们切入正题。首先介绍一下我的驱动目标,目的是为了使在pda上运行的应用程序能够通过USB口和我们这个外设通讯,可以发送指令给外设,也要求从外设获取数据,因此我把它定位在流接口上,这就引出第一个概念,流接口,应该说这里所说的流接口是Wince上驱动的一种,象串口啊这样的可以用CreateFile打开的都属于这个范畴。其关键在于1)实现xxx_系列的函数2)注册表中定义Prefix和Dll。其中xxx就是注册表中Prefix的值,比如串口就是“COM”。而注册表中的DLL就是你的驱动程序dll(wince下的驱动就是一个dll),加载驱动的时候,会到你的dll中去找xxx_系列的函数,所以你的dll一定要导出这些函数符号。
XXX_Init
XXX_Deinit
XXX_Open
XXX_Close
XXX_Read
XXX_Write
XXX_Seek
XXX_IOControl
XXX_PowerDown
XXX_PowerUp
经过测试,其中前六个是必须实现且导出的,否则是不能够成功加载的。

         好,先介绍了一些关于流接口驱动的相关知识,这部分在网上还是能够找到不少资料的,我就不多说,回到我要开发的驱动上,说到底我这个还首先是个USB设备,被识别之后才能将它驱动成一个流设备
        说到USB驱动,就要先介绍一下USB驱动加载过程了。当usb设备接到主机(usb host,因此你的pda必须是能够作为usb host使用的)上时,系统就会根据它的信息去从注册表里查找它的驱动相关信息,在HKLM/Drivers/Usb/LoadClients下面会有一些主键Group1_ID/Group2_ID/Group3_ID/DeviceID,而DeviceID下面有一个字符串Dll=DriverFile.dll
注意,其中Group1_ID,Group2_ID,Group3_ID分别表示什么,以及其匹配的优先级别,可以在《WindowsCE 驱动开发指南》一书中USB驱动开发一章去详细研究。
         如果找到匹配的注册表信息,系统就会加载DriverFile.dll,否则就会提示用户输入一个驱动名称。
         那么加载dll的时候会有些什么动作呢?驱动程序DLL和普通的DLL是否具备不同的入口点呢?
         先回答第二个问题,答案是否定的,其入口点和普通的dll没有区别。但是加载的过程就不那么简单了。你可以理解系统已经作了一些事情,使得你可以用一个普通的dll来作为驱动程序,但是显然,你必须实现系统所要求的接口函数。
         对于USB驱动程序来说,有这么几个接口函数是必须实现且导出的:
BOOL USBDeviceAttach(USB_HANDLE hDevice,
                                    LPCUSB_FUNCS lpUsbFuncs, 
                                    LPCUSB_INTERFACE lpInterface,
                                    LPCWSTR szUniqueDriverId, 
                                    LPBOOL fAcceptControl,
                                    LPCUSB_DRIVER_SETTINGS lpDriverSettings,
                                     DWORD dwUnused);
BOOL USBInstallDriver(LPCWSTR szDriverLibFile);
BOOL USBUnInstallDriver();
          建议大家先读一下sdk的include目录下的usbdi.h头文件,这里面定义了很多usb驱动相关的结构、函数接口,包括注释。

 

昨天太忙,没来得及写,今天晚上火车要回武汉了,5.1估计没空写,这两天又有不少新发现想要些出来跟大家分享,但是一下子又理不清出头绪,还是不能着急,慢慢写,不要让列位看官越看越糊涂才好。
         上一篇咱们写到了USB驱动必须实现的三个入口函数:USBInstallDriver,USBUnInstallDriver和USBDeviceAttach。这一篇就主要介绍一下这几个函数(及另外两个函数:ActivateDevice和USBDeviceNotificationCallback,有时间的话,后来发现没时间写这么长了Attach都写不完,只好下篇再写)。
         其实网上搜索到的关于WinceUSB驱动开发的文章都有介绍这些函数,这些函数干什么用的,里面调用了那些东东都有说明,但是似乎天下文章一大抄的原则永远没有改变,每篇文章说的内容都差不多,还有些该说明的细节根本没有一篇提到过,根本就不是给新手入门看的,我想可能学习Wince驱动开发最大的疑惑就是哪些是我们该做的,哪些是不需要我们作的,我在学习的时候,看了这些所谓的入门文章,还是没有完全搞清楚,所以我才专门用一篇的篇幅把我研究所得写出来,可能会对新入门的朋友有所帮助。
         先说USBInstallDriver,这个函数在驱动程序DLL被加载的时候会被调用,但是不是任何情况下加载驱动都会调用这个函数入口,前面提到过USB驱动的注册表键值,当系统能够根据注册表定位到驱动程序dll并且成功加载的话,这个函数就不会被调用了。反之,当不能够找到匹配的驱动或者不能够成功加载驱动的时候,系统会弹出一个对话框,让用户输入一个驱动程序名称,这个时候,系统就会加载用户输入的这个驱动程序文件,并调用其中的USBInstallDriver函数了。USBUnInstallDriver函数呢,我很迷惑,《WindowCE驱动开发指南》有一句话说WinCE永远不会调用它,我也不明白,但是就我测试的结果来看,确实没发现这个函数被调用过。
        USBInstallDriver函数里面作什么的呢?说白了,就是写注册表,让系统下次能够通过注册表信息匹配到这个驱动程序文件。其他文章都说了,怎么写注册表,就是用USBD.dll中的RegisterClientDriverID和RegisterClientSettings两个函数,少不了LoadLibrary,GetProcAddress,FreeLibrary。网上看到过一个问题,问驱动程序不是被USBD进程加载的么?为什么不能直接用这个两个函数,还要LoadLibrary和GetProcAddress来调用这些函数呢?我不知道怎么去解答这个问题,只觉得即使在一个进程里面,似乎不这样你也得不到这两个函数的地址吧。至于到处都说USBInstallDriver里面不要用Reg的API函数去操作注册表,却没有个所以然,这个应该是因为这些注册表主键可能会根据操作系统的变更而变更的,而不论你是哪个操作系统,RegisterClientDriverID和RegisterClientSettings都会找到对应的正确的注册表主键去添加值,所以建议不要用regAPI来操作,换句话说你用了RegAPI去操作注册表,写入信息,也不会有什么问题,除非你的wince系统中那些驱动信息不应该写在那几个主键下了。
        要注意的一点是:在RegisterClientSettings的参数中,给的USB_DRIVER_SETTINGS(内有9个ID)如果和你的设备的ID对不上,结果就是系统仍然不能够通过注册表信息加载你的驱动程序,所以,那个提示你输入驱动程序的对话框还会继续弹出来,但是USBInstallDriver成功返回的话,其中写入注册表的信息是成功写入了的(如果不成功,也会继续弹出那个对话框)。
        在USBInstallDriver函数调用完之后,驱动程序dll会被释放掉,然后系统再读取注册表信息去找匹配的驱动来加载,所以才会出现上述情况。因此如果你写入的USB_DRIVER_SETTINGS是和你的设备匹配的,系统就会加载你的驱动,去继续干活了。
        这次系统加载会干什么呢?会调用驱动DLL中的USBDeviceAttach入口函数。这个函数的学问就大啦,函数的声明如下:
BOOL USBDeviceAttach(USB_HANDLE hDevice, LPCUSB_FUNCS lpUsbFuncs,
                     LPCUSB_INTERFACE lpInterface, LPCWSTR szUniqueDriverId,
                     LPBOOL fAcceptControl,
                     LPCUSB_DRIVER_SETTINGS lpDriverSettings, DWORD dwUnused)
lpUsbFuncs是usb的一些函数接口,定义在USBDI.H头文件中,大家自己先看看,对照msdn看看。今天只取其中一个函数说一下:lpGetDeviceInfo
在USBDeviceAttach中写这么一行:
LPCUSB_DEVICE lpUsbDev = (lpUsbFuncs->lpGetDeviceInfo)(hDevice);
如果取出的lpUsbDev是NULL的话,那就说明该设备无法使用,咱们也不用继续折腾了,不为空的话,建议大家把这个lpUsbDev指向的那些数据对照msdn搞清楚其含义,这也有助于你理解usb规范中的一些东西。这个结构里面套结构,套的很深很深,而且我看MSDN2005和我的eVc4中的头文件中的定义有些地方有出入,大家自己研究一下,捉摸一下吧,结构就对照自己的头文件中的定义去探索,各成员的含义就对照MSDN去解读,完成了,差不多就进了一大步了

之前公司由于项目需要让我研究PDA上的WinCE系统下的USB外设驱动开发,刚刚有点入门的感觉结果又终止了这个计划,我也一直在郁闷这个事情,不想现如今,机会又来了。我又开始了驱动开发的研究学习之旅,这里将继续记录我的心得体会。
    之前的入门记录(二)已经讲到了USBDeviceAttach函数,原形这里再列一下:

BOOL USBDeviceAttach(USB_HANDLE hDevice, LPCUSB_FUNCS lpUsbFuncs,
                     LPCUSB_INTERFACE lpInterface, LPCWSTR szUniqueDriverId,
                     LPBOOL fAcceptControl,
                     LPCUSB_DRIVER_SETTINGS lpDriverSettings, DWORD dwUnused);

不过(二)只谈到了它的第一个参数hDevice,调用一句:  LPCUSB_DEVICE lpUsbDev = (lpUsbFuncs->lpGetDeviceInfo)(hDevice); 
就能够获得一个USB_DEVICE的指针,然后顺藤摸瓜可以看到这个设备的许多信息,被命名为Descriptor的,PC上有一个软件叫USBView的可以看到接到USB口上的设备信息,读取的应该就是这个结构。我当时傻乎乎的,自己写打印函数,在驱动程序加载的时候把这些信息MessageBox显示出来:P,所以呢,今天这篇先讨论一下关于这个驱动的调试问题。在MSDN上有关于wince驱动程序调试的专题,大体是介绍使用PB开发驱动的情况下测试、调试驱动程序的,而我是用EVC4开发的,没有那个什么(名字忘记了:P),没法调试。当研究一些驱动的源代码的时候,最开始看到的那些 DEBUGMSG、DEBUGZONE还有个叫什么什么tail的那些宏,其实都是在PB的调试环境下用的, 功能应该是类似TRACE之类的,打印一些信息到Output窗口的,因为没有那个调试环境,所以这些东东都没法用了,因此要看我的驱动加载过程中的一些信息,要么就是打印到文件,要么就是用MessageBox了,我选择用MessageBox直观的显示,呵呵,笨笨的办法还是很好用的,跟设断点似的。
     下面继续说USBDeviceAttach函数,其第二个参数lpUsbFuncs,这个是一个函数指针数组,有点vtable的味道,具体的看看USB_FUNCS这个结构的声明就差不多了,在MSDN中也能够通过这个结构查看其所有指向的函数的调用方法及用途。在驱动程序中,往往要用到这个vtable中的很多函数,所以我们需要把这个vtable保存下来备用,如何保存和备用我会在下篇中写明白,在Attach过程中还需要保存很多有用的东东。
     那么继续往下,lpInterface,一个指向USB_INTERFACE的指针,我一直对这个参数没太弄明白,我在这个函数里面得到的这个指针是一个空指针,而看别人的代码中间,当这个指针为空的时候attach是返回FALSE的,显然对我这种情况是不适用的,我后来想想,觉得大概是因为我的外设Interface的class、subclass、protocol都是0,所以才出现这种情况吧(准确的说是因为我在USBInstallDriver函数中,RegisterClientDriverID调用给的参数USB_DRIVER_SETTINGS结构体中关于Interface的几个变量值我全给的USB_NO_INFO,我后来尝试赋值为0,结果就得到了非空的Interface指针)。那么对于我这种情况,interface是个空指针该怎么办呢?可以用USB_FUNCS中的lpFindInterface来“找出”合适的Interface指针,具体的用法还是看官自己研究MSDN吧。
     其实猛地一下蹦出一个Interface的概念,估计初次接触的都会有点糊涂,我当时也很糊涂,Interface在现如今含义太多了,不过可以肯定这里的不是COM中的Interface~:),在查阅资料的时候,我找到了它的确切定义:
 USB peripheral devices consist of one or more logical components that implement the abilities of the devices. These components are called interfaces.Each interface typically provides some useful grouping of functionality, but exactly what constitutes an interface is an implementation detail. For example, a USB mouse device could present one interface for horizontal and vertical movement information and a separate interface for left and right button information. As another option, the device could present a single interface containing all of the information. Both are valid approaches, but each approach has implications for how the device driver must operate.
这段话我就不翻译了,本来英文就不怎么地,翻译过来有误导之嫌,还是留给大家原汁原味的比较好。
    其实研究wince的驱动,或者单纯的讲USB驱动,还是应该了解一下wince下USB的驱动模型的,貌似很简单的一个模型,但是好像还没有能够找到比较精辟的阐述讲解,看着MSDN能够让你看睡着了也不知所云,只能是边研究边体会,我很想在我的文章里对这个模型进行一番讲解,但是发现自己也没有理解到能够给别人讲解的地步。
    好了,不废话了,继续就Interface这个指针继续往下谈,我看了PB下的USB Printer的驱动源码,在这个阶段它调用了SetInterface这个函数,我也依葫芦画瓢,调用了,但却阻塞在这个调用上不能继续,至今我仍不知道是什么原因。这也可以说是我目前的疑问点之一,文中我用特殊颜色标记出来,有朋友能够解疑释惑的可以和我联系,我自己研究出来了,以后也会在后记中加上其答案。
    其它的似乎就没有太多好说的了,直接在MSDN中间都能够看懂是干什么,今天就先写到这里,下次再写的内容就和我的外设有很直接的关系了,只能是根据我外设的具体情况介绍我探索驱动开发的经历。我的外设还算比较简单的,只有两个BULK的EndPoint,什么是EndPoint?呵呵~~留给看官自己研究下吧~

BTW:经过几天的摸索,我终于完成了我的外设的驱动开发,看着测试程序成功的打开设备,写数据又读数据,心中无比欣慰~~不过由于写程序的时候是摸着石头过河,而且到后来才如愿以偿的看到了PB下USBPrinter的源代码,才发现自己的程序结果实在有点混乱。这两天再调整调整,USBPrinter的源代码中果然还是有不少可以借鉴的东西。

2007.8.6后记:“好”日子差不多又要到头了,这次驱动开发的成果在我看来才只是刚刚可用而已,已经调配我做别的事情了,这方面的研究又要被停止下来了。之后一段时间估计很难抽出时间自己继续深入研究了,回头看看自己写的东西居然没有介绍LPBOOL fAcceptControl这个参数,不过好在介绍这个参数的文章也比较多,简单来说,它是一个输出参数。当把它指向的那个变量赋值为真的时候,我们的设备驱动程序就取得了设备的控制权了,系统也就不会再继续为之寻找匹配的驱动了(我的理解是这样,不知是否有错误)。这里小小的后记补充一下。入门记录(四)可能会在更晚的时候,抽时间纪录下来,希望到时我还记得我要写些什么~:)