Рабочие процедуры обслуживания IOCTL запросов
Запросы данного типа формулируются в рамках двух более гибких типов запросов на ввод/вывод (IRP пакетах). Значение основного кода IRP_MJ_Xxx драйвер может найти в соответствующей ему ячейке стека IRP пакета, a IOCTL код размещен в Parameters.DeviceIoControl.IoControlCode той же ячейки стека IRP пакета.
Следует отметить, что реализация процедуры разборки таких IRP запросов в драйвере требует вторичной диспетчеризации — в соответствии со значением IoControlCode. Это значение известно еще со времен MS DOS под именем IOCTL
— Input/Output ConTroL code.
Значения IOCTL, передаваемые в драйвер, могут быть определены разработчиком драйвера и имеют фиксированную внутреннюю организацию. Рисунок 8.2 демонстрирует поля 32-битной структуры IOCTL кода. Пакет DDK имеет в своем составе макроопределение CTL_CODE, которое обеспечивает приемлемый механизм генерации значений IOCTL, уже использованный в главе 3. Таблица 8.8 описывает аргументы этого макроопределения.
Рис. 8.2. Структура блока данных IOCTL |
Таблица 8.8. Аргументы макроопределения CTL_CODE
Параметры | Описание | |
DeviceType | FILE_DEVICE_XXX значения передаваемые в IoCreateDevice • 0x0000 — 0x7FFF — зарезервировано Microsoft • 0x8000 — 0xFFFF — определяется пользователем |
|
ControlCode | Определяемые драйвером IOCTL значения • 0x000 — 0x7FF — зарезервировано Microsoft (public) • 0x800 — 0xFFF — определяется пользователем |
|
TransferType | Способ получения доступа к буферу • METHOD_BUFFERED • METHOD_IN_DIRECT • METHOD_OUT_DIRECT • METHOD_NEITHER |
|
RequiredAccess | Требования инициатора относительно типа доступа • FILE_ANY_ACCESS • FILE_READ_DATA • FILE_WRITE_DATA • FILE_READ_DATA | FILE_WRITE_DATA |
Операции драйвера, которые работают с IOCTL запросами, часто требуют задания буферной области для размещения входных либо выходных данных, то есть поступающих от пользовательского приложения в драйвер либо в обратном направлении, соответственно. Возможно, что в одном запросе используются сразу оба буфера. В самом деле, вызов функции пользовательского режима DeviceIoControl среди прочих входных параметров имеет два указателя на две буферные области, одну — для входных данных, другую — для выходных. Механизм переноса данных, обеспечиваемый Диспетчером ввода/вывода, определяется как раз в IOCTL. Это может быть либо буферизованный, либо прямой ввод-вывод, либо метод NEITHER. Как было сказано ранее относительно запросов чтения/записи, при буферизованном способе работы с данными, Диспетчер ввода/вывода копирует данные пользовательского буфера в/из промежуточного буфера, размещенного в нестраничном пуле, при работе с которым драйвер не будет испытывать сложностей. При прямом способе ввода/вывода драйвер получает прямой доступ к определенной пользователем буферной области памяти, которая предварительно зафиксирована в оперативной памяти.
В данном случае флаги, определяющие тип буферизации в объекте устройства (pDeviceObject->Flags), не имеют значения при работе с IOCTL запросами. Механизм буферизированного обмена определяется при каждом задании значения IOCTL в специально предназначенном для этого фрагменте этой структуры данных. Данный подход обеспечивает максимальную гибкость при работе с вызовом пользовательского режима DeviceIoControl.
Поле TransferType (таблица 8.8) представляет собой два бита, которые определяют один из следующих типов буферизации:
описанному 5-м (!!) параметром.
Так как поле TransferType встроено в значение IOCTL, то документированные Microsoft программные компоненты определяют и механизм буферизации. Для значений IOCTL, определенных в коде драйвера, могут быть заданы любые требуемые значения для описания механизма переноса данных. Для переноса небольших объемов данных и при небольшой скорости обмена вполне приемлем буферизованный ввод/вывод. Для переноса больших объемов и быстрой работы более подойдет прямой ввод/вывод.
Как только у драйвера появляется объявленная им рабочая процедура для обслуживания IRP пакетов с кодами IRP_MJ_INTERNAL_DEVICE_CONTROL либо IRP_MJ_DEVICE_CONTROL, Диспетчер ввода/вывода начинает пропускать соответствующие пакеты IRP внутрь драйвера. Интерпретация поступающих кодов IOCTL управления устройством становится обязанностью и ответственностью драйвера, включая проверку значений полей внутри кода IOCTL. Любое 32 разрядное число, посланное инициатором запроса в качестве IOCTL кода, поступит в соответствующую рабочую процедуру драйвер, поскольку Диспетчер ввода/вывода не выполняет проверки корректности IOCTL кодов.
Типовая конфигурация рабочей процедуры по обслуживанию IOCTL запросов должна быть большим оператором switch, например:
NTSTATUS IoControlCodeHandler(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) { NTSTATUS status = STATUS_SUCCESS; PMYDEVICE_EXTENSION pDevExt; ULONG ioctlCode, inSize, outSize; // Находим нужную ячейку стека IRP пакета PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp); // Находим код IOCTL запроса ioctlCode = pIrpStack ->Parameters.DeviceIoControl.IoControlCode; // и требуемого размера передаваемых данных inSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLenght; outsize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLenght;
switch(controlCode) { // Вторичная диспетчеризация case IOCTL_CODE_1: { // Всегда следует проверять входные параметры if(inSize > 0 || outSize > 0) { Status = STATUS_INVALID_PARAMETER; break; } } default: // Драйвер получил непредусмотренные коды IOCTL status = STATUS_INVALID_DEVICE_REQUEST; break; } pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = 0; // нет данных для передачи IoCompleteRequest( pIrp, IO__NO_INCREMENT ); return status; }
Доступ к буферным областям, содержащим данные или предназначенным для данных, описывается таблицей 8.9.
Таблица 8.9. Передача адресов буферов данных в IRP пакетах, описывающих IOCTL запросы
METHOD_BUFFERED | METHOD_IN_DIRECT или METHOD_OUT_DIRECT |
METHOD_NEITHER | |
Input Буфер с данными | Использует буферизацию (системный буфер) Адрес буфера в системном адресном пространстве указан в pIrp->AssociatedIrp.SystemBuffer |
Клиентский виртуальный адрес в Parameters.DeviceIoControl. Type3InputBuffer | |
Длина указана в Parameters.DeviceIoControl.InputBufferLength | |||
Output Буфер для данных |
Использует буферизацию (системный буфер) Адрес буфера в системном адресном пространстве указан в pIrp-> AssociatedIrp.SystemBuffer |
Использует прямой доступ, клиентский буфер преобразован в MDL список, указатель на который размещен в PIrp->MdlAddress |
Клиентский виртуальный адрес в pIrp->UserBuffer |
Длина указана в Parameters.DeviceloControl.OutputBufferLength |
Названия Input и Output здесь и в литературе трактуются с точки зрения драйвера. Буфер 'Input' содержит данные, поступающие от клиента, скорее всего, предназначенные для вывода в устройство. Буфер 'Output' указывает на то место, куда следует поместить данные, ожидаемые клиентом, скорее всего, прочитанные из устройства. Кстати сказать, в описании вызова DeviceIoControl в документации MSDN никакого разночтения с данной трактовкой названий не наблюдается: буфер с данными для выполнения операции (3-й параметр вызова) называется lplnputBuffer, а буфер для получаемых данных (5-й параметр вызова) называется lpOutputBuffer. |
Следует также обратить внимание на то, что при методе буферизации METHOD_BUFFERED Диспетчер ввода/вывода в качестве системного буфера для драйвера получает одну область системного адресного пространства, достаточно большую для того, чтобы вместить наибольший из входного/выходного буферов клиента. В эту область Диспетчер ввода/вывода перед вызовом рабочей процедуры, обслуживающей запросы данного типа, копирует данные, передаваемые драйверу. В эту же область драйвер выводит свой данные, предназначенные для получающего буфера клиента. Следует внимательно относиться к операциям записи в этот буфер вводимых данных, поскольку есть опасность испортить, возможно, еще находящиеся там данные, предназначенные к выводу в устройство. Разумеется, если клиент драйвера предлагает в одном запросе действия и ввода, и вывода.
Может показаться странным, что для метода METHOD_IN_DIRECT строится MDL список для выходного (output) буфера, а для входного как бы используется менее мощный метод METHOD_BUFFERED. Тем не менее, это так. Желающие могут обойти это самостоятельно, просто переставив в своих запросах DeviceIoControl
адрес input буфера на место output буфера (или учитывая это в драйвере, как это сделала фирма Cypress в драйвере Ezusb.sys). Следует отметить, что имеющаяся в документации DDK пометка относительно построения MDL списка для поставляемых клиентом данных (для input буфера с размещением в поле IRP пакета MdlAddress) на практике и в остальной литературе по данному вопросу подтверждения не находит.
Повторим сведения о размещении областей с данными/для данных при подготовке IRP пакета, описывающего IOCTL запрос к драйверу.