分类:
2011-10-12 15:13:21
开发usb驱动程序的方法(连载一)
开始驱动程序设计
下面的文字是从Microsoft的DDK帮助中节选出来的,它让我们明 白在开始设计驱动程序应该注意些什么问题,这些都是具有普遍 意义的开发准则。应该支持哪些I/O请求在开始写任何代码之前, 应该首先确定我们的驱动程序应该处理哪些IRP例程。
如果你在设计一个设备驱动程序,你应该支持和其他相同类型 设备的NT驱动程序相同的IRP_MJ_XXX和IOCTL请求代码。
如果你是在设计一个中间层NT驱动程序,应该首先确认你下层 驱动程序所管理的设备,因为一个高层的驱动程序必须具有低层 驱动程序绝大多数IRP_MJ_XXX例程入口。高层驱动程序在接到I/O 请求时,在确定自身IRP当前堆栈单元参数有效的前提下 ,设置好IRP中下一个低层驱动程序的堆栈单元,然后再调用IoCallDriver 将请求传递给下层驱动程序处理。
一旦决定好了你的驱动程序应该处理哪些IRP_MJ_XXX,就可以开始 确定驱动程序应该有多少个Dispatch例程。当然也可以考虑把某些 RP_MJ_XXX处理的例程合并为同一例程处理。例如在ChangerDisk和 VDisk里,对IRP_MJ_CREATE和IRP_MJ_CLOSE处理的例程就是同一函数。 对IRP_MJ_READ和IRP_MJ_WRITE处理的例程也是同一个函数。
应该有多少个Device对象?
一个驱动程序必须为它所管理的每个可能成为I/O请求的目标的物理和逻辑设备创建一个命名Device对象。一些低层的驱动程序还可能要创建一些不确定数目的Device对象。例如一个硬盘驱动程序必须为每一个物理硬盘创建一个Device对象,同时还必须为每个物理磁盘上的每个逻辑分区创建一个Device对象。
一个高层驱动驱动程序必须为它所代表的虚拟设备创建一个Device 对象,这样更高层的驱动程序才能连接它们的Device对象到这个驱动程序的Device对象。另外,一个高层驱动程序通常为它低层驱动 程序所创建的Device对象创建一系列的虚拟或逻辑Device对象。
尽管你可以分阶段来设计你的驱动程序,因此一个处在开发阶段的 驱动程序不必一开始就创建出所有它将要处理的所有Device对象。 但从一开始就确定好你最终要创建的所有Device对象将有助于设计者所要解决的任何同步问题。另外,确定所要创建的Device对象还有助于你定义Device对象的Device Extension的内容和数据结构。
开始驱动程序开发
驱动程序的开发是一个从粗到细逐步求精的过程。NT DDK的src\ 目录下有一个庞大的样板代码,几乎覆盖了所有类型的设备驱动程序、高层驱动程序和过滤器驱动程序。在开始开发你的驱动程序之前,你应该在这个样板库下面寻找是否有和你所要开发的类似类型的例程。例如我们所开发的驱动程序,虽然DDK对USB描述得不是很详细,我们还是可以在src\storage\class目录发现很多和USB设备有关的驱动程序。下面我们来看开发驱动程序的基本步骤。
最简的驱动程序框架
1、 写一个DriverEntry例程,在里面调用IoCreateDevice创建 一个Device对象。
2、 写一个处理IRP_MJ_CREATE请求的Dispatch例程的基本框架 (参见DDK Kernel-Mode Drivers 4.4.3描述的一个DispatchCreate 例程所要完成的最基本工作。当然写了DispatchCreate例程后, 要在DriverEntry例程为IRP_MJ_CREATE初始化例程入口)。如果驱动程序创建了多于一个Device对象,则必须为IRP_MJ_CLOSE 请求写一个例程,该例程通常情况下可以和DispatchCreate共用一个例程,参见参见DDK Kernel-Mode Drivers 4.4.3。
3、 编译连接你的驱动程序。
用下面的方法来测试你的驱动程序。
首先按上面介绍的方法安装好驱动程序。
其次我们还得为NT逻辑设备名称和目标Device对象名称之间建立 起符号连接,我们在前面已经知道Device对象名称对WIN32用户模式 是不可见的,是不能直接通过API来访问的,WIN 32 API只能访问NT 逻辑设备名称。我们可以通过修改注册表来建立这两种名称之间的符 号连接。运行REGEDT32.EXE在\HKEY_LOCAL_MACHINE\ System\ CurrentControlSet\Control\ Session Manager\ DOS Devices下建立起符号连接(这种符号连接也可以在驱动程序里调用函数 IoCreateSymbolicLink来创建)。
重新启动系统。
编写一个简单的测试程序调用WIN32API CreateFile函数以刚才你命名的NT逻辑设备名打开这个设备。如果打开成功,那么你也就成功地写出了一个最简单的驱动程序了。
支持更多的设备I/O请求
例如你的驱动程序可能需要对IRP_MJ_READ请求做出响应(完成后可用WIN32 API ReadFile函数进行测试)。如果你的驱动程序需要能够手工卸载,那么还必须对IRP_MJ_CLOSE做出响应。为你所需要处理IRP_MJ_XXX写好处理例程,并在DriverEntry里面初始化好这些例 程入口。
一个低层的驱动程序可能需要最起码一个StartIo,ISR和DpcForIsr 例程,可能需要一个SynchCritSection例程,如果设备使用了DMA, 那么可能还需要一个AdapterControl例程。关于这些例程,请参考 DDK相应文档。
对于高层驱动程序可能需要一个或多个IoCompletion例程,最起码 完成检查I/O状态块然后调用IoCompleteRequest的工作。 如果需要,还要对Device Extension数据结构和内容做些修改
开发usb驱动程序的方法(连载二)
NT还有更多其他的对象,例如中断对象、Controller对象、定时器对象等等,但在我们开发的驱动程序中并没有用到,因此在这里不做介绍。
I/O缓冲策略
很明显的,驱动程序和客户应用程序经常需要进行数据交换,但我们知道驱动程序和客户应用程序可能不在同一个地址空间,因此操作系统必须解决两者之间的数据交换。这就就设计到设备的I/O缓冲策略。
读写请求的I/O缓冲策略
前面说到通过设置Device对象的Flag可以选择控制处理读写请求的I/O缓冲策略。下面对这些缓冲策略分别做一介绍。
1、缓冲I/O(DO_BUFFERED_IO)
在读写请求的一开始,I/O管理器检查用户缓冲区的可访问性,然后分配与调用者的缓冲区一样大的非分页池,并把它的地址放在IRP的AssociatedIrp.SystemBuffer域中。驱动程序就利用这个域来进行实际数据的传输。
对于IRP_MJ_READ读请求,I/O管理器还把IRP的UserBuffer域设置 成调用者缓冲区的用户空间地址。当请求完成时,I/O管理器利用 这个地址将数据从驱动程序的系统空间拷贝回调用者的缓冲区。对 于IRP_MJ_WRITE写请求,UserBuffer被设置为NULL,并把用户缓冲 区的数据拷贝到系统缓冲区中。
2、 直接I/O(DO_DIRECT_IO)
I/O管理器首先检查用户缓冲区的可访问性,并在物理内存中锁定它。然后它为该缓冲区创建一个内存描述表(MDL),并把MDL的地址 存放在IRP的MdlAddress域中。AssociatedIrp.SystemBuffer和 UserBuffer都被设置为NULL。驱动程序可以调用函数 MmGetSystemAddressForMdl得到用户缓冲区的系统空间地址,从而 进行数据操作。这个函数将调用者的缓冲区映射到非份页的地址空 间。驱动程序完成I/O请求后,系统自动从系统空间解除缓冲区的映射。
3、 这两种方法都不是
这种情况比较少用,因为这需要驱动程序自己来处理缓冲问题。 I/O管理器仅把调用者缓冲区的用户空间地址放到IRP的UserBuffer 域中。我们并不推荐这种方式。
IOCTL缓冲区的缓冲策略
IOCTL请求涉及来自调用者的输入缓冲区和返回到调用者的输出 缓冲区。为了理解IOCTL请求,我们先来看看WIN32 API DeviceIoControl函数的原型。
BOOL DeviceIoControl (
HANDLE hDevice, // 设备句柄
DWORD dwIoControlCode, // IOCTL请求操作代码
LPVOID lpInBuffer, // 输入缓冲区地址
DWORD nInBufferSize, // 输入缓冲区大小
LPVOID lpOutBuffer, // 输出缓冲区地址
DWORD nOutBufferSize, // 输出缓冲区大小
LPDWORD lpBytesReturned, // 存放返回字节数的指针
LPOVERLAPPED lpOverlapped // 用于同步操作的Overlapped结构体指针
);
IOCTL请求有四种缓冲策略,下面一一介绍。
1、 输入输出缓冲I/O(METHOD_BUFFERED)
I/O管理器首先分配一个非分页池,它足够大地存放调用者的输入或输出缓冲区(不管哪个更大)。非分页缓冲区的地址放在IRP的AssociatedIrp.SystemBuffer域中,然后把IOCTL的输入数据拷贝 到这个非份页缓冲区中,并把IRP的UserBuffer域设置成调用者输出缓冲区的用户空间地址。当驱动程序完成IOCTL请求时,I/O管理器将这个非份页缓冲区中的数据拷贝到调用者的输出缓冲区。注意这里同一个非份页池同时用于输入和输出缓冲区,因此驱动程序在向缓冲区写东西之前应该把输入的所有数据读出来。
2、 直接输入缓冲输出I/O(METHOD_IN_DIRECT)
I/O管理器首先检查调用者输入缓冲区的可访问性,并在物理内存中将其锁定。然后为该输入缓冲区创建一个MDL,并把指定该MDL的指针存放到IRP的MdlAddress域中。同时,I/O管理器还在非份页池中分配一输出缓冲区,并把这个缓冲区的地址存放在IRP的AssociatedIrp.SystemBuffer域中,并把IRP的UserBuffer域设置成调用者输出缓冲区的用户空间地址。当驱动程序完成IOCTL请求时,I/O管理器将非份页缓冲区中的数据拷贝到调用者的输出缓冲区。
3、 缓冲输入直接输出I/O(METHOD_OUT_DIRECT)
I/O管理器首先检查调用者输出缓冲区的可访问性,并在物理内存中将其锁定。然后为该输出缓冲区创建一个MDL,并把指定该MDL的指针存放到IRP的MdlAddress域中。同时,I/O管理器还在非份页池中分配一输入缓冲区,并把这个缓冲区的地址存放在IRP的AssociatedIrp.SystemBuffer域中, 同时把调用者用户输入缓冲区中的数据拷贝到系统缓冲区中,并把IRP的 UserBuffer域设置为NULL。
4、 上面三种方法都不是(METHOD_NEITHER)
I/O管理器把调用者的输入缓冲区的地址放到IRP当前I/O堆栈单元的Parameters.Devi ceIoControl.TypeInputBuffer域中,把输出缓冲 区的地址存放到IRP的UserBuffer域中。这两个地址都是用户空间地 址。
从上面的说明可以看出,在执行缓冲I/O时,I/O管理器将在非份页池 中分配内存,如果调用者的缓冲区比较大时,分配的非份页池也将 比较大。非份页池是系统比较宝贵的资源,因此,如果调用者的缓 冲区比较大时,我们一般采用直接I/O的方式(例如磁盘读写请求等), 这样不仅节省系统资源,另一方面由于省去了I/O管理器在系统缓冲 区和调用者缓冲区之间的数据拷贝,也提高了效率,这对存在大量 数据传送的驱动程序尤其明显。
可以注意到DDK中的Samples下,几乎所有的例程的读写请求都是直 接I/O的,而对于IOCTL请求则是缓冲区I/O的居多
开发usb驱动程序的方法(连载三)
NT驱动程序的分层结构
驱动程序是指管理某个外围设备的一段程序代码。NT采用更灵活的分层驱动方法,允许杂应用程序和硬件之间 存在几个驱动程序层次。分层机制允许NT更加广泛地定义驱动程序,包括文件系统、逻辑卷管理器和各种网络组件,各种物理设备驱动程序等等。
1、 设备驱动程序
这些是管理实际数据传输和控制特定类型的物理设备的操作的驱动程序,包括开始和完成I/O操作,处理中断和执行特定的设备要求的任何差错处理。
2、 中间驱动程序
NT允许在物理设备驱动程序上分层任意数目的中间驱动程序。这些中间层次提供扩展I/O系统的功能一种方法,而不必修改底层的驱动程序。这也是微软鼓吹的他们的系统灵活的一面!实际上我觉得这样反而牺牲了一些效率上的东西。
3、 文件系统驱动程序(FSD)
FSD是一类比较特殊的驱动程序,通常负责维护各种文件系统 所需要的磁盘结构。注意我们并不能使用DDK来开发FSD,而必须使用Microsoft的文件系统开发人员工具包。
一般比较少写中间过滤驱动程序,过滤驱动程序它截获和修改高层发送给类驱动程序的请求。这样就允许利用现有类驱动程序的功能,而不必从头开始写所有程序。NT内核模式对象在我们的实际开发过程中的对象是设备,由于端口驱动程序已经隐藏了硬件控制操作,因此我在这里不讲述跟硬件相关的部份。如果今后的开发对象不同,需要对硬件进行操作的时候,可能会对中断、DMA等有比较详细的了解,这些内容可以参考DDK帮助。
usb设备开发实例
通用串行总线(Universal Serial Bus USB),是一种快速、灵活的总线接口。与其它通信接口比较,USB接口的最大特点是易于使用,这也是USB的主要设计目标。作为一种高速总线接口,USB适用于多种设备,比如数码相机、MP3播放机、高速数据采集设备等。易于使用还表现在USB接口支持热插拔,并且所有的配置过程都由系统自动完成,无需用户干预。
USB接口支持1.5Mb/s(低速)、12Mb/s(全速)和高达480Mb/s(USB 2.0规范)的数据传输速率,扣除用于总线状态、控制和错误监测等的数据传输,USB的最大理论传输速率仍达1.2Mb/s或9.6Mb/s,远高于一般的串行总线接口。
USB接口芯片价格低廉,一个支持USB 1.1规范的USB接口芯片价格大多在$1~2之间,跟一个232或485接口芯片价格差不多,这也大大促进USB设备的开发与应用。
在进行一个USB设备开发之前,首先要根据具体使用要求选择合适的USB控制器。目前,市场上供应的USB控制器主要有两种:带USB接口的单片机(MCU)或纯粹的USB接口芯片。
带USB接口的单片机从应用上又可以分成两类,一类是从底层设计专用于USB控制的单片机,比如Cypress公司的CY7C63513(低速)、CY7C64013(全速),但由于价格、开发工具以及单片机性能有限等问题,所以一般不推荐选用。另一类是增加了USB接口的普通单片机,例如Intel公司的8X931(基于8051)、8X930(基于高速、增强的8051)、Cypress公司的EZ-USB(基于8051),选择这类USB控制器的最大好处在于开发者对系统结构和指令集非常熟悉,开发工具简单,但对于简单或低成本系统,价格高将会是最大的障碍。一般来说,后者的价格是前者价格的10倍。
纯粹的USB接口芯片仅处理USB通信,必须有一个外部微处理器来进行协议处理和数据交换。典型产品有Philips公司的PDIUSBD11(I2C接口)、PDIUSBD12(并行接口),NS公司的USBN9603/9604(并行接口),NetChip公司的NET2888等。USB接口芯片的主要特点是价格便宜、接口方便、可靠性高,尤其适合于产品的改型设计(硬件上仅需对并行总线和中断进行改动,软件则需要增加微处理器的USB中断处理和数据交换程序、PC机的USB接口通信程序,无需对原有产品系统结构作很大的改动)。
在选定USB控制器以后,如果是带USB接口的单片机,则是一般单片机应用系统的开发;反之,就是如何把USB接口芯片与单片机应用系统融合的问题,一般USB接口芯片都支持多种并行总线结构(复用/非复用),可以方便的与多种单片机接口。硬件设计中要注意的就是USB接口芯片的时钟速度比较高,如果芯片内部没有PLL来倍频,则外部晶体振荡电路(多数在48MHz)的设计就应该特别注意,包括晶体的选择(负载电容大小)、匹配网络的设计以及PCB布线。
USB设备的软件设计主要包括两部分:一是USB设备端的单片机软件,主要完成USB协议处理与数据交换(多数情况下是一个中断子程序)以及其它应用功能程序(比如A/D转换、MP3解码等)。二是PC端的程序,由USB通信程序和用户服务程序两部分组成,用户服务程序通过USB通信程序与系统USBDI(USB Device Interface)通信,由系统完成USB协议的处理与数据传输。PC端程序的开发难度比较大,程序员不仅要熟悉USB协议,还要熟悉Windows体系结构并能熟练运用DDK工具。
USB接口软件主要完成USB协议的处理和数据的交换,一定要严格遵循USB2.0规范第九章的规定(详见 Universal Serial Bus Specification Revision 2.0 : Chapter 9.USB Device Framework )。
要快捷、成功的开发一个USB设备,正确、合理的调试方法是必不可少的环节。调试基本分三步进行:首先对外部设备(单片机部分)借助PC调试软件(芯片生产商提供或从网上下载WINRT-USB、Kernel Driver等调试软件)将设备端的USB协议(主要有描述符请求、端口配置、地址设置以及基本数据交换)调通。然后,用调试好的USB设备接口来开发、调试PC软件,这一步相对比较容易。最后,加上USB设备端的其它用户程序,对整个完整的系统进行系统调试。
下面从硬件、软件两方面具体介绍作者设计的一个便携式USB数据采集设备,重点介绍USB接口部分。该数据采集系统以AD公司的带8通道12位A/D、2路12位D/A的52内核单片机ADuC812作为系统控制器,采用Philips公司的PDIUSBD12作为USB接口芯片。由于系统中需要断电后保存采集数据,扩展了两片28F040,因此在这里把PDIUSBD12与CPU的接口采用了总线复用方式,通过ALE信号把数据分离出来,并把低64K RAM空间全留给PDIUSBD12(ADuC812的RAM空间有1M,分页管理,每页64K,共256页,对应DPP寄存器值0~255,PDIUSBD12占第0页,即DPP=0),地址线A(P2.0)作为PDIUSBD12的指令/数据选择线,则地址000100H写指令、000000H读写数据;单片机的P3.5口线提供PDIUSBD12的复位信号,接非门是保证单片机复位时PDIUSBD12也复位。PDIUSBD12与单片机的数据交换采用中断方式(INT0),实际应用中如果系统中断资源不够(特别是系统改型设计时),也可以接成查询方式,只是注意查询间隔不要超过USB接口的最大等待时间(最大500mS)。PDIUSBD12的GOOD-LINK指示灯(LED)在USB通信时会闪烁,常亮或一直不亮说明USB接口有问题,调试时非常有用。PDIUSBD12采用PLL倍频产生系统时钟,只需外接低频晶体,PCB设计比较方便。
单片机软件设计主要注意以下几点:
* PDIUSBD12的中断输出引脚只要中断寄存器不为0就保持低电平,所以单片机的对应中断(INT0)应设置成电平触发;中断处理后要用读上次传输状态寄存器清除中断寄存器中对应位(D0-D5)。
* DIUSBD12靠软件控制USB端口的连接,程序在系统初始化处理完后软件设置连接到USB端口,然后开中断。
* PDIUSBD12对内部寄存器的读写没有边界限制,程序设计中一定不要读写超过端点深度的数据。特别对于描述符请求,由于其长度大于Control
IN 深度(16 Bytes),要分几个数据周期传输。
描述符一定要设置正确,并且注意USB协议中所有字数据均定义为低字节在前传输(LSB),例如Phlips的ID为471H,应在iDVendor中定义成71H、04H。
* 在接收到Setup包后,一定要用ACK Setup指令来重新使能Control IN 和Control
OUT端点。向IN端点写数据后,要用Validate
Buffer指令使数据可以在下一个IN数据周期发送。从OUT端点读数据后,要用Clear
Buffer指令来清空缓冲区,否则后面OUT周期传输的数据将被丢弃(返回NAK)。
*协议的处理一定要按USB规范要求进行,对无效请求,用Set Endpoint Status指令将Control IN和Control
OUT端点Stall即可。
PC机软件作者用VC6.0开发,分USB接口通信程序和应用程序两部分,其开发以及系统调试过程与前文所述相同,此处不再赘述。