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设备控制操作)
 
                
还没有人评论...