Разработка драйвера, распознающего USB-устройство как виртуальный COM-порт в системе Windows NT

Тип работы:
Курсовая
Предмет:
Программирование


Узнать стоимость

Детальная информация о работе

Выдержка из работы

Курсовой проект

по дисциплине «Системное программное обеспечение»

Разработка драйвера, распознающего USB-устройство как виртуальный COM-порт в системе Windows NT

Задание

Разработать драйвер, распознающий USB-устройство как виртуальный COM-порт в системе Windows NT. Драйвер должен организовать потоковый ввод-вывод массивов данных (Bulk Data Transfer).

Введение

При решении широкого круга задач возникает необходимость сконфигурировать устройство в соответствии с необходимыми задачами пользователя. К категории этих задач можно отнести разработку драйверов, что очень актуально в современном мире, где информация является одним из важнейших ресурсов.

Одним из компонентов системы может быть драйвер, выполняющий протоколирование обмена информацией между компьютером и некоторым внешним устройством на универсальной последовательной шине USB.

Современные операционные системы могут динамически загружать и выгружать драйвера, получать их из сети (internet), осуществлять взаимодействие между драйверами разного уровня, используя их как своеобразные библиотеки.

В соответствии с заданием к курсовому проекту необходимо разработать фильтр-драйвер на языке C, обеспечивающий обмен информацией между компьютером и внешним USB-устройством. Обеспечить поддержку виртуального преобразования USB-COM (VUSB) с микроконтроллерами FTDI и семейства основных микроконтроллеров ввода-вывода компании Atmel (AT8U2 и выше)

Основные требования, предъявляемые к драйверу:

· Разрабатываемый драйвер должен обрабатывать запросы на чтение и запись, приходящие к устройству;

· Драйвер должен обеспечивать поддержку окончания обработки всех IRP пакетов в стеке перед отключением устройства;

· Пакеты, которые не обрабатываются фильтр-драйвером должны передаваться вниз по стеку основным драйверам;

· Драйвер не должен приводить к сбоям в работе операционной системы;

· Обеспечить поддержку подключения нескольких USB-устройств использованием подсистемы PnP.

1. Основная часть

1.1 Архитектура Windows NT

windows драйвер код шина

Архитектура Windows NT соответствует классическим представлениям о проектировании операционных систем. Наиболее распространены реализации данной ОС для платформы Intel x86 в одно- или многопроцессорных конфигурациях, однако существуют также версии для DEC Alpha и MIPS. Данная операционная система использует защищённый режим центрального процессора, реализует механизмы виртуальной памяти и многозадачности.

Исполняемый код в Windows NT имеет два уровня привилегий: код пользовательского режима и код режима ядра. Уровень привилегий накладывает определённые ограничения: в пользовательском режиме не могут выполняться привилегированные инструкции процессора, не разрешено обращение к защищённым страницам памяти. Эти ограничения накладываются для обеспечения безопасности работы системы. Пользовательское приложение не должно иметь возможность в результате ошибки или преднамеренно вносить изменения в критические таблицы операционной системы или в память других приложений. В частности, такие ограничения запрещают пользовательскому приложению напрямую управлять внешними устройствами, потому что каждое из них является разделяемым ресурсом.

В Windows NT обеспечение обмена данными и управление доступом к внешнему устройству как к разделяемому ресурсу возлагается на его драйвер. Ввод и вывод в драйверах осуществляется посредством IRP-пакетов (Input/Output Request Packet). Запросы на ввод/вывод, посылаемые приложениями или другими драйверами, обрабатываются драйвером, после чего запрашивающей программе в том же пакете посылается статус завершения операции. Подробнее о пакетах ввода/вывода будет сказано далее, общий же принцип взаимодействия проиллюстрирован на Рис. 1.

Рис. 1 Архитектура ввода/вывода Windows NT

Применительно к поставленной задаче, из вышеизложенного следует, что полное протоколирования обмена данными с внешним устройством может быть осуществлено только на уровне драйвера.

Управление внешним устройством в общем случае сводится к заполнению его регистров необходимыми данными. Монопольный доступ драйвера к этим регистрам гарантируется операционной системой. Очевидно, что при данных обстоятельствах требуется, чтобы драйвер устройства выполнялся в режиме ядра.

Обобщённая классификация драйверов Windows NT может быть представлена следующим образом:

· Драйверы режима ядра:

o Унаследованные драйверы;

o Драйверы файловой системы;

o Видеодрайверы;

o Драйверы PnP (Plug And Play):

§ Драйверы WDM.

· Драйверы пользовательского режима:

o Драйверы виртуальных устройств.

1.2 Шина USB

Спецификация USB была разработана консорциумом компаний, включая Intel и Microsoft. Целью нового стандарта было обеспечение организации недорогой среднескоростной шины в таких областях применения, как передача цифровых изображений, компьютерная телефония и мультимедийные игры. Текущими версиями спецификации USB является версии 1.1 и 2.0 (во вторую заложены более высокие скоростные характеристики).

Предельная скорость передачи данных по шине USB спецификации 1.1 составляет 12 Мбит/с (Full Speed). Медленные устройства используют низкую скорость передачи — 1,5 Мбит/с (Low Speed). Стандарт USB версии 2.0 поддерживает физическую скорость передачи до 480 Мбит/с (High Speed). Данные передаются последовательно по паре проводников. Питание для некоторых устройств доступно по отдельным проводникам питания и заземления (для устройств с небольшим энергопотреблением).

Устройства USB могут быть подключены 5-метровым кабелем (а практически — и более длинным). Использование USB-хаба (hub — концентратор) позволяет увеличить дальность размещения устройств от хост-контроллера, а так же количество устройств, подключаемых к одной шине USB. Последовательно можно подключить до пяти хабов, обеспечив длину соединения 30 метров. К хост-контроллеру можно подключить до 127 устройств, шинный адрес которых устанавливается динамически при подключении устройств.

Рис. 2. Сеть USB-устройств

Работа программиста, создающего драйвер внешнего (не находящегося на материнской плате) USB устройства сводится к тому, чтобы воспользоваться программным интерфейсом системных драйверов шины USB, общение с которым происходит при помощи пакетов, называемых URB (USB Request Block) пакетами. Работа с регистрами USB контроллеров на материнской плате теперь стала уделом узкого круга специалистов — разработчиков материнских плат и операционных систем. Всем остальным разработчикам USB-устройств в операционной системе Windows предлагается достаточно развитый программный интерфейс WDM-драйверов, которые берут на себя все аппаратно-ориентированные операции.

1.2.1 Внутренняя организация шины USB

Все операции по передаче данных по шине USB инициируются хостом. Периферийные устройства не могут сами начать обмен данными, они могут только реагировать на команды хоста. Рассмотрим общую схему обмена данными по шине USB.

