Рис.2.7. Место драйвера-фильтра в стеке клавиатуры
2.8.2 Получение доступа к аудиоустройству
Для того чтобы воспроизводить звук, используя Kernel Streaming, необходимо:
Перебрать все аудио-фильтры категории KSCATEGORY_AUDIO.
Для фильтра перебрать все входные пины с требуемым форматом данных.
Выбрать подходящий пин и инициализировать объект пина.
Установить состояние пина в KSSTATE_RUN, отправив IRP-пакет.
Отправить пину музыкальные команды IRP-пакетом.
Интерфейс для работы с функциями Kernel Streaming в режиме ядра описан не полностью. Разработчики MS Windows рекомендуют воспроизводить звук только из режима пользователя, используя API Windows Multimedia, библиотеки DirectMusic или DirectSound.
Существует описание интерфейса для работы с фильтрами и пинами в режиме пользователя. Данная функциональность предоставляется библиотекой DirectKS от фирмы Microsoft. Исходные тексты этой библиотеки открыты. В исходном варианте этой библиотеки был реализован доступ к устройствам, воспроизводящим WAVE-данные. Эта библиотека используется в данном проекте, и в неё внесены изменения. Реализован доступ к устройствам, воспроизводящим MIDI-ноты.
В данном проекте поиск нужного пина и инициализация его объекта производится в режиме пользователя с использованием библиотеки DirectKS. Далее объект пина передаётся в драйвер режима ядра. Драйвер отправляет музыкальные команды аудиоустройству, отправляя IRP-пакеты пину.
2.8.3 Взаимодействие компонент программного обеспечения
В рамках данного курсового проекта реализован драйвер-фильтр и программа, которая управляет работой драйвера. Используется открытая библиотека DirectKS. На рис. 2.8 представлена схема взаимодействия компонент проекта, а также процессов и драйверов операционной системы, которые участвуют в работе этих компонент.
Рис.2.8. Схема взаимодействия компонент разрабатываемого проекта
2.9 Алгоритм работы драйвера-фильтра
Поскольку разрабатываемый драйвер-фильтр является WDM-драйвером, то должен иметь следующие точки входа: DriverEntry, DriverUnload, AddDevice, функции для обработки пакетов IRP. Функции для обработки прерываний в данной работе не регистрируются, поскольку драйвер не работает с прерываниями.
2.9.1 Функции загрузки/выгрузки драйвера
DiverEntry
Заполнение массива MajorFunctions.
Регистрируется процедура обработки пакета на чтение, процедура обработки IOCTL-запросов, процедуры обработки запросов от менеджера PnP и менеджера питания. Остальные элементы массива заполняются адресом функции __MyFilterDispatchGeneral, которая пропускает пакеты ниже по стеку.
Регистрация процедуры AddDevice. В данной работе она называется MyFilterAddDevice.
Регистрация процедуры DriverUnload, называющейся MyFilterUnload.
AddDevice
В данной работе функция __MyFilterAddDevice создает одно функциональное устройство с именем \Device\kbd_filter. Происходит резервирование места для хранения адреса устройства, расположенного ниже в стеке драйверов. Это сделано для того, чтобы при разрушении стека драйверов передать запрос PnP на демонтаж нижестоящему драйверу. Созданное устройство подключается к стеку драйверов клавиатуры. Это делается с помощью функции IoAttachDeviceToDeviceStack. Это стандартная функция Windows, она принимает PDO и указатель на структуру подключаемого FDO. FDO занимает место в стеке драйверов сразу после объекта, находящегося в вершине стека. Теперь подключаемый FDO становится вершиной стека. Очередность загрузки драйверов описана в реестре Windows.
Для того чтобы пользовательское приложение смогло обратиться к драйверу для FDO должно быть зарегистрировано DOS имя. Используя это имя, приложение сможет послать драйверу IOCTL-запрос. Для регистрации такого имени создается строка-юникод со значением \DosDevices\kbd_filter и применяется функция IoCreateSymbolicLink. Ее параметрами является только что созданная строка и имя FDO, которое обслуживает наш драйвер. Теперь \DosDevices\kbd_filter - это DOS имя созданного FDO устройства.
В этой функции происходит также инициализация других объектов, используемых в работе драйвера-фильтра:
PinInit() - инициализация модуля, который работает с пином.
KeyMidiInit() - инициализация таблицы, в которой хранится информация о музыкальных параметрах клавиш.
Создание системного потока PlayThread, который отправляет музыкальные команды аудиоустройству.
Создание двух очередей, используемых в работе двух потоков.
Создание объектов синхронизации потоков .
DriverUnload
Поскольку данный фильтр является PnP драйвером, то на процедуру DriverUnload ничего не возложено.
2.9.2 Функции обработки пакетов IRP
Разрабатываемый драйвер-фильтр осуществляет обработку следующих пакетов IRP:
IRP_MJ_DEVICE_CONTROL
IRP_MJ_READ
IRP_MJ_PNP
IRP_MJ_POWER
Остальные IRP пакеты пропускаются ниже по стеку драйверов.
Функция обработки пакетов IRP_MJ_DEVICE_CONTROL
В данной работе пользовательское приложение должно иметь возможность посылать IOCTL-запросы драйверу. Приложение должно иметь возможность отправить драйверу объект открытого музыкального пина и музыкальные параметры клавиши.
Для этого в теле драйвера определены две 32-битные константы:
#define IOCTL_SHARE_PIN \
CTL_CODE(FILE_DEVICE_KEYBOARD, 0x810, METHOD_BUFFERED, FILE_ANY_ACCESS)
По этому коду в драйвер передаётся 4 байта, которые являются HANDLE объекта пина, открытого в пользовательской программе.
#define IOCTL_MIDI_NOTE \
CTL_CODE(FILE_DEVICE_KEYBOARD, 0x811, METHOD_BUFFERED, FILE_ANY_ACCESS)
По этому коду в драйвер передаётся 7 байт, которые можно описать структурой:
typedef struct _KEY_MIDI_INFO
{UCHAR ScanCode; // Скан-код клавиши, генерируемый клавиатурой
UCHAR Flag; // Флаг клавиши, генерируемый клавиатурой
UCHAR Position; // Позиция на клавиатуре (всего 104 клавиши)
UCHAR Channel; // Музыкальный канал
UCHAR Instrument; // Музыкальный инструмент
UCHAR Note; // Музыкальная нота
UCHAR Used; // Для клавиши используется нота или нет
} KEY_MIDI_INFO, * PKEY_MIDI_INFO;
Это коды IOCTL-запросов, которые не используются драйверами стека клавиатуры. Поэтому в данном проекте они могут быть использованы безо всяких опасений.
Применяется способ передачи данных METHOD_BUFFERED. Т.к. передаётся буфер в 4 или 7 байт, то его размер не повредит системному пулу, и копироваться пользовательский буфер в системный будет очень быстро. Нет необходимости применять более сложные методы METHOD_IN_DIRECT или METHOD_NEITHER, которые используются при передаче больших объемов данных.
Функция обработки пакетов IRP_MJ_READ
Данная функция осуществляет обработку пакетов на чтение. IRP-пакет сначала попадает в разрабатываемый драйвер. Вызовется зарегистрированная в DriverEntry функция __MyFilterDispatchRead. К моменту вызова __MyFilterDispatchRead, буфер не содержит кодов считанных клавиш. Для того чтобы получить доступ к ним __MyFilterDispatchRead должна установить CallBack процедуру __MyFilterReadComplete. Она получит управление, когда буфер IRP-пакета будет содержать информацию о нажатых клавишах. Пакет будет подниматься вверх по стеку драйверов и вызывать CallBack функции на каждом уровне стека. CallBack процедура устанавливается с помощью функции IoSetCompletionRoutine.
MyFilterReadComplete получает буфер нажатых клавиш, как массив структур KEYBOARD_INPUT_DATA, функция выполняется на IRQL <= DISPATCH_LEVEL.
Далее в MyFilterDispatchRead происходит копирование текущей ячейки IRP-пакета в следующую ячейку. Таким образом происходит передача неизмененных параметров в Kbdclass.
Функция обработки пакетов IRP_MJ_PNP
Драйвер-фильтр должен обрабатывать только запросы IRP_MN_REMOVE_DEVICE и IRP_MN_SURPRISE_REMOVAL. При этом функция посылает данный пакет менеджера PnP нижестоящему в стеке устройству. В обработчиках этих запросов происходит освобождение памяти, которая выделялась для модуля работы с пином, для таблицы музыкальных нот, происходит завершение работы музыкального потока, освобождение очередей и объектов синхронизации, используемых в драйвере.
В обработчике IRP_MN_REMOVE_DEVICE дополнительно происходит:
отключение устройства от стека драйверов вызовом функции IoDetachDevice,
удаление устройства FDO вызовом функции IoDeleteDevice,
удаление символьной ссылки вызовом IoDeleteSymbolicLink.
Остальные пакеты пропускаются ниже по стеку.
Функция обработки пакетов IRP_MJ_POWER
Т.к. разрабатываемый драйвер является фильтром, задача которого - получить информацию о нажатых клавишах, то в нём не производится никаких действий, связанных с изменением питания. Поэтому эти IRP-пакеты пропускаются ниже по стеку.
Обработка остальных пактов IRP
Остальные IRP-пакеты, которые не обрабатываются в данном фильтре, пропускаются ниже по стеку. Функции данного драйвера-фильтра не в праве самостоятельно обрабатывать эти запросы, так как это могут запросы, адресованные нижестоящим драйверам. Примером одного из таких запросов является IOCTL-запрос, адресованный драйверу i8042prt и предназначенный для перепрограммирования котроллера клавиатуры и для зажжения лампочек на клавиатуре.
Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9