VS2013 WDK8.1驱动开发9(IO设备控制操作)
本系列博客为学习《Windows驱动开发技术详解》一书的学习笔记。
前言
除了读写设备外,我们还可以通过DeviceIoControl操作设备。
IO设备控制操作
DeviceIoControl内部会使操作系统创建一个IRP_MJ_DEVICE_CONTROL类型的IRP。
DeviceIoControl的声明如下:
BOOL
DeviceIoControl(
HANDLE hDevice, // 打开的设备
DWORD dwIoControlCode, // 控制码
LPVOID lpInBuffer, // 输入缓冲区
DWORD nInBufferSize, // 输入缓冲区大小
LPVOID lpOutBuffer, // 输出缓冲区
DWORD nOutBufferSize, // 输出缓冲区大小
LPDWORD lpBytesReturned, // 实际返回的字节数
LPOVERLAPPED lpOverlapped // 是否OVERLAP操作
);
其中lpBytesReturned对于IRP中的pIrp->IOStatus.Information。
DeviceIoControl的第二个参数是I/O控制码,控制码也称IOCTL值。这是一个32位无符号整形,IOCTL需要符合DDK的规定:
我们可以使用DDK的CTL_CODE宏来创建该值:
#define CTL_CODE( DeviceType, Function, Method, Access ) ( \
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
)
- DeviceType:设备对象类型,需要和使用IoCreateDevice创建设备时设置的类型相匹配。
- Access:访问权限,一般使用FILE_ANY_ACCESS。
- Function:控制代码,0X000~0X7FF微软保留0X800~0XFFF由程序员自己定义。
- Method:操作模式,主要有下面3种:
- METHOD_BUFFERED:缓冲区方式操作。
- METHOD_IN_DIRECT:直接输入方式操作。
- METHOD_OUT_DIRECT:直接输出方式操作。
缓冲区内存模式IOCTL
当我们使用CTL_CODE宏创建I/O控制码时,如果设置Method为METHOD_BUFFERED,则就是缓冲区内存模式。用户提供的输入缓冲区中的内容被复制到IRP中的pIrp->AssociatedIrp.SystemBuffer内存地址,复制的字节数是由DeviceIoControl指定的输入字节数。同时我们也可以写入pIrp->AssociatedIrp.SystemBuffer的内存地址,这被当作设备输出的数据。操作系统会将这个地址的数据复制到DeviceIoControl提供的输出缓冲区,复制的字节数由pIrp->IoStatus.Information指定。
I/O堆栈中的Parameters.DeviceIoControl.InputBufferLength记录输入缓冲区长度,Parameters.DeviceIoControl.OutputBufferLength记录输出缓冲区长度,Parameters.DeviceIoControl.IoControlCode记录I/O控制码。
如下图:
我们完成如下的IoControl例程:
#define IOCTL_TEST1 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
/// @brief 设备IoControl例程
/// @param[in] pDevObject
/// @param[in] pIrp
/// @return
NTSTATUS DeviceIoControlRoutine(IN PDEVICE_OBJECT pDevObject, IN PIRP pIrp)
{
KdPrint(("\nEnter DeviceIOControlRoutine\n"));
PIO_STACK_LOCATION pIOStack = IoGetCurrentIrpStackLocation(pIrp);
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObject->DeviceExtension;
// 得到输入输出缓冲区大小
ULONG inputBufferLen = pIOStack->Parameters.DeviceIoControl.InputBufferLength;
ULONG outputBufferLen = pIOStack->Parameters.DeviceIoControl.OutputBufferLength;
// 得到IOCTL码
ULONG ctlCode = pIOStack->Parameters.DeviceIoControl.IoControlCode;
NTSTATUS status = STATUS_SUCCESS;
ULONG infor = 0;
switch (ctlCode)
{
case IOCTL_TEST1:
{
KdPrint(("IOCTL_TEST1\n"));
UCHAR* inputBuffer = (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;
for (ULONG i = 0; i < inputBufferLen; i++)
{
KdPrint(("%X ", inputBuffer[i]));
}
UCHAR* outPutBuffer = inputBuffer;
memset(outPutBuffer, 0xEF, outputBufferLen);
infor = outputBufferLen;
status = STATUS_SUCCESS;
break;
}
default:
status = STATUS_INVALID_VARIANT;
infor = 0;
break;
}
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = infor;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("Leave DeviceIOControlRoutine\n"));
return status;
}
我们打印出输入缓冲区中的内容,并且在输出缓冲区中填写0xEF,Win32代码部分如下:
UCHAR inputBuffer[10] = { 0 };
UCHAR outputBuffer[10] = { 0 };
memset(inputBuffer, 0xBB, 10);
DWORD dwOutput;
//输入缓冲区作为输入,输出缓冲区作为输出
BOOL iRet = DeviceIoControl(hDevice, IOCTL_TEST1, inputBuffer, 10, outputBuffer, 10, &dwOutput, NULL);
if (FALSE == iRet)
return;
printf("Output buffer:%d bytes\n", dwOutput);
for (int i = 0; i < (int)dwOutput; i++)
{
printf("%02X ", outputBuffer[i]);
}
printf("\n");
程序运行结果如下:
直接内存模式IOCTL
在定义这种IOCTL时要指定METHOD_IN_DIRECT或者METHOD_OUT_DIRECT,它们的区别仅仅跟打开设备的权限有关,我们一般使用FILE_ANY_ACCESS打开设备,所以可以指定任意一个。只读权限打开设备只能使用METHOD_IN_DIRECT。
在这种方式下输入缓冲区中的内容会被复制到IRP中的pIrp->AssociatedIrp.SystemBuffer内存地址,输出缓冲区会被锁定,我们需要在内核地址空间下重新映射,使用MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority)可以进行映射。
如下图:
我们完成如下的IoControl例程:
#define IOCTL_TEST2 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
/// @brief 设备IoControl例程
/// @param[in] pDevObject
/// @param[in] pIrp
/// @return
NTSTATUS DeviceIoControlRoutine(IN PDEVICE_OBJECT pDevObject, IN PIRP pIrp)
{
KdPrint(("\nEnter DeviceIOControlRoutine\n"));
UNREFERENCED_PARAMETER(pDevObject);
PIO_STACK_LOCATION pIOStack = IoGetCurrentIrpStackLocation(pIrp);
// 得到输入输出缓冲区大小
ULONG inputBufferLen = pIOStack->Parameters.DeviceIoControl.InputBufferLength;
ULONG outputBufferLen = pIOStack->Parameters.DeviceIoControl.OutputBufferLength;
// 得到IOCTL码
ULONG ctlCode = pIOStack->Parameters.DeviceIoControl.IoControlCode;
NTSTATUS status = STATUS_SUCCESS;
ULONG infor = 0;
switch (ctlCode)
{
case IOCTL_TEST2:
{
KdPrint(("IOCTL_TEST1\n"));
UCHAR* inputBuffer = (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;
UCHAR* outPutBuffer = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
for (ULONG i = 0; i < inputBufferLen; i++)
{
KdPrint(("%X ", inputBuffer[i]));
}
memset(outPutBuffer, 0xEF, outputBufferLen);
infor = outputBufferLen;
status = STATUS_SUCCESS;
break;
}
default:
status = STATUS_INVALID_VARIANT;
infor = 0;
break;
}
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = infor;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("Leave DeviceIOControlRoutine\n"));
return status;
}
我们打印出输入缓冲区中的内容,并且在输出缓冲区中填写0xEF,Win32代码部分如下:
UCHAR inputBuffer[10] = { 0 };
UCHAR outputBuffer[10] = { 0 };
memset(inputBuffer, 0xBB, 10);
DWORD dwOutput;
//输入缓冲区作为输入,输出缓冲区作为输出
BOOL iRet = DeviceIoControl(hDevice, IOCTL_TEST2, inputBuffer, 10, outputBuffer, 10, &dwOutput, NULL);
if (FALSE == iRet)
return;
printf("Output buffer:%d bytes\n", dwOutput);
for (int i = 0; i < (int)dwOutput; i++)
{
printf("%02X ", outputBuffer[i]);
}
printf("\n");
程序运行结果如下:
后话
本文完整工程和代码托管在GitHub上猛戳我。
其他章节链接
VS2013 WDK8.1驱动开发3(手动加载NT驱动程序)
VS2013 WDK8.1驱动开发9(IO设备控制操作)
还没有人评论...