Система USB разделяется на три логических уровня с определенными правилами взаимодействия. Устройство USB содержит интерфейсную, логическую и функциональную части. Хост тоже делится на три части: интерфейсную, системную и программное обеспечение. Каждая часть отвечает только за определенный круг задач. Логическое и реальное взаимодействие между ними показано на рисунке 3.

Рис. 3. Взаимодействие компонентов USB

Таким образом, операция обмена данными между прикладной программой и шиной USB выполняется путем передачи буферов памяти через следующие уровни:

· уровень клиентского программного обеспечения в хосте — обычно представляется драйвером устройства USB, обеспечивает взаимодействие пользователя с операционной системой с одной стороны и системным драйвером с другой;

· уровень системного программного обеспечения USB в хосте (USBD, Universal Serial Bus Driver) — управляет нумерацией устройств на шине, управляет распределением пропускной способности шины и мощности питания, обрабатывает запросы пользовательских драйверов;

· хост-контроллер интерфейса шины USB (HCD, Host Controller Driver) — преобразует запросы ввода/вывода в структуры данных, по которым хост-контроллер выполняет физические транзакции, работает с регистрами хост-контроллера.

Уровень клиентского программного обеспечения определяет тип передачи данных, необходимый для выполнения затребованной прикладной программой операции. После определения типа передачи данных этот уровень передает системному уровню следующее:

· буфер памяти, называемый клиентским буфером;

· пакет IRP, указывающий тип необходимой операции. Непосредственной обработкой запроса занимается системный драйвер USB.

Уровень системного драйвера USB необходим для управления ресурсами USB. Он отвечает за выполнение следующих действий:

· распределение полосы пропускания шины USB;

· назначение логических адресов устройств каждому физическому USB-устройству;

· планирование транзакций.

Логическое устройство USB представляет собой набор независимых конечных точек, с которыми клиентское программное обеспечение обменивается информацией. Каждому логическому устройству USB назначается свой адрес, уникальный на данной шине USB. Каждая конечная точка логического устройства идентифицируется своим номером и направлением передачи (IN — передача к хосту, OUT — от хоста).

Транзакция на шине USB — это последовательность обмена пакетами между хостом и периферийным устройством, в ходе которой может быть передан или принят один пакет данных. Когда клиентское программное обеспечение передает IRP уровню системного драйвера, USB-драйвер преобразует их в одну или несколько транзакций шины и затем передает получившийся перечень транзакций драйверу контроллера хоста.

Системный драйвер USB состоит из драйвера USB и драйвера хост-контроллера. Когда клиентский уровень передает IRP уровню системного обеспечения USB, USB-драйвер преобразует их в одну или несколько транзакций шины и затем передает получившийся перечень транзакций драйверу контроллера хоста. Драйвер контроллера хоста принимает от системного драйвера шины перечень транзакций и выполняет следующие действия:

· планирует исполнение полученных транзакций, добавляя их к списку транзакций;

· извлекает из списка очередную транзакцию и передает ее уровню хост-контроллера интерфейса шины USB;

· отслеживает состояние каждой транзакции вплоть до ее завершения.

При выполнении всех связанных с командным пакетом транзакций системный уровень уведомляет об этом клиентский уровень.

Уровень хост-контроллера интерфейса шины USB получает отдельные транзакции от драйвера контроллера хоста (в составе уровня системного обеспечения USB) и преобразует их в соответствующую последовательность операций шины. В результате этого USB-пакеты передаются вдоль всей физической иерархии хабов до периферийного USB-устройства.

Нижний уровень периферийного USB-устройства называется уровнем интерфейса шины USB. Он взаимодействует с интерфейсным уровнем шины USB на стороне хоста и передает пакеты данных от хоста к периферийному устройству в формате, определяемом спецификацией USB. Затем он передает пакеты вверх — уровню логического USB-устройства.

Средний уровень периферийного устройства USB-устройства называется уровнем логического USB-устройства. Каждое логическое USB-устройство представляется набором своих конечных точек, с которыми может взаимодействовать системный уровень USB-хоста. Эти точки являются источниками и приемниками всех коммуникационных потоков между хостом и периферийными USB-устройствами.

Самый верхний уровень периферийного USB-устройства называется функциональным уровнем. Этот уровень соответствует уровню клиентского обеспечения хоста. С точки зрения клиентского уровня, нижележащие уровни нужны для организации между ним и конечными точками прямых «каналов данных», которые идут вплоть до функционального уровня. А с точки зрения нашей схемы функциональный уровень выполняет следующие действия:

· получает данные, посылаемые клиентским уровнем хоста из конечных точек каналов данных нижележащего уровня логического USB-устройства;

· посылает данные клиентскому уровню хоста, направляя их в конечные точки каналов данных нижележащего уровня логического USB-устройства.

Логически передача данных между конечной точкой и программным обеспечением производится с помощью выделения канала и обмена данными по этому каналу, а с точки зрения представленных уровней, передача данных выглядит следующим образом:

Рис. 4. Уровни передачи данных

Конечная точка (Endpoint) — это часть USB-устройства, которая имеет уникальный идентификатор и является получателем или отправителем информации, передаваемой по шине USB. Проще говоря, это буфер, сохраняющий несколько байт. Обычно это блок данных в памяти или регистр микроконтроллера. Данные, хранящиеся в конечной точке, могут быть либо принятыми данными, либо данными, ожидающими передачу. Хост также имеет буфер для приема и передачи данных, но хост не имеет конечных точек.

Конечная точка имеет следующие основные параметры:

· частота доступа к шине;

· допустимая величина задержки обслуживания;

· требуемая ширина полосы пропускания канала;

· номер конечной точки;

· способ обработки ошибок;

· максимальный размер пакета, который конечная точка может принимать или отправлять;

· используемый конечной точкой тип посылок;

· направление передачи данных.

Любое USB-устройство имеет конечную точку с нулевым номером (Endpoint Zero). Эта точка позволяет хосту опрашивать устройство с целью определения его типа и параметров, выполнять инициализацию и конфигурирование устройства.

Кроме нулевой точки, устройства, обычно, имеют дополнительные конечные точки, которые используются для обмена данными с хостом. Дополнительные точки могут работать либо только на прием данных от хоста (входные точки, IN), либо только на передачу данных хосту (выходные точки, OUT).

Нулевая точка устройства доступна после того, как устройство подключено к шине, включено и получило сигнал сброса по шине (bus reset). Все остальные конечные точки после включения питания или сброса находятся в неопределенном состоянии и недоступны для работы до тех пор, пока хост не выполнит процедуру конфигурирования устройства.

Спецификация шины определяет четыре различных типа передачи данных для конечных точек:

