Программирование драйверов Windows

       

Рабочие процедуры обслуживания IOCTL запросов


Запросы данного типа формулируются в рамках двух более гибких типов запросов на ввод/вывод (IRP пакетах). Значение основного кода IRP_MJ_Xxx драйвер может найти в соответствующей ему ячейке стека IRP пакета, a IOCTL код размещен в Parameters.DeviceIoControl.IoControlCode той же ячейки стека IRP пакета.

  • IRP_MJ_DEVICE_CONTROL позволяет получать расширенные запросы от клиентов пользовательского режима посредством их действий через API вызов DeviceIoControl.
  • IRP_MJ_INTERNAL_DEVICE_CONTROL позволяет получать расширенные запросы от кода (клиента), функционирующего на уровне режиме ядра. Доступ к этим операциям из кода пользовательского режима не разрешается. Эта возможность используется, главным образом, другими драйверами в многоуровневом драйверном стеке для передачи специальных запросов. С другой стороны, версия для внутреннего пользования идентична стандартной версии. Значение IoControlCode помещается в IRP пакет инициатором запроса.
  • Следует отметить, что реализация процедуры разборки таких IRP запросов в драйвере требует вторичной диспетчеризации &#8212 в соответствии со значением IoControlCode. Это значение известно еще со времен MS DOS под именем IOCTL

    &#8212 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 &#8212 0x7FFF &#8212 зарезервировано Microsoft

    • 0x8000 &#8212 0xFFFF &#8212 определяется пользователем

    ControlCode Определяемые драйвером IOCTL значения

    • 0x000 &#8212 0x7FF &#8212 зарезервировано Microsoft (public)

    • 0x800 &#8212 0xFFF &#8212 определяется пользователем

    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 среди прочих входных параметров имеет два указателя на две буферные области, одну &#8212 для входных данных, другую &#8212 для выходных. Механизм переноса данных, обеспечиваемый Диспетчером ввода/вывода, определяется как раз в IOCTL. Это может быть либо буферизованный, либо прямой ввод-вывод, либо метод NEITHER. Как было сказано ранее относительно запросов чтения/записи, при буферизованном способе работы с данными, Диспетчер ввода/вывода копирует данные пользовательского буфера в/из промежуточного буфера, размещенного в нестраничном пуле, при работе с которым драйвер не будет испытывать сложностей. При прямом способе ввода/вывода драйвер получает прямой доступ к определенной пользователем буферной области памяти, которая предварительно зафиксирована в оперативной памяти.

    В данном случае флаги, определяющие тип буферизации в объекте устройства (pDeviceObject-&#62Flags), не имеют значения при работе с IOCTL запросами. Механизм буферизированного обмена определяется при каждом задании значения IOCTL в специально предназначенном для этого фрагменте этой структуры данных. Данный подход обеспечивает максимальную гибкость при работе с вызовом пользовательского режима DeviceIoControl.

    Поле TransferType (таблица 8.8) представляет собой два бита, которые определяют один из следующих типов буферизации:

  • METHOD_BUFFERED. Диспетчер ввода/вывода копирует пользовательский буфер в/из вспомогательного буфера, который он размещает в нестранично организованной памяти.


  • METHOD_IN_DIRECT. Диспетчер ввода/вывода предоставляет список страниц, которые представляют пользовательский буфер. Драйвер использует этот список для того, чтобы осуществить прямой ввод/вывод (используя DMA или программируемый ввод/вывод) от устройства к пользовательскому буферу, в Win32 API вызове DeviceIoControl



    описанному 5-м (!!) параметром.


  • METHOD_OUT_DIRECT. Диспетчер ввода/ вывод предоставляет список страниц, которые представляют пользовательский буфер. Драйвер использует этот список для того, чтобы осуществить прямой ввод/вывод (используя DMA или программируемый ввод/вывод) от пользовательского буфера к устройству (буфер вводится в Win32 API вызове DeviceIoControl также 5-м параметром).


  • METHOD_NEITHER. Диспетчер ввода/вывода не предлагает буферизированной передачи данных. Пользовательский буфер (скорее всего, в странично организованной памяти) предоставляется драйверу.


  • Так как поле 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 -&#62Parameters.DeviceIoControl.IoControlCode; // и требуемого размера передаваемых данных inSize = pIrpStack-&#62Parameters.DeviceIoControl.InputBufferLenght; outsize = pIrpStack-&#62Parameters.DeviceIoControl.OutputBufferLenght;



    switch(controlCode) { // Вторичная диспетчеризация case IOCTL_CODE_1: { // Всегда следует проверять входные параметры if(inSize &#62 0 || outSize &#62 0) { Status = STATUS_INVALID_PARAMETER; break; } } default: // Драйвер получил непредусмотренные коды IOCTL status = STATUS_INVALID_DEVICE_REQUEST; break; } pIrp-&#62IoStatus.Status = status; pIrp-&#62IoStatus.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-&#62AssociatedIrp.SystemBuffer
    Клиентский виртуальный адрес в Parameters.DeviceIoControl. Type3InputBuffer
    Длина указана в Parameters.DeviceIoControl.InputBufferLength
    Output

    Буфер для данных
    Использует буферизацию (системный буфер)

    Адрес буфера в системном адресном пространстве указан в pIrp-&#62 AssociatedIrp.SystemBuffer
    Использует прямой доступ, клиентский буфер преобразован в MDL список, указатель на который размещен в

    PIrp-&#62MdlAddress
    Клиентский виртуальный адрес в pIrp-&#62UserBuffer
    Длина указана в 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 запрос к драйверу.


    Содержание раздела