본문 바로가기
개발/Windows

드라이버와 통신하기 IRP_MJ_DEVICE_CONTROL

by lucidmaj7 2020. 1. 7.
728x90
반응형

드라이버와 통신하기 IRP_MJ_DEVICE_CONTROL

장치 드라이버는 I/O Manager로 부터 File Object로 취급되며 File Operation을 수행 할 수 있다고 하였다. User Application에서 Create, Read, Write, Close Operation를 통해 드라이버와 통신 할 수 있다. 하지만 장치가 쓰기, 읽기 작업만 하지 않을 것 이다. 장치가 동작하는데 다양한 기능들을 수행하기 위해서는 이보다 더 많은 명령이 있어야 할 것이다. 때문에 I/O Control Code를 통해 다양한 명령을 정의하고 수행 할 수 있다.

MSDN에 의하면 I/O Control 코드는 User Application과 드라이버간 통신을 가능하게 해준다. 또한 드라이버 사이에 통신을 가능하게 해준다. DeviceIoControl함수를 통해 IOCTL을 드라이버에게 보내지면 I/O Manager는 IRP를 생성하여 드라이버로 보낸다. 이 때 드라이버로 전달되는 IRP Major function이 IRP_MJ_DEVICE_CONTROL이다.

I/O Control Code (IOCTL) 정의

User Application과 통신을 위해 IOCTL을 정의할경우 IRP_MJ_DEVICE_CONTROL의 IRP가 할당되 전달되는데 이때 I/O Control Code(IOCTL)를 같이 전달 해주어야 한다. IOCTL은 개발자가 정의 해줄 수 있다.

IOCTL은 아래와 같이 32비트로 구성된 구조를 지닌다.

이러한 형식을 쉽게 정의하기 위해 CTL_CODE라는 매크로(wdm.h)를 제공한다.

#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)
  • DeviceType : 장치 유형을 구분한다. DEVICE_OBJECT구조체의 DeviceType 멤버에서 설정된 값과 일치해야한다. 아래 코드에서는 FILE_DEVICE_UNKNOWN가 DeviceType이다.

      //create Device Object 
      status = IoCreateDevice(
          pDriverObject, 
          0, 
          &deviceNTName, 
          FILE_DEVICE_UNKNOWN,
          FILE_DEVICE_SECURE_OPEN, 
          0, 
          &pDeviceObject);

    보통 소프트웨어 드라이버의 경우 FILE_DEVICE_UNKNOWN를 사용한다.

  • FunctionCode : 드라이버가 수행할 기능을 식별한다. 0x800 미만의 값은 MS가 예약해 놨으므로 0x800이상으로 설정하여야 한다.

  • TransferType : 드라이버에게 데이터를 전달할 방법을 설정한다. 앞서 포스팅한 글에서 Buffered I/O, Direct I/O 등을 선택할 수 있다 DO_BUFFERED_IO로 설정한 경우 METHOD_BUFFERED를 입력한다. 그 이외에도 METHOD_IN_DIRECT, METHOD_OUT_DIRECT 등을 설정 할 수 있다.

      // Set Buffered I/O
      pDeviceObject->Flags|= DO_BUFFERED_IO;
  • RequiredAccess : Device를 Open할때 지정하는 Access 유형을 나타낸다 대게 FILE_ANY_ACCESS를 사용한다. 이는 엑세스 권한과 관계없이 IOCTL을 보낼 수 있다.

구현

IOCTL CTL_CODE 정의

User Application과 드라이버에서 아래와 같이 CTL_CODE를 정의한다.

#define IOCTL_MY_CTL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x4000, METHOD_BUFFERED , FILE_ANY_ACCESS)

User Application

MY_CTL버튼을 클릭하면 EditBox의 내용을 복사하여 IOCTL_MY_CTL Device Control을 통해 드라이버로 전달한다.

