您的位置:首页 > 其它

windows CE下的USB设备驱动程序分析(二)

2009-01-21 10:41 288 查看
在上次了解了所有USBD接口函数以后,我们已经有了很多基础知识了,回顾USB样例的文件夹结构,我们还能记得USB/CLASS/COMMON这个文件夹下是存放所公共部分的源程序,它是微软专门抽象出来的能为大多数USB设备驱动程序服务的一些结构体以及函数的封装,我们这次再来概略的了解一下这里面的源程序。
这里面包含了三个程序,分别是:
remlock usbclient utils
下面我们分别来了解一下这三个程序的功能和接口,很显然,USB设备驱动程序肯定是会用到这其中的一部分函数的,因此我们不一定需要读懂这其中的每一行,但至少要对这些函数有个印象,不至于在读驱动程序时不知道函数的来源。
remlock程序是一个移除设备的锁,利用这个结构体
typedef struct _REMOVE_LOCK
{
BOOL Removed;
LONG IoCount;
HANDLE RemoveEvent;
} REMOVE_LOCK, *PREMOVE_LOCK
来实现在设备移除时进行的同步控制。其中Removed成员是对设备是否已经移除的标识,IoCount成员是对设备进行访问的数量,这也是驱动程序中常用的行为,就像此前我们看到的那个Lock程序一样,RemoveEvent是一个内核事件,熟悉WIN32编程的应该都很清楚,它是内核通知应用程序的一种方式,也是线程这间并发控制的一种手段,如果不熟悉,还是像我在以前文章中提到的那样,一定要找WINDOWS高级编程之类的书把它学明白,否则就很难控制驱动程序了。
利用它实现的那几个函数就不说了,与临界区的用法是一样的。另外提一句,在此程序中有类似InterlockedIncrement这样的函数,这种函数是WIN32 API函数,专门用来提供多线程对同一变量的同步访问的,可以通过MSDN查到详细用法。
usbclient程序是对USBD进行包装以供USB设备驱动程序使用的函数接口,通过usbclient.h我们可以发现里面是关于数据传输、属性设置、状态描述和复位的一组函数原形的定义,我们再看usbclient.c文件,这些函数大部分都拥有一个LPCUSB_FUNCS类型的参数,回顾上次我们对USBD的了解可知,正是通过这一参数才能访问USBD提供的服务功能,浏览一下函数的实现发现,确实每个函数都是通过这个参数调用了USBD的函数,然后处理调用后的结果,所以这里只是多了一层封装,使得驱动程序的编写更加清晰易于维护。
另外,这里我们要留意一下IssueBulkTransfer()、IssueInterruptTransfer()、IssueVendorTransfer() 这三个函数,它们实现了通用的Bulk传输、中断传输和自定义的传输方式,在驱动程序中要用得到。
utils程序很简单,是对注册表操作的封装,利用_REG_VALUE_DESCR这个结构体和GetSetKeyValues()函数可以方便的访问注册表,在驱动程序的安装中会用得较多。
又说了这么多东西,虽然没有看多少程序,但我们又离驱动程序近了一层,至少知道了很多函数是要在驱动程序中用到的,如果有兴趣,可以具体阅读每一个函数的实现方法,但我觉得这并不影响对驱动程序的开发。如果是我写驱动,在没有特别的情况下,我会把这些公用的源程序照搬过来,这可是能极大的缩短开发周期的事哦!
正如所料,接下来我们就进入到DRIVERS/USB/CLASS/STORAGE/CLASS文件夹下,接触USB设备驱动程序。
我们先来了解两个头文件,分别是STORAGE/INC/usbmsc.h和STORAGE/CLASS/usbmscp.h,其中前者是USB存储设备公用的头文件,后者是需要按照自己的设备更改的头文件。我们先来看前者。

