Новые рабочие процедуры в WDM драйверах
Процедура AddDevice, вызываемая PnP Менеджером, только лишь производит инициализацию объекта устройства и, если необходимо, структуры данных расширения объекта устройства. В процедуре AddDevice, по правилам хорошего тона WDM модели, действия над собственно аппаратурой не должны совершаться. Но тогда остаются нерешенными две важные задачи:
Все это должен сделать драйвер по получении IRP пакета с кодом IRP_MJ_PNP. Такие IRP пакеты посылается PnP Менеджером, когда происходят события включения или выключения устройства, либо возникают вопросы по конфигурированию устройства.
Категория IRP_MJ_PNP пакетов включает запросы широкого спектра, которые детализируются суб-кодами IRP_MN_Xxx. Поскольку они пропускается через единственную рабочую процедуру, то ее обязанностью является вторичная диспетчеризация по этим суб-кодам, содержащимся в IRP пакете и описывающим специфические действия, осуществления которых ожидает PnP Менеджер.
Регистрация новой для WDM модели рабочей процедуры, которой будет поручено обрабатывать запросы IRP_MJ_PNP со всеми подтипами IRP_MN_Xxx, производится традиционным образом в процедуре DriverEntry:
pDriverObj->MajorFunction[IRP_MJ_PNP] = MyPnP_Handler;
Пример программного кода для осуществления вторичной диспетчеризации на основе суб-кодов IRP_MN_Xxx приводится ниже.
NTSTATUS MyPnP_Handler ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ) { // Получить указатель на текущую ячейку стека IRP пакета PIO_STACK_LOCATION pIrpStackLocation = IoGetCurrentIrpStackLocation( pIrp );
switch (pIrpStackLocation ->MinorFunction) { case IRP_MN_START_DEVICE: . . . // Внимание. Все ветви оператора switch должны возвратить // результаты обработки . . . default: // если не поддерживается здесь, то передать запрос вниз: IoSkipCurrentIrpStackLocation(pIrp); return IoCallDriver(. . ., pIrp); } }
Первым параметром вызова IoCallDriver (см.
таблицу 9.5), разумеется, является указатель на объект устройства, к которому было произведено подключение в процедуре AddDevice.
Вызов IoSkipCurrentIrpStackLocation (см. таблицу 9.6) сообщает Диспетчеру ввода/вывода, что драйвер отказывается от дальнейшего участия в судьбе данного IRP пакета. В том случае, если драйвер желает получить управление над IRP пакетом в момент, когда его обработка нижними слоями драйверов будет завершена, то он должен воспользоваться системным вызовом IoCopyCurrentIrpStackLocationToNext
(см. таблицу 9.7) и зарегистрировать процедуру CompletionRoutine. Она будет вызвана в соответствующий момент.
Таблица 9.5. Прототип функции IoCallDriver
NTSTATUS IoCallDriver | IRQL <= DISPATCH_LEVEL |
Параметры | Обращается к другому драйверу с запросом, сформулированным в пакете IRP (запросы типа IRP_MJ_POWER следует выполнять при помощи вызова PoCallDriver) |
IN PDEVICE_OBJECT pDevObj | Указатель на объект устройства, которому адресован IRP запрос |
IN PIRP pIrp | Указатель на отправляемый IRP пакет |
Возвращаемое значение |
• STATUS_SUCCESS • STATUS_PENDING — в случае, если пакет требует дополнительной обработки • STATUS_Xxx — в случае ошибки |
VOID IoSkipCurrentIrpStackLocation | IRQL <= DISPATCH_LEVEL |
Параметры | Изменяет указатель стека IRP так, что нижестоящий драйвер будет считать текущую ячейку стека IRP своей |
IN PIRP pIrp | Указатель на модифицируемый IRP пакет |
Возвращаемое значение | void |
VOID IoCopyCurrentIrpStackLocationToNext | IRQL <= DISPATCH_LEVEL |
Параметры | Копирует содержимое ячейки стека IRP для текущего драйвера в ячейку стека для нижестоящего драйвера |
IN PIRP pIrp | Указатель на модифицируемый IRP пакет |
Возвращаемое значение | void |
Процедура завершения ввода/вывода регистрируется вызовом IoSetCompletionRoutine (см. таблицу 9.8).
Таблица 9.8. Прототип макроопределения IoSetCompletionRoutine
VOID IoSetCompletionRoutine | IRQL <= DISPATCH_LEVEL |
Параметры | Выполняет регистрацию callback-функции завершения обработки IRP пакета |
IN PIRP pIrp | Указатель на отслеживаемый IRP пакет |
IN PIO_COMPLETE_ROUTINE CompletionRoutine | Функция, которая должна получить управление, когда обработка IRP будет завершена |
IN PVOID pContext | Параметр, который получит регистрируемая callback функция CompletionRoutine |
IN BOOLEAN doCallOnSuccess | Вызывать CompletionRoutine в случае успешного завершения обработки данного IRP пакета |
IN BOOLEAN doCallOnError | Вызывать CompletionRoutine в случае завершения обработки данного IRP с ошибкой |
IN BOOLEAN doCallOnCancel | Вызывать CompletionRoutine в случае прерванной обработки данного IRP пакета |
Возвращаемое значение | void |
![]() |
Подключать собственные процедуры CompletionRoutine для детектирования окончания обработки IRP пакетов можно не только к пакетам с кодом IRP_MJ_PNP, но также ко всем остальным, отправляемым драйверам нижних слоев. |
Таблица 9.9. Прототип функции завершения ввода/вывода CompletionRoutine
NTSTATUS CompletionRoutine | IRQL == см. текст ниже |
Параметры | Перехватывает пакет IRP после завершения работы нижнего драйверного слоя |
IN PDEVICE_OBJECT pDevObj | Объект устройства (в составе данного драйвера), которому был ранее адресован данный IRP пакет |
IN PIRP pIrp | Указатель на IRP пакет, обработка которого только что завершена |
IN PVOID pContext | Аргумент, указанный в IoSetCompleteRoutine |
Возвращаемое значение |
STATUS_MORE_PROCESSING_REQUIRED STATUS_SUCCESS |
Однозначно предсказать, на каком уровне IRQL выполняется процедура завершения, невозможно. В том случае, если нижележащий драйвер, вызывает IoCompleteRequest
(подробности — ниже) с уровня IRQL равного PASSIVE_LEVEL, то процедура завершения находящегося выше драйвера выполняется на уровне PASSIVE_LEVEL. В случае, если лежащий ниже драйвер завершает обработку IRP пакета на уровне DIPATCH_LEVEL (например, из DPC процедуры), то и процедура завершения лежащего выше драйвера выполняется на уровне DISPATCH_LEVEL.
Выполнение программного кода на уровне DISPATCH_LEVEL ограничивается системными вызовами, которые работают на этом уровне IRQL. Разумеется, следует особо позаботиться, чтобы здесь не производилась работа со страничной памятью.
Таблица 9.10. Прототип функции IoCompleteRequest
VOID IoCompleteRequest | IRQL <= DISPATCH_LEVEL |
Параметры | Вызывается, когда драйвер желает полностью завершить обработку данного IRP пакета. Обеспечивает вызов процедур завершения всех драйверов, имеющихся над данным (см. ниже) |
IN PIRP pIrp | Указатель на текущий IRP пакет, обработка которого только что завершена |
IN CCHAR PriorBoost | Величина, на которую следует изменить приоритет потока, выполняющего обработку данного IRP пакета. Величина IO_NO_INCREMENT используется, если никаких изменений делать не нужно. |
Возвращаемое значение | void |
Таким образом, мы получим сигнал о завершении обработки пакета IRP на вполне определенном уровне IRQL, равном именно PASSIVE_LEVEL. Полностью данный метод описывается в примере ниже:
. . . . . . // код рабочей процедуры, выполняющийся на уровне PASSIVE_LEVEL . . . . . . IoCopyCurrentIrpStackLocationToNext(pIrp);
// Резервируем место под объект события: KEVENT myEvent; // Инициализируем его, состояние не сигнальное: KeInitializeEvent( &myEvent, NotificationEvent, FALSE ); // Регистрируем свою процедуру завершения обработки IRP пакета. // Указатель на объект myEvent передаем как дополнительный параметр. IoSetCompletionRoutine( pIrp, MyCompleteRoutine, (PVOID)&myEvent, TRUE, TRUE, TRUE);
// Предположим, что указатель на объект устройства, // к которому был подключен текущий объект устройства, был ранее // сохранен в структуре расширения текущего объекта устройства. PDEVICE_EXTENSION pDeviceExtension = (PDEVICE EXTENSION) pDeviceObject->DeviceExtension; PDEVICE_OBJECT pUnderlyingDevObj = pDeviceExtension->pLowerDevice;
// Отправляем IRP пакет на обработку нижними драйверными слоями IoCallDriver( pUnderlyingDevObj, pIrp );
// Организуем ожидание, пока не закончится работа на нижних уровнях KeWaitForSingleObject( &myEvent, Execute, KernelMode, FALSE, NULL);
// Теперь завершаем обработку IRP пакета. // Его адрес не изменился - pIrp. // По "возвращении" из "ожидания" уровень IRQL остался прежним для // данного потока. // Поскольку Диспетчер ввода/вывода и PnP Менеджер вызывают // рабочие процедуры драйвера на уровне PASSIVE_LEVEL, то таким // он в данном потоке и остался. . . . } NTSTATUS MyCompleteRoutine( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp, IN PVOID pContextArgument ) { // Вычисляем указатель на Объект События: PEVENT pEvent = (PEVENT) pContextArgument; // Устанавливаем его в сигнальное состояние KeSetEvent( pEvent, 0, FALSE ); // IRQL <=DISPATCH_LEVEL
// Пакет IRP получен. Завершение работы здесь. Но не окончательно.
return STATUS_MORE_PROCESSING_REQUIRED; }
Рассмотрим подробнее работу вызова IoCompleteRequest. Когда некий код некоего драйвера делает этот вызов, программный код IoCompleteRequest
обращается к ячейкам стека IRP пакета и анализирует, зарегистрировал ли верхний (над текущим) драйвер процедуру завершения CompleteRoutine — это как раз отмечено в стеке IRP пакета. В том случае, если таковой процедуры не обнаруживается, указатель стека поднимается и снова выполняется проверка. Если обнаружена зарегистрированная функция, то она выполняется. В том случае, если вызванная таким образом функция возвращает код завершения, отличный от STATUS_MORE_PROCESSING_REQUIRED, то указатель стека снова поднимается, и действия повторяются. Если в результате вызова получен код завершения STATUS_MORE_PROCESSING_REQUIRED, то управление возвращается инициатору вызова IoCompleteRequest.
Когда код IoCompleteRequest благополучно достигает в своем рассмотрении вершины стека, то Диспетчер ввода/вывода выполняет действия по освобождению данного IRP пакета (наряду с некоторыми другими операциями).
Отсюда несколько важных следствий.
Во-первых, если драйвер сам создал IRP пакет (подробно рассматривается ниже), то вызов IoCompleteRequest означает приказ Диспетчеру ввода/вывода заняться его освобождением — поскольку иных драйверов, процедуры завершения которых можно было бы рассматривать, просто нет.
Во-вторых, если текущий драйвер зарегистрировал свою процедуру завершения и, не вызывая нижних драйверов, сразу выполнил IoCompleteRequest, то такая процедура завершения вызвана не будет — код IoCompleteRequest
ee в рассмотрение просто не примет, переходя сразу к анализу ячеек стека IRP для вышестоящих драйверов.
В-третьих, возможна ситуация, когда после выполнения вызова IoCompleteRequest
драйвером в середине драйверного стека IRP пакет все еще существует. Не исключено, что он не завершен, поскольку один из верхних драйверов (который, возможно, существует) отказался это сделать в своей процедуре завершения, которую он, возможно, зарегистрировал.Однако текущий драйвер таких допущений делать не должен. Впрочем, как и всех остальных предположений относительно верхних драйверных слоев.
В любом случае, после вызова IoCompleteRequest драйвер не имеет права прикасаться к IRP пакету, который передан этому вызову как завершаемый. Кроме того, возврат кода STATUS_MORE_PROCESSING_REQUIRED — это практика зарегистрированных процедур завершения, что является "просьбой" Диспетчеру ввода/вывода возвратиться к данной процедуре завершения позже.