Если промежуточный драйвер занимается "выпуском" собственных IRP пакетов, направляя при этом нижним драйверным слоям (одному или нескольким разным драйверам) по нескольку экземпляров сразу, то он рано или поздно попадет в следующую непростую ситуацию. До момента окончания обработки всех "вторичных" пакетов драйвер не может завершить обработку исходного (изначально поступившего) IRP пакета. Возможны два способа работы с размноженными вторичными пакетами.
В первом, "синхронном", случае рабочая процедура должна дождаться, пока все созданные драйвером IRP пакеты не будут обработаны в нижних уровнях. В общем случае, рабочая процедура выполняет следующее:
Выполняет вызовы IoBuildSynchronousFsdRequest или вызовы IoBuildDeviceIoControlRequest для того, чтобы создать необходимое количество IRP пакетов "синхронного" типа.
Выполняет вызовы IoCallDriver для передачи всех созданных драйвером пакетов IRP другим драйверам.
Выполняет вызовы KeWaitForMultipleObjects и ожидает завершения обработки всех переданных IRP пакетов.
Выполняет действия по переносу информации из полученных пакетов и их последующую очистку и освобождение.
Наконец, выполняет вызов IoCompleteRequest относительно исходного IRP пакета для того, чтобы возвратить его инициатору вызова.
Поскольку исходный запрос удерживается внутри рабочей процедуры (поскольку она не возвращает управление), то нет и необходимости помечать исходный IRP пакет как ожидающий обработки.
Второй, "асинхронный", случай несколько сложнее, поскольку непонятно, где именно драйвер может остановиться и дожидаться завершения обработки всех пакетов. В этом случае драйверу рекомендуется подключить процедуру завершения к каждому созданному им IRP пакету, и процедура завершения (скорее всего — единственная для всех пакетов) должна сама определить, наступило ли время считать, что обработка исходного IRP запроса действительно завершена. План действий примерно таков:
Пометить пакет IRP, поступивший в рабочую процедуру от Диспетчера ввода/вывода как ожидающий обработки при помощи IoMarkPending.
Создать дополнительные пакеты IRP с использованием одного из описанных выше методов.
Подключить процедуру завершения (возможно — одну и ту же) к каждому из вновь созданных IRP пакетов вызовом IoSetCompletionRoutine. При выполнении этого вызова следует передать указатель на исходный IRP пакет в аргументе pContext.
Запомнить число созданных пакетов IRP в неиспользуемом поле исходного IRP пакета. Поле Parameters.Key текущей ячейки стека IRP пакета вполне годится.
Передать пакеты всем нужным драйверам вызовом IoCallDriver.
Возвратить значение STATUS_PENDING, поскольку обработка исходного запроса (пакета IRP) не завершена.
По окончании обработки каждого IRP пакета драйвером нижнего уровня во втором, "асинхронном", варианте вызывается процедура завершения рассматриваемого ("нашего") драйвера, которая выполняет следующие операции:
Выполняет необходимый перенос информации, очистку и удаление созданного драйвером IRP пакета, вернувшегося от нижнего драйвера.
Уменьшает на единицу сохраненное ранее число незавершенных пакетов IRP. Это действие рекомендуется выполнять, приняв хотя бы минимальные меры по безопасному доступу к этому значению. Вполне подходит для этой цели вызов InterlockedDecrement.
В случае, если незавершенных пакетов не осталось, выполняет вызов IoCompleteRequest, что сигнализирует о полном завершении обработки исходного IRP запроса.
Возвращает управление Диспетчеру ввода/вывода с кодом завершения STATUS_MORE_PROCESSING_REQUIRED — для того, чтобы не допустить вызов процедур завершения вышестоящих драйверов для работы над пришедшим "снизу" IRP пакетом, созданным данным драйвером. Кстати заметить, к этому моменту рассматриваемый IRP пакет уже уничтожен.