· управляющие передачи (Control Transfers) — используются хостом для конфигурирования устройства во время подключения, для управления устройством и получения статусной информации в процессе работы. Протокол обеспечивает гарантированную доставку таких посылок;

· передачи массивов данных (Bulk Data Transfers) — применяются при необходимости обеспечения гарантированной доставки данных от хоста к функции или от функции к хосту, но время доставки не ограничено;

· передачи по прерываниям (Interrupt Transfers) — используются в том случае, когда требуется передавать одиночные пакеты данных небольшого размера. Каждый пакет требуется передать за ограниченное время. Операции передачи носят спонтанный характер и должны обслуживаться не медленнее, чем того требует устройство;

· изохронные передачи (Isochronous Transfers) — применяются для обмена данными в «реальном времени», когда на каждом временном интервале требуется передавать строго определенное количество данных, но доставка информации не гарантирована (передача данных ведется без повторения при сбоях, допускается потеря пакетов).

Канал (pipe) — это логическое соединение между конечной точкой устройства и ПО хоста. Существует две модели каналов:

· потоковый канал (или просто поток, streaming pipe) — это канал для передачи данных, структура которых определяется клиентским ПО. Потоки используются для передачи массивов данных, передачи данных по прерываниям и изохронной передачи данных. Поток всегда однонаправленный. Один и тот же номер конечной точки может использоваться для двух разных потоковых каналов — ввода и вывода. Передачи данных в потоковых каналах подчиняются следующим правилам:

§ запросы клиентских драйверов для разных каналов, поставленные в определенном порядке друг относительно друга, могут выполняться в другом порядке;

§ запросы для одного канала будут исполняться строго в порядке их поступления;

§ если во время выполнения какого-либо запроса происходит серьезная ошибка (STALL), поток останавливается;

· канал сообщений (message pipe или control pipe) — это канал для передачи данных, структура которых определяется спецификацией USB. Каналы этого типа двунаправленные и применяются для передачи управляющих посылок. Каналы сообщений строго синхронизированы — спецификация USB запрещает одновременную обработку нескольких запросов: нельзя начинать передачу нового сообщения, пока не завершена обработка предыдущего. В случае возникновения ошибки передача сообщения может быть прервана хостом, после чего хост может начать передачу нового сообщения.

Основными характеристиками каналов являются:

§ полоса пропускания канала;

§ используемый каналом тип передачи данных;

§ характеристики, соответствующие конечной точке: направление передачи данных и максимальный размер пакета.

Полоса пропускания шины делится между всеми установленными каналами. Выделенная полоса закрепляется за каналом, и если установление нового канала требует такой полосы, которая не списывается в уже существующее распределение, запрос на выделение канала отвергается. Архитектура USB предусматривает внутреннюю буферизацию всех устройств, причем, чем большей полосы пропускания требует устройство, тем больше должен быть его буфер. Шина USB должна обеспечивать обмен с такой скоростью, чтобы задержка данных в устройстве, вызванная буферизацией, не превышала нескольких миллисекунд.

Канал сообщений, связанный с нулевой конечной точкой, называется Основным каналом сообщений (Default Control Pipe или Control Pipe 0). Владельцем этого канала является USBD, и он используется для конфигурирования устройства. Основной канал сообщений поддерживает только управляющие передачи. Остальные каналы (они называются клиентскими каналами, Client Pipe) создаются в процессе конфигурирования устройства. Их владельцами являются драйверы устройств. По клиентским каналам могут передаваться как потоки, так и сообщения с помощью любых типов передач.

Набор клиентских каналов, с которыми работает драйвер устройства, называется интерфейсом устройства или связкой клиентских каналов.

1.3 Драйверная модель WDM

WDM (Windows Driver Model) — новая модель архитектуры драйверов, предложенная Microsoft для Windows 2000, хотя эта архитектура развивалась, начиная с Windows 3. 11, продолжая развиваться и в Windows 98 и Windows NT, но по-настоящему полной она стала только в Windows 2000.

С точки зрения WDM, существует три типа драйверов:

· Драйвер шины — драйвер, обслуживающий контроллер шины, адаптер, мост или любые другие устройства, имеющие дочерние устройства. Для каждого типа шины в операционной системе имеется свой драйвер;

· Функциональный драйвер — основной драйвер устройства, предоставляющий его функциональный интерфейс. Этот драйвер обязателен кроме тех случаев, когда ввод-вывод осуществляется драйвером шины или драйвером фильтров шины. Функциональный драйвер по определению обладает наиболее полной информацией о своем устройстве. Обычно только этот драйвер имеет доступ к специфическим регистрам устройства;

· Драйвер фильтра — драйвер, поддерживающий дополнительную функциональность устройства (или существующего драйвера) или изменяющий запросы ввода/вывода и ответы на них от других драйверов. Таких драйверов может быть несколько, хотя их присутствие необязательно. Они могут работать как на более высоком уровне, чем функциональный драйвер или драйвер шины, так и на более низком.

В среде WDM один драйвер не может контролировать все аспекты устройства: драйвер шины информирует диспетчера PnP об устройствах, подключенных к шине, в то время как функциональный драйвер управляет устройством.

1.3.1 Драйверные слои

Согласно перечисленным выше типам драйверов, существует три типа
объектов:

· Объекты физических устройств (PDO, Physical Device Object) — эти объекты создаются для каждого физически идентифицируемого элемента аппаратуры, подключенного к шине данных;

· Объекты функциональных устройств (FDO, Functional Device Object) — подразумевает единицу логической функциональности устройства;

· Объекты фильтров устройств (FiDO, Filter Device Object) — предоставляют дополнительную функциональность.

В Windows NT последовательность загрузки драйверов устройств такая:

1. Во время загрузки операционной системы производится загрузка шинных драйверов для каждой известной системе шины (список шин создается при установке операционной системы и сохраняется в реестре);

2. Вызывается DriverEntry, а затем AddDevice для каждого шинного драйвера. В AddDevice создается FDO для драйвера системной шины. Затем на созданный FDO отправляется запрос IRP_MN_START_DEVICE;

3. Шинный драйвер составляет список всех устройств, подключенных к шине. Для каждого найденного устройства создается объект PDO;

4. На каждый PDO посылается запрос IRP_MN_QUERY_DEVICE_RELATION, в ответ на который шинный драйвер возвращает идентификаторы всех найденных устройств;

5. На эти PDO посылают запрос IRP_MN_QUERY_ID, в ответ на который драйвер системной шины сообщает идентификаторы этих устройств;

6. Получив идентификаторы, система пытается найти и загрузить драйверы устройств;