在usbmsc.h这个头文件中,前边定义了很多常量,包括子类和协议的常量,这是从哪里来的呢?前文我们已经提到过,这些量值是依据USB设备规范得来的,在规范上都作了定义,所以此处的值必须与USB规范中的相一致。再向下的命令块结构体和数据块结构体是用来与USB设备通讯用的,可以通过这两个结构体的实例与USB设备传输数据。 下面的函数原型就不说了,前文提到过,在这里只记得有这几个函数就行了。
再来看usbmscp.h这个头文件,这个头文件是要按照自己的需要和USB设备来进行修改的,比如DRIVER_NAME_SZ是驱动程序的名字,RESET_TIMEOUT 是一个超时的默认值。还有很重要的一个就是USBMSC_DRIVER_SETTINGS的设置,这个设置是与USBDI.H中的USB_DRIVER_SETTINGS结构体一一对应的,为了符合我自己的设备,通常要把dwVendorId和dwProductId等设置成设备的对应值,比如我的U盘的VendorID是0x058F,ProductID是0x9321,那我就会把这两个值对应的写在相应的位置上。同时在系统注册表中也会利用这两个值修改注册表的键以便设备管理器可以顺利的找到我的设备驱动。
下面还有一个_USBMSC_DEVICE结构体,它是用来描述你自己的USB存储设备的,是封装了USBD函数表指针、磁盘设备指针、管道和配置项的最重要的数据结构,在驱动程序实现上此数据结构就是重点的参数,鉴于样例程序对每一个结构体元素都作了明确的注释,此处我就不一一描述了,它就像C++中的类一样,是最后把一些小类组合起来的可以最终使用的结构。
好了,对这两个头文件有所了解以后,我们就进入最关键的部分,源程序。我们接下来来看usbmsc.c这个文件。为什么要先看这个文件而不是同一文件夹下的其他几个文件呢?我来解释一下。在这个文件夹中有一个usbmsc.def的文件,大家都知道它是定义了导出函数的,通常与它同名的程序文件都会含有DllEntry的入口,既然入口在这,那我们自然就先来看这个文件了。如果用到了其他的文件,再看不迟。
这可是一个有1000多行的源程序,但不要害怕,我们只看最主要的,别的函数的实现你可以自己去研究。首先看到文件的DllEntry入口之前有5个函数原型的定义,从函数名上就可以知道这个函数的功能了,很显然这几个函数是程序实现过程中被调用的,所以目前知道功能就行了,不用了解实现方法。忘了说一句,这个程序中包含了bot.h和cbit.h两个头文件,可见程序中是要用到它们的功能的,不过先不管它,继续往下看。
DllEntry入口函数的下面,就是USBInstallDriver()这个函数了,它的作用是进行与USB设备相关的注册表操作,主要的语句是:
bRc = RegisterClientDriverID( wsUsbDeviceID );
bRc = RegisterClientSettings( szDriverLibFile, wsUsbDeviceID, NULL, &usbDriverSettings );
即先注册设备类别,然后是设备细节。 同样,USBUnInstallDriver()函数是以相反的顺序解除注册信息的。 这几个与注册有关的函数在前面我们提到过,是由USBD接口提供的,这里我们可以看到USBD对设备驱动程序的重要性。
在继续向下看,我们发现了USBDeviceAttach()函数,这可是最重要的地方了,当有USB设备插入插口以后,操作系统是如何识别它的呢,如何将其做为一个文件夹加以访问的呢?我们就来解开这里的谜团。
为了我们方便说明,我将此程序简化如下:



