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驱动开发1(最简单的NT驱动)

VS2013 WDK8.1驱动开发2(最简单的WDM驱动)

VS2013 WDK8.1驱动开发3(手动加载NT驱动程序)

VS2013 WDK8.1驱动开发4(NT式驱动基本结构)

VS2013 WDK8.1驱动开发5(WDM驱动基本结构)

VS2013 WDK8.1驱动开发6(内存管理)

VS2013 WDK8.1驱动开发7(派遣函数)

VS2013 WDK8.1驱动开发8(设备读写操作)

VS2013 WDK8.1驱动开发9(IO设备控制操作)


赞助作者写出更好文章


您还未登录,登录GitHub账号发表精彩评论

 GitHub登录


最新评论

    还没有人评论...

 

 

刘杰

29岁, 现居苏州

微信:

CoderJieLiu

邮箱:

coderjie@outlook.com

Github:

https://github.com/BurnellLiu

简介:

弱小和无知不是生存的障碍,傲慢才是!

Think Twice, Code Once!

本站: 登录 注册

苏ICP备16059872号-1 苏ICP备16059872号-2 . Copyright © 2017. http://www.coderjie.com. All rights reserved.

账号登录

注册 忘记密码