7. Найдя драйвер для устройств, система загружает его в память, вызывая его DriverEntry. Потом вызывается AddDevice, где создается FDO для устройства. Если устройств, управляемых этим драйвером, несколько, то AddDevice будет вызвана для каждого устройства. Если в реестре зарегистрированы дополнительные фильтры, то они также загружаются в память. Затем система посылает на FDO запрос IRP_MN_START_DEVICE;

8. Происходит посылка на FDO запроса IRP_MN_QUERY_DEVICE_RELATIONS. Если устройство само является шиной или держит на себе другие устройства, которыми само не управляет, то для устройства на нем повторяется вся последовательность действий, начиная с пункта 5.

Функция AddDevice, вызываемая для каждого FDO, вызывает IoCreateDevice и IoAttachDeviceToStack, обеспечивая построение стека устройств. Стек устройств обеспечивает прохождение запросов от пользовательских программ до аппаратного (нижнего) уровня драйверов (Рис. 5).

Из вышесказанного становится понятным, что разрабатываемый драйвер должен являться драйвером-фильтром нижнего уровня, связанным с клиентским драйвером USB-накопителя. Необходимость работы с клиентским USB-драйвером объясняется тем, что именно на этом уровне перехватываемая информация обладает требуемой структурированностью — передаются именно файлы, а не блоки информации (кадры или составленные из них транзакции), определяемые протоколом обмена по USB.

Рис. 5. Стек устройств

1.3.2 Точки входа WDM-драйвера

WDM-драйверы отличаются от унаследованных драйверов тем, что должны содержать дополнительные точки входа для поддержки PnP, и, в целом, они более логичны по структуре. Приведем список точек входа и кратко охарактеризуем их назначение:

NTSTATUS DriverEntry (

IN PDRIVER_OBJECT DriverObject, // указатель на объект драйвера

IN PUNICODE_STRING RegistryPath) // путь к подразделу регистра,

// относящегося к драйверу

Эта функция выполняется при загрузке драйвера операционной системой. В WDM-драйверах на DriverEntry возложены обязанности по регистрации всех остальных точек входа драйвера.

NTSTATUS AddDevice (

IN PDRIVER_OBJECT DriverObject, // указатель на объект драйвера

IN PDEVICE_OBJECT PhysicalDeviceObject) // указатель на родительский PDO

В драйверах, поддерживающих PnP, через эту точку входа менеджер PnP посылает драйверу уведомление об обнаружении устройства, за которое должен отвечать драйвер. Функция AddDevice должна создать объект устройства с помощью вызова IoCreateDevice и при необходимости присоединить его к стеку устройств с помощью IoAttachDeviceToDeviceStack.

NTSTATUS DriverUnload (

IN PDRIVER_OBJECT DriverObject) // указатель на объект драйвера

Вызывается при выгрузке драйвера. В этой функции должны осво
бождаться все затребованные драйвером ресурсы. Драйверы WDM-модели
выполняют эти действия в обработчике запросов IRP_MJ_PNP с субкодом IRP_MN_REMOVE_DEVICE, то есть при удалении устройства из системы.

Следует выделить отдельный класс точек входа драйвера, которые предназначены для обработки IRP-пакетов с различными кодами операций. Эти точки входа регистрируются при загрузке драйвера в функции DriverEntry. Регистрация производится путем заполнения элементов массива MajorFunction адресами диспетчеризуемых функций. Индексом в этом массиве являются коды IRP_MJ_XXX, то есть описанные числами типы пакетов IRP. Диспетчер ввода/вывода, ориентируясь на заполнение этого массива, вызывает нужные функции драйвера.

1.3.3 Пакет запроса ввода/вывода (IRP)

Пакеты ввода/вывода (IRP-пакеты) используются для передачи запросов к драйверу от его клиентов. Они являются структурами данных переменной длины, и состоят из стандартного заголовка, содержащего общую учетную информацию, и одного или нескольких блоков параметров, называемых ячейками стека ввода/вывода (I/O Stack Location).

Приведем структуру заголовка IRP-пакета:

Таблица 1. Структура заголовка IRP-пакета

Поля

Описание

IO_STATUS_BLOCK IoStatus

Статус запроса

PVOID AssociatedIrp. SystemBuffer

Указатель на системный буфер для случая, если устройство поддерживает буферизованный ввод/вывод

PMDL MdlAddress

Указатель на MDL-список в случае, если устройство поддерживает прямой ввод/вывод

PVOID UserBuffer

Адрес пользовательского буфера для ввода/вывода

BOOLEAN Cancel

Индикатор того, что IRP-пакет должен быть аннулирован

Основное назначение ячеек стека ввода/вывода состоит в том, чтобы хранить функциональный код и параметры запроса на ввод/вывод. Ниже, в таблице 1.5.2 приводятся поля ячеек стека ввода/вывода, к которым драйвер может обращаться непосредственно по указателю (чего не рекомендуется делать для остальных полей):

Таблица 2. Структура ячейки стека ввода/вывода

Поля

Описание

UCHAR MajorFunction

Код IRP_MJ_XXX, описывающий назначение операции

UCHAR MinorFunction

Субкод операции

PDEVICE_OBJECT DeviceObject

Указатель на объект устройства, которому был адресован данный объект IRP

PFILE_OBJECT FileObject

Файловый объект для данного запроса, если он задан

union Parameters (трактовка определяется значением MajorFunction)

struct Read

Параметры для IRP типа IRP_MJ_READ:

ULONG Length

ULONG Key

LARGE_INTEGER ByteOffset

struct Write

Параметры для IRP типа IRP_MJ_WRITE:

ULONG Length

ULONG Key

LARGE_INTEGER ByteOffset

struct DeviceControl

Параметры для IRP типа IRP_MJ_DEVICE_CONTROL:

ULONG OutputBufferLength

ULONG InputBufferLength

ULONG IoControlCode

PVOID Type3InputBuffer

Приведем графическое представление структуры IRP-пакета:

Рис. 6. Структура IRP пакета

Общение с USB-устройствами в ОС Windows NT на уровне драйверов, как было сказано в разделе внутренней организации USB, происходит посредством передачи URB-пакетов. Указатели на URB-пакеты содержат ячейки стека IRP-пакета, доступ к этим указателям осуществляется следующим образом:

PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation (Irp);

PURB Urb = IrpSp-> Parameters. Others. Argument1;

Приведем частичное объявление структуры из справочной документации Microsoft. Отметим только поля, использование которых необходимо в рамках данной курсовой работы:

typedef struct _URB {

union{

struct _URB_HEADER UrbHeader;

struct _URB_SELECT_INTERFACE UrbSelectInterface;

struct _URB_SELECT_CONFIGURATION UrbSelectConfiguration;

struct _URB_BULK_OR_INTERRUPT_TRANSFER UrbBulkOrInterruptTransfer;

}

} URB, *PURB;

Поле UrbHeader хранит информацию о коде URB-пакета, по которому можно определить, какая операция запрашивается.

Поля UrbSelectInterface и UrbSelectConfiguration служат для запроса по выбору интерфейса и конфигурации устройства, которые будут использоваться при работе с устройством. Пакеты этой структуры отправляются хостом к устройству в начале его работы, при конфигурировании.

Поле UrbBulkOrInterruptTransfer несет наиболее важную в рамках данной курсовой работы информацию — указатели на блоки ввода/вывода USB-устройства. Приведем описание структуры _URB_BULK_OR_INTERRUPT_TRANSFER:

struct _URB_BULK_OR_INTERRUPT_TRANSFER {

struct _URB_HEADER Hdr;

USBD_PIPE_HANDLE PipeHandle;

ULONG TransferFlags;

ULONG TransferBufferLength;

PVOID TransferBuffer;

PMDL TransferBufferMDL; };

Поля этой структуры описаны в следующей таблице:

Таблица 3. Поля структуры _URB_BULK_OR_INTERRUPT_TRANSFER.

Поле

Описание

struct _URB_HEADER Hdr

Стандартный заголовок URB-пакета, содержащий код запроса

USBD_PIPE_HANDLE PipeHandle

Дескриптор канала, на который передаются данные

ULONG TransferFlags

Флаги, определяющие направление передачи данных и способ обработки ошибок

ULONG TransferBufferLength

Длина передаваемого блока данных в байтах

PVOID TransferBuffer

Указатель на передаваемый буфер. Буфер находится в нестраничной памяти

PMDL TransferBufferMDL

Указатель на MDL-список, несущий передаваемую информацию. Буфер находится в страничной памяти

Следует отметить, что один из указателей TransferBuffer или TransferBufferMDL равен NULL, то есть в пределах одного пакета передается только одна порция данных.

Задача протоколирования обмена информацией сводится к перехвату и сохранению буферов TransferBuffer и TransferBufferMDL.

1.3. 4 Уровни запроса прерываний

В каждый момент времени центральный процессор находится на одном из уровней IRQL (Interrupt Request Level — уровень запросов прерываний). Уровни IRQL располагаются в порядке убывания от HIGHEST_LEVEL до PASSIVE_LEVEL. Каждому из прерываний (прерывания от внешних устройств, системные часы, и т. д.) соответствует свой уровень IRQL. Специальным действиям операционной системы также назначены IRQL. Они отмечены в нижней части приведённой таблицы:

Таблица 4. Уровни запросов прерываний

Уровень

Назначение

HIGHEST_LEVEL

Наивысший уровень. Все прерывания заблокированы

POWER_LEVEL

Прерывания по отказу питания

IPI_LEVEL

Межпроцессорное взаимодействие

CLOCK2_LEVEL

Прерывание по системному таймеру 2

СLOCK1_LEVEL

Прерывание по системному таймеру 1

PROFILE_LEVEL

Прерывание по таймеру замера производительности

уровни DRQL

Обычные прерывания устройств

DISPATCH_LEVEL

Диспетчеризация потоков и выполнение отложенных процедур

APC_LEVEL

Выполнение асинхронного вызова процедуры

PASSIVE_LEVEL

Обычное исполнение кода потока

Общее правило обработки уровней запросов прерываний гласит, что прерывания с IRQL, меньшим, чем у выполняемого в данный момент кода, маскируются. Во время исполнения кода потока (пользовательского или системного) устанавливается наименьший IRQL = 0 (PASSIVE_LEVEL). Работа драйвера чаще всего выполняется на уровне IRQL = 2 (DISPATCH_LEVEL). Уровни, лежащие над ним, называются DIRQL (Device IRQL) и выставляются для обработчиков прерываний от внешних устройств (ISR -- interrupt service routine). Даже во время выполнения ISR драйвера может произойти прерывание с большим IRQL, например, принадлежащее другому драйверу.

Чем выше текущий уровень IRQL исполняемого кода, тем меньше функций ему доступно. Так, например, диспетчер потоков работает на уровне
DISPATCH_LEVEL, и, следовательно, не будет вызываться, пока на процессоре с уровнем большим или равным DISPATCH_LEVEL исполняется другой код. Таким образом, на уровнях DISPATCH_LEVEL и выше отключается переключение потоков. Функции ожидания диспетчерских объектов (события, мьютексы, семафоры) с отличным от нуля временем, обращение к файлам, подкачка отсутствующих в физической памяти страниц -- всё это также становится недоступным. Для корректного сохранения запросов в файле фильтр в таких случаях должен применять специальную методику.

1.3. 5 Уведомление о завершении запроса нижестоящим драйвером

При отслеживании обмена данными драйвер-фильтр может получать уведомления о том, что некоторый переданный запрос был завершён нижестоящим драйвером. Механизм уведомления заключается в том, что вызовом специальной функции IoSetCompletionRoutine фильтр обращается к стеку в пакете IRP. В позиции стека, следующей за текущей позицией, он устанавливает в специальном поле адрес функции завершения (completion routine). Затем при передаче пакета по цепочке позиция стека увеличивается.

Когда нижестоящий драйвер отправляет пакет запроса на завершение (вызовом IoCompleteRequest), подсистема ввода/вывода начинает просматривать стек внутри этого пакета от конца к началу. Если в какой-то позиции стека определена функция завершения, управление передаётся ей. Отработав, функция возвращает результат, сигнализирующий об успехе, ошибке или необходимости дальнейшей обработки запроса.

При первых двух вариантах подсистема ввода/вывода переходит к следующей позиции стека и продолжает просмотр, пока не будет достигнуто его начало. После этого запрос завершается нормальным образом.

При третьем же варианте просмотр стека немедленно прекращается и запрос не будет завершён. Эта возможность реализована для того, чтобы драйвер-фильтр мог выполнить какие-либо действия над пакетом запроса после того, как тот будет обработан в нижестоящем драйвере. После такой «дополнительной обработки» пакет снова должен быть отправлен на завершение.

2. Техническая часть

2.1 Точки входа разрабатываемого драйвера

Разрабатываемый драйвер является драйвером нижнего уровня. В стеке драйверов USB-накопителя он находится непосредственно под драйвером устройства, если после его загрузки не произойдет установки какого-либо другого драйвера-фильтра нижнего уровня.

Разрабатываемый драйвер включает в себя следующие точки входа:

· DriverEntry; В этой функции происходит регистрация всех стандартных точек входа драйвера и обработчиков IRP-пакетов.

· AddDevice; Управление этой функции передается диспетчером ввода/вывода после того, как завершает свою работу DriverEntry. AddDevice создает функциональный объект устройства с помощью вызова IoCreateDevice и подключает его к стеку драйверов выбранного устройства (вызовом IoAttachDeviceToDeviceStack). Кроме того, в этой функции производятся действия по подготовке к протоколированию: считываются настройки из системного реестра, выделяется буфер для сбора протоколируемой информации, создается лог-файл.

· DriverUnload; Функция DriverUnload необходима для того, чтобы сделать драйвер выгружаемым. В унаследованных драйверах на эту функцию возложен весь процесс выгрузки драйвера: удаление символьных ссылок, объектов устройств драйвера, отключение прерываний от объектов, освобождение выделенной памяти. В WDM-драйверах все эти действия возложены на функцию-обработчик пакетов с кодом IRP_MJ_PNP.

Функции обработки IRP-пакетов:

· FilterDispatchPnp; На эту функцию возложены обязанности по обработке IRP-пакетов с различными кодами, хотя в разрабатываемом драйвере существует необходимость в обработке только двух типов запросов. Все запросы с кодом, отличным от IRP_MJ_PNP передаются вниз по стеку драйверов без изменений. Запросы же IRP_MJ_PNP диспетчеризуются по суб-кодам в функции PnP_Dispatch. Необходимость диспетчеризации по суб-кодам запросов IRP_MJ_PNP вызвана тем, что драйвер не должен нарушать порядка работы операционной системы и обязан подчиняться PnP-менеджеру, то есть в драйвере должны корректно обрабатываться события старта и удаления устройства.

· FilterDispatchIoVista; Запросы ввода/вывода к USB-накопителю передаются в составе IRP-пакетов с кодом IRP_MN_INTERNAL_DEVICE_CONTROL. Этот пакет содержит полную информацию о направлении и характере передаваемых данных. То есть для протоколирования обмена информацией с USB-устройством следует перехватывать пакеты именно этого типа.

Для того чтобы перехватывать информацию, передаваемую в обоих направлениях, следует установить функцию обратного вызова диспетчера ввода/вывода. При наличии этой функции разрабатываемый драйвер-фильтр получит возможность перехвата данных, передаваемых от устройства к хосту.

Такая методика выбрана из-за того, что функция FilterDispatchIoVista работает на уровне запроса прерываний, равном DISPATCH_LEVEL, что сильно затрудняет использование механизмов синхронизации, которые могли бы позволить перейти на уровень запроса прерываний, равный PASSIVE_LEVEL, где становятся доступными функции работы с файлами. Если бы это было достигнуто в разрабатываемом драйвере, то отпала бы необходимость выделения больших объемов нестраничной памяти для хранения протокола.

Запись файла на диск в момент удаления устройства возможна, так как это событие инициализируется PnP-менеджером, запросы которого всегда происходят на уровне IRQL, равном PASSIVE_LEVEL.

2.2 Размещение кода драйвера в памяти

Некоторые функции драйвера, например те, которые выполняют инициализацию, выгодно выполнить и освободить память, занимаемую ими. В языке C есть специальная директива #pragma_alloc_text (< тип секции>, < имя размещаемой функции>), позволяющая управлять размещением кода. В качестве типа секции могут указываться значения «INIT» или «PAGE».

Функции с размещением в секции «INIT» выгружаются, и память, занимаемая ими, освобождается сразу по завершении их работы. В разрабатываемом драйвере в секции «INIT» размещена точка входа DriverEntry, поскольку она выполняется единожды при загрузке драйвера.

Точки входа AddDevice и DriverUnload располагаются в секции «PAGE», то есть в страничной памяти, поскольку они гарантированно вызываются на уровне привилегий, равном PASSIVE_LEVEL и, даже оказавшись выгруженными на диск, будут немедленно загружены в физическую память менеджером страничной памяти (который способен работать только на уровне PASSIVE_LEVEL).

Уровень привилегий его запросов слабо предсказуем и может быть равен DISPATCH_LEVEL. На этом уровне подкачка страниц невозможна, что при обращении к выгруженной функции приведет к краху системы.

2.3 Установка драйвера в системе

Драйвер собирается средствами пакета Microsoft Windows Driver Kits (WDK 7600. 16 385. 1):

Компиляция драйвера в WDK:

Устанавливается вызовом стандартного PnP-менеджера установки устройств.

2.4 Драйвер-фильтр

От разрабатываемого драйвера требуется достаточная скорость надежность. Вопрос скорости можно поднимать как основной только при условии подключения множества устройств. При его работе осуществляется множество манипуляций с памятью, операций с указателями, преобразований типов. Важно представлять структуру скомпилированного продукта, чтобы правильно представить себе логику его работы. Среди языков программирования, удовлетворяющих этим требованиям, известны языки С и ассемблер. Для написания драйвера предпочтение было отдано языку C.

Для описания работы драйвера с устройством составлена общая схема коммуникации разработанного фильтр-драйвера с другими драйверами ОС Windows и устройствами:

/

Драйвер состоит из основных логических частей:

· Makefile

· Sorces

· Заголовочный файл. h

· Исполнительный файл. c

3. Реализация кода драйвера

Код реализован на языке C, комментарии поясняют процессы, происходящие в отдельных функциях

Компонент makefile

#

# DO NOT EDIT THIS FILE!!! Edit. sources. if you want to add a new source

# file to this component. This file merely indirects to the real make file

# that is shared by all the driver components of the Windows NT DDK

#

Компонент sources

!include sources. inc

TARGETNAME=vm01vusb

C_DEFINES= $(C_DEFINES)

/DVERSION_MAJOR=1 /DVERSION_MINOR=0 /DVERSION_MICRO=0

/DVERSION_NANO=0 /DVER_VERSION=1,0,0,0

/DVER_VERSION_STR=""1.0.0. 0″"

Компонент vm01vusb. h

#include < ntddk. h>

#if !defined (_VM01VUSB_H_)

#define _VM01VUSB_H_

#define DRIVERNAME «vm01vusb. sys: «

#define USE_BULK L"\vm01vusb\UseBulk"

#define DBG_LEVEL 0xffffffff

#if DBG

#define DbgPrt (_x_) DbgPrintEx _x_

#define TRAP () DbgBreakPoint ()

#else

#define DbgPrt (_x_)

#define TRAP ()

#endif

#ifndef STATUS_CONTINUE_COMPLETION

#define STATUS_CONTINUE_COMPLETION STATUS_SUCCESS

#endif

#define POOL_TAG 'vUSB'

typedef enum _DEVICE_PNP_STATE {

NotStarted = 0, // Еще не запущен

Started, // Устройством получено START_DEVICE IRP

StopPending, // Устройством получено QUERY_STOP IRP

Stopped, // Устройством получено STOP_DEVICE IRP

RemovePending, // Устройством получено QUERY_REMOVE IRP

SurpriseRemovePending, // Устройством получено SURPRISE_REMOVE IRP

Deleted // Устройством получено REMOVE_DEVICE IRP

} DEVICE_PNP_STATE;

#define INITIALIZE_PNP_STATE (_Data_)

(_Data_)-> DevicePnPState = NotStarted;

(_Data_)-> PreviousPnPState = NotStarted;

#define SET_NEW_PNP_STATE (_Data_, _state_)

(_Data_)-> PreviousPnPState = (_Data_)-> DevicePnPState;

(_Data_)-> DevicePnPState = (_state_);

#define RESTORE_PREVIOUS_PNP_STATE (_Data_)

(_Data_)-> DevicePnPState = (_Data_)-> PreviousPnPState;

typedef enum _DEVICE_TYPE {

DEVICE_TYPE_INVALID = 0, // Неопределенный тип;

DEVICE_TYPE_FIDO, // Устройство-фильтр.

DEVICE_TYPE_CDO, // Управляющее устройство.

} DEVICE_TYPE;

// common header для device extension управляющих и фильтрующих объектов устройств

typedef struct _COMMON_DEVICE_DATA

{

DEVICE_TYPE Type;

} COMMON_DEVICE_DATA, *PCOMMON_DEVICE_DATA;

typedef struct _DEVICE_EXTENSION

{

COMMON_DEVICE_DATA;

// Обратный указатель на объект устройства.

//

PDEVICE_OBJECT Self;

// Верхушка стека до добавления драйвера.

//

PDEVICE_OBJECT NextLowerDriver;

// текущее PnP состояние устройства

//

DEVICE_PNP_STATE DevicePnPState;

// запоминаем PnP состояние устройства

//

DEVICE_PNP_STATE PreviousPnPState;

// снимаем lock для передачи IRP-пакетов, устройство может быть удалено

// драйвер можеты быть безопасно выгружен.

//

IO_REMOVE_LOCK RemoveLock;

// set когда PendingIoCount переходит в 0; флаги устройства могут быть выгружены

KEVENT RemoveEvent;

// set когда PendingIoCount переходит в 1 (первое переключение в Add Device)

// сигнализирует об отсутствии запросов ввода-вывода

KEVENT NoPendingIoEvent;

// установка сигнала сгенерированного драйвером запроса по питанию завершена

KEVENT SelfRequestedPowerIrpEvent;

// спинлок для защиты inc/dec перобразований логиким ввода-вывода

KSPIN_LOCK IoCountSpinLock;

// инкремент когда добавлено устройство или получен запрос ВВ

// декремент когда запрос завершен, спущен ниже или устройство удалено

ULONG PendingIoCount;

// флаг при запуске IRP_MN_REMOVE_DEVICE

BOOLEAN DeviceRemoved;

// флаг когда драйвер вернул SUCCESS при IRP_MN_QUERY_REMOVE_DEVICE

BOOLEAN RemoveDeviceRequested;

// флаг когда драйвер вернул SUCCESS при IRP_MN_QUERY_STOP_DEVICE

BOOLEAN StopDeviceRequested;

// флаг когда устройство успешно запущено

BOOLEAN DeviceStarted;

// флаг когда IRP_MN_WAIT_WAKE запрошен и мы в power state где мы можем игнализировать ожиданию

BOOLEAN EnabledForWakeup;

//флаг конкретной обработки самогененрированного запроса питания

BOOLEAN SelfPowerIrp;

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

DRIVER_INITIALIZE DriverEntry;

DRIVER_ADD_DEVICE FilterAddDevice;

__drv_dispatchType (IRP_MJ_PNP)

DRIVER_DISPATCH FilterDispatchPnp;

__drv_dispatchType (IRP_MJ_POWER)

DRIVER_DISPATCH FilterDispatchPower;

__drv_dispatchType (IRP_MJ_INTERNAL_DEVICE_CONTROL)

DRIVER_DISPATCH FilterDispatchIoXp;

__drv_dispatchType (IRP_MJ_INTERNAL_DEVICE_CONTROL)

DRIVER_DISPATCH FilterDispatchIoVista;

__drv_dispatchType_other

DRIVER_DISPATCH FilterPass;

DRIVER_UNLOAD FilterUnload;

IO_COMPLETION_ROUTINE FilterDeviceUsageNotificationCompletionRoutine;

NTSTATUS

FilterStartCompletionRoutine (

__in PDEVICE_OBJECT DeviceObject,

__in PIRP Irp,

__in PVOID Context

);

PCHAR

PnPMinorFunctionString (

UCHAR MinorFunction

);

#endif

Компонент vm01vusb. c

#include < wdm. h>

#include < usbdi. h>

#include «vm01vusb. h»

#ifdef ALLOC_PRAGMA

#pragma alloc_text (INIT, DriverEntry)

#pragma alloc_text (PAGE, FilterAddDevice)

#pragma alloc_text (PAGE, FilterDispatchPnp)

#pragma alloc_text (PAGE, FilterUnload)

#pragma alloc_text (PAGE, FilterDispatchIoXp)

#pragma alloc_text (PAGE, FilterDispatchIoVista)

#endif

/*++ Основная функция загрузки драйвера DriverEntry название остается неизменным, системой передается ссылка на объект драйвера и указатель на Unicode-строку, содержащую путь в реестре к драйверу

--*/

NTSTATUS

DriverEntry (

__in PDRIVER_OBJECT DriverObject,

__in PUNICODE_STRING RegistryPath

)

{

NTSTATUS status = STATUS_SUCCESS;

ULONG ulIndex;

PDRIVER_DISPATCH * dispatch;

ULONG value;

UNREFERENCED_PARAMETER (RegistryPath);

// Обработчики драйвера

// Индекс верхнего устройства в стеке

for (ulIndex = 0, dispatch = DriverObject-> MajorFunction;

ulIndex <= IRP_MJ_MAXIMUM_FUNCTION;

ulIndex++, dispatch++)

{

*dispatch = FilterPass;

}

DriverObject-> MajorFunction[IRP_MJ_PNP] = FilterDispatchPnp;

DriverObject-> MajorFunction[IRP_MJ_POWER] = FilterDispatchPower;

DriverObject-> DriverExtension->AddDevice = FilterAddDevice;

DriverObject-> DriverUnload = FilterUnload;

DriverObject-> MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] =

(RtlIsNtDdiVersionAvailable (NTDDI_VISTA) ||

(RtlIsNtDdiVersionAvailable (NTDDI_WINXP) & &

RtlCheckRegistryKey (RTL_REGISTRY_SERVICES, USE_BULK)≠STATUS_SUCCESS))?

FilterDispatchIoVista: FilterDispatchIoXp;

return status;

}