void CwdmTestAppDlg::OnBnClickedButton3()
{
    // TODO: Add your control notification handler code here
    if (m_DeviceFile.m_hFile == CFile::hFileNull)
    {
        return;
    }
    if (m_ctrlWriteEdit.GetWindowTextLengthW())
    {
        CString str;
        m_ctrlWriteEdit.GetWindowText(str);
        DWORD dwAlloc = str.GetLength() + 1;
        PWCHAR pBuf = new WCHAR[dwAlloc];
        ZeroMemory(pBuf, sizeof(WCHAR) * dwAlloc);
        wcsncpy_s(pBuf, dwAlloc, str.GetBuffer(), _TRUNCATE);

        m_DeviceFile.Write(pBuf, sizeof(WCHAR) * dwAlloc);
        BOOL bIoCTL = FALSE;
        DWORD dwRetBytes = 0;
        if (DeviceIoControl(m_DeviceFile.m_hFile, IOCTL_MY_CTL, pBuf, sizeof(WCHAR) * dwAlloc, &bIoCTL, sizeof(BOOL), &dwRetBytes, NULL))
        {
            CString strMsg;
            strMsg.Format(_T("DeviceIoControl IOCTL_MY_CTL success !!! return %s"), bIoCTL ? _T("TRUE") : _T("FALSE"));
            AfxMessageBox(strMsg);
        }
        else
        {
            AfxMessageBox(_T("DeviceIoControl IOCTL_MY_CTL fail!!!!!! !!!"));
        }

        delete[] pBuf;
    }
}

드라이버

IRP_MJ_DEVICE_CONTROL루틴을 아래와 같이 구현한다. User Application에서 전달 받은 버퍼를 복사하여 출력하고, Output Buffer에 TRUE, FALSE를 복사하고 루틴을 마친다.
이때 irpSp->Parameters.DeviceIoControl.InputBufferLength에는 User Application으로부터 받은 버퍼의 사이즈, irpSp->Parameters.DeviceIoControl.OutputBufferLength에는 User Application으로 전달할 버퍼의 사이즈가 저장되어 있다.
BUFFERED_IO를 사용하므로 버퍼는 pIrp->AssociatedIrp.SystemBuffer를 사용한다.

    else if (irpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL)
    {
        //Get IoControl Code from irp stack.
        ctlCode = irpSp->Parameters.DeviceIoControl.IoControlCode;
        switch (ctlCode)
        {
        case IOCTL_MY_CTL:
            KdPrint(("IRP_MJ_DEVICE_CONTROL IOCTL_MY_CTL  %x <==\n", ctlCode));

            //exceptions
            if (irpSp->Parameters.DeviceIoControl.InputBufferLength == 0)
            {
                KdPrint(("IRP_MJ_DEVICE_CONTROL IOCTL_MY_CTL  Parameters.DeviceIoControl.InputBufferLength 0 <==\n"));
                break;
            }
            if (irpSp->Parameters.DeviceIoControl.OutputBufferLength == 0)
            {
                KdPrint(("IRP_MJ_DEVICE_CONTROL IOCTL_MY_CTL  Parameters.DeviceIoControl.OutputBufferLength 0 <==\n"));
                break;
            }
            if (!pIrp->AssociatedIrp.SystemBuffer)
            {
                KdPrint(("IRP_MJ_DEVICE_CONTROL IOCTL_MY_CTL !pIrp->AssociatedIrp.SystemBuffer <==\n"));
                break;
            }

            buf = ExAllocatePoolWithTag(NonPagedPool, irpSp->Parameters.DeviceIoControl.InputBufferLength, ALLOC_TAG);
            if (buf)
            {
                //copy buffer
                RtlCopyMemory(buf, pIrp->AssociatedIrp.SystemBuffer, irpSp->Parameters.DeviceIoControl.InputBufferLength);
                //print
                KdPrint(("IoControlDispatch IOCTL_MY_CTL Length:%lu %S<==\n", irpSp->Parameters.DeviceIoControl.InputBufferLength, buf));
                bIOCtl = TRUE;
                ExFreePool(buf);
            }
            //copy buffer

            RtlCopyMemory( pIrp->AssociatedIrp.SystemBuffer, &bIOCtl ,sizeof(int));
            pIrp->IoStatus.Information = sizeof(int);
            //print
            KdPrint(("IoControlDispatch IOCTL_MY_CTL set output buffer:%d <==\n", bIOCtl));


            break;
        default:
            KdPrint(("IRP_MJ_DEVICE_CONTROL unknown CTL  %x <==\n", ctlCode));
            break;
        }
    }

동작

Github

https://github.com/lucidmaj7/WDM-BufferedIO-Example/tree/DeviceIoControlExample

참고

728x90
반응형

댓글