后面的程序将以此行号进行说明。
我们来看程序的第4行,这里有一个判断语句,它是在判断插入的设备是否是USBMSC_INTERFACE_CLASS类型的,这个常量是在usbmsc.h文件中定义的,也就是说如果设备不是USB存储设备,那么就结束这个函数,也就是此驱动只能处理USB存储设备。
当发现设备符合此驱动程序的要求后,就通过函数ParseUsbDescriptors()来解析这个设备,这个函数在下面的程序中将被实现,我们可以看一下该函数的函数体,很显然,它是在为设备进行各种配置,这就不多说它了。
再往下,分配内存,设置标志,从注册表中读取信息。注意,这里读取到的注册表信息是Drivers//USB//ClientDrivers//Mass_Storage_Class和bInterfaceSubClass变量组合成的注册表键下的值,具体可参阅源程序,这个注册表键下放置的内容是
[HKEY_LOCAL_MACHINE/Drivers/USB/ClientDrivers/Mass_Storage_Class/6]
"DLL"="USBDISK6.DLL"
"Prefix"="DSK"
"Folder"="USB Disk"
"IOCTL"=dword:4
"IClass"="{A4E7EDDA-E575-4252-9D6B-4195D48BB865}"
由此可以看出,通过此处的注册表读取,驱动程序可以知道这个设备将通过哪种形式以及哪个DLL向操作系统提供接口。同时也为后续的操作进行了准备。
最关键的部分就在接下来的LoadDriver()那句,加载了另一个驱动程序的DLL文件,就是上述注册表中的USBDISK6.DLL文件,计数器增一,取到该文件中UsbDiskAttach函数及UsbDiskDetach函数的地址,注册事件通知处理函数,然后调用了该DLL文件中的UsbDiskAttach函数。
由此可见,USB设备驱动程序有两层功能,一方面是识别出指定的设备并进行配置,另一方面按照要求调用更高层的驱动程序来向操作系统提供接口。当调用了USBDISK6.DLL后,操作系统就会按该文件中的程序以一个磁盘的形式或文件夹的形式进行处理,通过文件系统的操作,就可以对其进行读写控制了。我们也可以看一下HID设备的这个函数,它也是通过这种方式让操作系统知道把USB设备识别成鼠标设备的。
前文我们说过还有一个通知消息的回调函数,我们在刚才的程序体中已经发现通过:
UsbFuncs->lpRegisterNotificationRoutine( hDevice, UsbDeviceNotify, pUsbDevice );
语句已经对这个函数进行了设置。我们再向下来看一下这个函数的函数体。这个函数很简单,只要对USB_CLOSE_DEVICE消息进行处理,既然是要关闭USB设备,那么调用USBDISK6.DLL中的Detach函数是必须的,让上层的驱动程序进行释放,然后将引用计数减一,如果不再有设备引用此驱动程序,则FreeLibrary(),仅此而已。
其余的函数可以再仔细研究一下,在此就不详细描述了,接下来我们要弄明白的就是到底操作系统是如何通过抽象的DISK读写具体的设备呢?
带着上次留下的疑问,我们继续来学习操作系统如何通过USBDISK读写USB设备的。我们先看USB/CLASS/STORAGE/DISK/SCSI2/usbdisk6.def文件。在这个文件中可以看到,该DLL一共导出了14个函数,其中两个是上次内容当中被设备驱动程序调用的UsbDiskAttach和UsbDiskDetach,余下的是一组以DSK开头的流驱动接口,易见,USBDISK是以流驱动的形式向操作系统提供服务的。
为了清晰起见,以下大量的程序我们并不学习,而只关心设备读写,因此我们来看DISK.C这个程序文件。找到DSK_Read和DSK_Write两个函数,令我们大失所望,因为这两个函数都是形如
UNREFERENCED_PARAMETER(pDevice);
UNREFERENCED_PARAMETER(pBuffer);
UNREFERENCED_PARAMETER(BufferLength);

DEBUGMSG(ZONE_ERR,(TEXT("DSK_Read/n")));
SetLastError(ERROR_INVALID_FUNCTION);
return 0;
这样的实现,也就是说用户无法通过常规的ReadFile和WriteFile函数使用这个设备,那怎么办?是否意味着这个DISK无法读写呢?当然不是,我们应该马上想到DSK_IOControl()这个函数,当遇到某些设备无法用常规的文件操作函数操作时,我们有DeviceIoControl()用户函数可以使用,而这个函数就会调用到驱动程序中的DSK_IOControl函数。
在这个函数中,我们找到了对IOCTL_DISK_READ等命令的处理程序,其中最关键的一句就是ScsiRWSG(pDevice, pSgReq, pDevice->Lun, bRead),即调用了一个ScsiRWSG的函数。
在Scsi2.c这个程序中,我们找到了这个函数,其中SG指的是一种读写缓冲区的数据结构,实际上就是带有缓冲区及长度的一个结构体,是CE下磁盘设备通用的读写数据结构,可以在diskio.h中找到它的定义。在这个函数中我们发现它再次调用了ScsiReadWrite()这个函数进行读写操作,找到这个函数,里面有我们最重要的一行调用,即调用了UsbsDataTransfer()函数,还记得这个函数在哪见过吗?没错,就是在USB设备的驱动程序当中。
通过这一过程我们发现,那些Scsi的函数都只是在准备一些缓冲区、数据结构等,并没有对硬件进行操作,真正要操作硬件设备的还是由驱动程序来完成的,可见,设备驱动程序是有着很强层次结构的,下层是专门针对物理设备的,上层是针对操作系统的抽象设备的,下层是U盘等物理实体,上层是文件夹,二者通过一定的通信或调用机制完成了设备在操作系统下的正常工作。
回到usbmsc.c程序中来,找到UsbsDataTransfer函数,这个函数很简单,根据传输协议调用CBIT_DataTransfer()或BOT_DataTransfer() 即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: