分类: LINUX
2008-01-18 15:37:34
当然,也有很多人觉得用DriverStudio不够正宗,或者说不能很好的理解Windows Device Driver的架构。我感觉这就有点像MFC和SDK的关系,关于这个问题在很多地方都有争论,比如在万千新闻组上,就讨论了将近2个月。每个人都有自己 的最爱,都有自己的习惯,只要你能把事情做好,我想用什么方法应该都是一样的。如果你已经习惯了用DDK开发,那完全还可以继续用下去;如果你觉得 DriverStudio不错,那尝试用一个可以给你按照OOP概念来编程的工具有什么不好呢?
在驱动开发网上,经常看到有人询问一些关于DriverStudio的使用的问题。我正好很有幸用它作了几个驱动程序,包括VXD, KMD和WDM,稍微有点心得,因此想写下来给大家作一个小小的参考。如果其中有错误,欢迎大家给我指出,谢谢。
下面我就介绍一下用DriverStudio开发一个USB驱动程序的过程。这个USB设备有3个双向端点,每个端点的配置如下:
EP 类型 地址 buffer(Bytes)
0 IN/OUT Control 0x80/0x00 16/16
1 IN/OUT Bulk 0x81/0x01 16/16
2 IN/OUT Bulk 0x82/0x02 64/64
我们的驱动程序需要实现的功能就是控制设备上的LED灯的亮和灭,以及通过Endpoint 2对设备进行读写。
由于DriveStudio由几个部分组成,我们写这个驱动程序只要用到DriverWorks,因此下面我们就简称它为DW。在这里,我们假定读者已经正确的安装了DW,并且已经编译好了各个库文件。
1. 首先,我们通过快捷方式“Setup DDK and Start MSVC“来启动VC IDE。这个快捷方式所指向的程序,会进行一些必要的设置,然后再启动VC IDE,这样我们的程序就可以使用DDK和DW的头文件和库了。
2. 从VC IDE的菜单"DriverStudio"中选择"DriverWizard", 在如图1所示的对话框中, 写上项目名称. 在这里, 我们将这个项目称为: TEST, 所在的目录为D:\TEST. 然后点按钮"Next >".
图1
3. 在接下来的这个对话框中(如图2), 我们需要选择驱动程序的类型. 由于USB设备驱动程序是WDM类型的, 所以我们选择第二项并且点按钮"Next >".
图2
4. 在第3个对话框中(如图3), 选择我们的驱动程序所操作的总线类型. 这里, 我们选择USB. 在USB Vendor ID和USB
Product ID中填入USB设备的VID和PID. 假定我们的USB设备的VID和PID分别是16进制的0471和1801.
然后点按钮"Next >". 关于VID和PID的规定请参考USB-IF的规范.
图3
5. 在接下来的对话框中(如图4), 我们需要加入Endpoint 1和Endpoint 2的定义. 由于在USB中规定Endpoint
0是必须存在的, 所以我们不需要对Endpoint 0进行定义. 点"Add..."按钮, 弹出一个如图5所示的对话框.
我们将它修改成如图6所示. 其中, 按照USB的规定, 对于端点, 它的地址是1; 按照前面说明的设备的特点, Endpoint
1的最大的包大小为16字节, 因此在"Max Transer Size"中填入16; Endpoint Name可以通过"Suggest
Name"得到. 按照这些原则, 继续设置其他的配置, 以使对话框4变成如图7所示. 接下来, 继续按"Next >"按钮.
图4
图5
图6
图7
6. 在如图8所示的对话框中, 可以填入我们需要的Driver Class的名字和文件名. 一般我们不需要更改. 继续按"Next >"按钮.
图8
7. 在如图9所示的对话框中, 因为不需要给其他的驱动程序提供接口, 也不需要提供Flush功能, 所以不需要任何修改, 直接按"Next >"按钮.
图9
8. 在如图10所示的对话框中, 我们选择给端点2产生BULK Read的代码, 并且按"Next >"按钮. DW会给我们产生一套对端点2进行读的代码, 不用修改, 就可以直接使用.
图10
9. 在如图11所示的对话框中, 我们选择给端点2产生BULK Write的代码, 并且按"Next "按钮. 这样, DW也会给我们产生一套对端点2进行写的代码, 不用修改, 就可以直接使用.
图11
10. 对于如图12的对话框, 我们直接按"Next >"按钮. 这里是设置是否要将I/O请求排队, 在这里, 我们不需要排队.
图12
11. 在如图13所示的对话框中, 我们不需要创建任何注册表项, 所以直接按"Next >"按钮.
图13
12. 如图14所示的对话框, 是让我们设置一些驱动程序的属性, 比如接口, 缓冲区之类的. 一般的都可以使用缺省设置. 继续按"Next >"按钮.
图14
13. 在如图15所示的对话框中, 是让我们给驱动程序增加一些IOCTL接口. 我们只增加一个如图16所示的IOCTL来控制USB设备的LED灯. 然后按"Next >"按钮.
图15
图16
14. 在最后一个如图17所示的对话框中, 可以设置一些驱动程序的属性, 产生一个console测试程序. 按下"Finish"按钮, 就结束了Wizard.
图17
这样, 我们就创建好了一个基本的驱动程序, 下面来看看还要做哪些工作才可以和我们的设备以及上层的应用程序通讯.
把函数NTSTATUS TESTDevice::TEST_IOCTL_LED_Handler(KIrp I)改成如下面的样子:
NTSTATUS TESTDevice::TEST_IOCTL_LED_Handler(KIrp I)
{
NTSTATUS status = STATUS_INVALID_PARAMETER;
t << "Entering TESTDevice::TEST_IOCTL_LED_Handler, " << I << EOL;
__try
{
// TODO: Verify that the input parameters are correct
// If not, return STATUS_INVALID_PARAMETER
if(I.IoctlOutputBufferSize() || !I.IoctlBuffer() ||
(I.IoctlInputBufferSize() != sizeof(UCHAR)))
__leave;
// TODO: Handle the the ZBUARD_IOCTL_LED_ON request, or
// defer the processing of the IRP (i.e. by queuing) and set
// status to STATUS_PENDING.
PURB pUrb = m_Lower.BuildVendorRequest(
NULL, // transfer buffer
0, // transfer buffer size
0, // request reserved bits
(UCHAR)(*(PUCHAR)I.IoctlBuffer()), // request. 1 = LED_ON, 0 = LED_OFF
0 // Value
);
// transmit
status = m_Lower.SubmitUrb(pUrb, NULL, NULL, 5000L);
}
__finally
{
// TODO: Assuming that the request was handled here. Set I.Information
// to indicate how much data to copy back to the user.
I.Information() = 0;
I.Status() = status;
}
return status;
}
这个函数是控制LED灯的,它是通过USB Vendor Request来向设备传送的。其中,request=1的时候表示让LED亮,request=0的时候让LED灭。它是通过DeviceIoControl由上层应用程序传下来。
再看看读写部分,经过检查NTSTATUS TESTDevice::Read(KIrp I)和NTSTATUS TESTDevice::Write(KIrp I)可以发现,DW已经给我们写好了读写的代码,我们可以直接使用了。这些代码就是在上面的第8和第9步中产生的代码。
最后,修改编译一下DriverStudio产生的测试程序Test_TEST程序,我们就可以通过命令行来测试我们的驱动程序了。对于LED的控 制,我们可以直观的在设备上看到,但对于读写的操作就需要和firmware程序配合,这已经超出了本文的范围,不在这里讨论了。
通过上面的讲解,我们可以看到有了DriverStudio,就可以快速的产生一个驱动程序,然后在里面作一些小的改动就可以使用了。即使是写一个 比较复杂的USB驱动程序,我们也可以不用管一些系统的IRP处理,只要专注于我们自己的特定应用就可以了。而且它把一个驱动程序概括成几个类的概念,并 且DW还附带有一些很有用的STL类,在VC IDE里面有了一个很清晰直观的表示。这样,对一些从上层应用转向驱动程序的开发人员,或者一些对C++/OOP很熟悉但不太了解系统内核的开发人员,都 比较容易上手。即使对于推崇直接用DDK编程的人来说,通过阅读DriverStudio附带的源代码,也可以对驱动程序的开发有一个更加深入的了解。