드라이버와 통신하기 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
참고
'개발 > Windows' 카테고리의 다른 글
Windows 커널모드 메모리 할당과 페이지 단편화 -ExAllocatePoolWithTag (0) | 2020.01.14 |
---|---|
DeviceIoControl 버퍼 사용법 (0) | 2020.01.07 |
Zw함수의 접두사의 의미는? (0) | 2020.01.03 |
커널드라이버 Buffered I/O 처리 하기 (MFC, CFile) (0) | 2020.01.03 |
장치 드라이버와 커널 오브젝트 (Device Driver & Kernel Object) (0) | 2019.12.30 |
댓글