/*++ Создание объекта устройства и регистрация символьных имён должно выполняться внутри процедуры

--*/

NTSTATUS

FilterAddDevice (

__in PDRIVER_OBJECT DriverObject,

__in PDEVICE_OBJECT PhysicalDeviceObject

)

{

NTSTATUS status = STATUS_SUCCESS;

PDEVICE_OBJECT deviceObject = NULL;

PDEVICE_EXTENSION deviceExtension;

ULONG deviceType = FILE_DEVICE_SERIAL_PORT;

PAGED_CODE ();

//if (!IoIsWdmVersionAvailable (1, 0×20)) {

if (!RtlIsNtDdiVersionAvailable (NTDDI_WIN2K)) {

// ОС Win2K делает проверку: если фильтр присоединен к устройству не специализирующемуся на этом DeviceType оно будет подключено. Такая проверка происходит, когда в файловой системе убирается стек устройств, объект устройства которого не имеет VPB. Для обхода мы будем использовать DeviceType объекта на вершине стека и указывать его в IoCreateDevice.

deviceObject = IoGetAttachedDeviceReference (PhysicalDeviceObject);

deviceType = deviceObject-> DeviceType;

ObDereferenceObject (deviceObject);

}

//Создание буферов для имён

RtlInitUnicodeString (& uniNameString, NameBuffer);

// Создаем объект устройства фильтра

status = IoCreateDevice (DriverObject,

sizeof (DEVICE_EXTENSION),

& uniNameString,

deviceType,

FILE_DEVICE_SECURE_OPEN,

FALSE,

& deviceObject);

if (!NT_SUCCESS (status)) {

// Отрицательный возврат здесь предотвращяет весть стак от функционирования, но скорее всего остальная часть стека не будет иметь возможности создать иной объект устройства, нас это устраивает.

return status;

}

// Инициализация блока данных объекта устройства

deviceExtension = (PDEVICE_EXTENSION) deviceObject-> DeviceExtension;

deviceExtension-> Type = DEVICE_TYPE_FIDO;

deviceExtension-> NextLowerDriver = IoAttachDeviceToDeviceStack (

deviceObject,

PhysicalDeviceObject);

// Индикация неудачного подключениядля PnP

if (NULL == deviceExtension-> NextLowerDriver) {

IoDeleteDevice (deviceObject);

return STATUS_UNSUCCESSFUL;

}

//Драйвер будет использовать прямой, буферный страничный I/O для запросов чтения и записи

deviceObject-> Flags |= deviceExtension-> NextLowerDriver->Flags &

(DO_BUFFERED_IO | DO_DIRECT_IO |

DO_POWER_PAGABLE);

// Присваиваем текущему объекту тип и характеристики

// следующего нижестояшегодрайвера

deviceObject-> DeviceType = deviceExtension-> NextLowerDriver->DeviceType;

deviceObject-> Characteristics = deviceExtension-> NextLowerDriver->Characteristics;

deviceExtension-> Self = deviceObject;

/*++ Разрешим использование запрета на удаление для того IRP пакетов чтобы они прошли по стеку. Такой запрет защищает нас от различных состояний, когда драйвер может быть выгружен, пока мы

еще иполняем коды отправки или завершения. --*/

// Максимум времени на запрет, в минутах

IoInitializeRemoveLock (& deviceExtension->RemoveLock,

POOL_TAG, 1, 100);

// Установка начального состояния Объекта фильтр-драйвера

INITIALIZE_PNP_STATE (deviceExtension);

deviceObject-> Flags &= ~DO_DEVICE_INITIALIZING;

return STATUS_SUCCESS;

}

/*++

Функция, осуществляющая перевод IRP пакетов,

которые драйвер не поддерживает, вниз по стеку драйверов.

Все не PnP IRP пакеты будут перенаправлены.

Использует указатель на объект устройства,

указатель на запрос пакета I/O.

--*/

NTSTATUS

FilterPass (

__in PDEVICE_OBJECT DeviceObject,

__in PIRP Irp

)

{

PDEVICE_EXTENSION deviceExtension;

NTSTATUS status;

deviceExtension = (PDEVICE_EXTENSION) DeviceObject-> DeviceExtension;

// Иницализация блокировки удаления, выполнение запроса I/O

status = IoAcquireRemoveLock (& deviceExtension->RemoveLock, Irp);

if (!NT_SUCCESS (status)) {

Irp-> IoStatus. Status = status;

IoCompleteRequest (Irp, IO_NO_INCREMENT);

return status;

}

//Собственно передача пакета нижестоящему драйверу

IoSkipCurrentIrpStackLocation (Irp);

status = IoCallDriver (deviceExtension-> NextLowerDriver, Irp);

IoReleaseRemoveLock (& deviceExtension->RemoveLock, Irp);

return status;

}

NTSTATUS

FilterDispatchPnp (

__in PDEVICE_OBJECT DeviceObject,

__in PIRP Irp

)

/*++

Пакеты для Pnp-менеджера, сообщает драйверу в каком состоянии находится устройство --*/

{

PDEVICE_EXTENSION deviceExtension;

PIO_STACK_LOCATION irpStack;

NTSTATUS status;

KEVENT event;

PAGED_CODE ();

deviceExtension = (PDEVICE_EXTENSION) DeviceObject-> DeviceExtension;

irpStack = IoGetCurrentIrpStackLocation (Irp);

status = IoAcquireRemoveLock (& deviceExtension->RemoveLock, Irp);

// Проверка подключения устройства

if (!NT_SUCCESS (status)) {

Irp-> IoStatus. Status = status;

IoCompleteRequest (Irp, IO_NO_INCREMENT);

return status;

}

switch (irpStack-> MinorFunction) {

case IRP_MN_START_DEVICE:

// Запуск устройства, нельзя посылать любые pnp IRP пакеты пока стартовое управление устройством не будет передано ниже по стеку драйверов

KeInitializeEvent (& event, NotificationEvent, FALSE);

IoCopyCurrentIrpStackLocationToNext (Irp);

IoSetCompletionRoutine (Irp,

(PIO_COMPLETION_ROUTINE) FilterStartCompletionRoutine,

& event,

TRUE,

TRUE,

TRUE);

status = IoCallDriver (deviceExtension-> NextLowerDriver, Irp);

// Ждем, пока нижний драйвер обработает пакет IRP. Важно что, когда выделяется паять бля события в стеке мы должны запустить ожидание в режиме ядра, чтобы предотвратить выход стека из станицы.

if (status == STATUS_PENDING)

{

KeWaitForSingleObject (& event, Executive, KernelMode, FALSE, NULL);

status = Irp-> IoStatus. Status;

ПоказатьСвернуть
Заполнить форму текущей работой