Смекни!
smekni.com

Драйвер клавиатуры реализующий функции музыкального синтезатора на клавиатуре для Windows NT 5 (стр. 4 из 8)

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 и предназначенный для перепрограммирования котроллера клавиатуры и для зажжения лампочек на клавиатуре.

В данной работе за пропускание пакетов вниз отвечает процедура __MyFilterDispatchGeneral. Она передает IRP пакет нижестоящему драйверу с помощью функции IoCallDriver. При этом нижестоящий драйвер должен считывать текущую ячейку IRP пакета. Это достигается за счет использования функции IoSkipCurrentIrpStackLocation.

2.9.3 Функции работы с аудио-устройством

В модуле midi_pin реализованы все функции, которые обеспечивают работу с музыкальным пином на уровне ядра. В работе с музыкальным пином используются запросы на установку состояния пина и отправление пакетов с музыкальными данными.

NTSTATUS PinInit()

В этой функции происходит инициализация полей заголовка IRP-пакета и MIDI-данных, которые используются во время отправления музыкальных команд в пин. Далее в функциях PinMidiNoteOn и PinMidiNoteOff используется инициализированный заголовок.

Инициализация заголовка IRP-пакета происходит следующим образом:

typedef __declspec(align(16)) struct _MIDI_DATA

{KSMUSICFORMAT InstrumentFormat; // include <ksmedia.h>

union

{UCHAR InstrumentByte[4];

UCHAR NoteOffByte[4];};

KSMUSICFORMAT NoteFormat;

UCHAR NoteOnByte[4];

} MIDI_DATA, * PMIDI_DATA;

MIDI_DATA PinMidiData;

KSSTREAM_HEADER PinWriteHeader; // include <ks.h>

RtlZeroMemory(&PinWriteHeader, sizeof(PinWriteHeader));

// 12 байт

PinMidiData.InstrumentFormat.TimeDeltaMs = 0;

PinMidiData.InstrumentFormat.ByteCount = 3;

PinMidiData.InstrumentByte[0] = 0x00;

PinMidiData.InstrumentByte[1] = 0x00;

PinMidiData.InstrumentByte[2] = 0x00;

PinMidiData.InstrumentByte[3] = 0x00;

// ещё 12 байт

PinMidiData.NoteFormat.TimeDeltaMs = 0;

PinMidiData.NoteFormat.ByteCount = 3;

PinMidiData.NoteOnByte[0] = 0x00;

PinMidiData.NoteOnByte[1] = 0x00;

PinMidiData.NoteOnByte[2] = 0x00;

PinMidiData.NoteOnByte[3] = 0x00;

PinWriteHeader.Size = sizeof(PinWriteHeader);

PinWriteHeader.TypeSpecificFlags = 0;

PinWriteHeader.PresentationTime.Time = 0;

PinWriteHeader.PresentationTime.Numerator = 1;

PinWriteHeader.PresentationTime.Denominator = 1;

PinWriteHeader.Duration = 0;

PinWriteHeader.FrameExtent = 24; // всего 24 байта

PinWriteHeader.DataUsed = 24; // всего 24 байта

PinWriteHeader.Data = &PinMidiData;

NTSTATUS PinOpenStream(IN HANDLE UserPin)

Когда через запрос IOCTL_SHARE_PIN драйвер получает объект открытого пина, то необходимо вызвать эту функцию. В ней происходит вызов функции ObReferenceObjectByHandle для того, чтобы получить указатель на объект пина в режиме ядра и увеличить число ссылок на объект. Это делается для того, чтобы объект пина не был удалён из таблицы объектов ОС после заверешения работы пользовательского приложения, в котором был создан объект пина. Также здесь происходит установка флага, что пин открыт для драйвера.

UserPin – HANDLE того пина, который содержится в буфере IRP-пакета.

NTSTATUS PinIsOpenedStream()

Возвращает STATUS_SUCCESS если пин открыт.

NTSTATUS PinFree()

Здесь происходит вызов функции ObDereferenceObject, которая уменьшает число ссылок на объект пина.

NTSTATUS PinSetState(IN KSSTATE State)

Устанавливает состояние пина в State (KSSTATE_RUN, KSSTATE_PAUSE или KSSTATE_STOP). Перед тем, как воспроизводить ноты, необходимо установить состояние пина в KSSTATE_RUN. Функция работает только при IRQL = PASSIVE_LEVEL.

NTSTATUS PinWriteData(IN KSSTREAM_HEADER * Pheader)

Отправляет заголовок в открытый пин посредством IOCTL_KS_WRITE_STREAM.

Функция работает только при IRQL = PASSIVE_LEVEL.

NTSTATUS PinMidiNoteOn(IN UCHAR Channel,

IN UCHAR Instrument, IN UCHAR Note)

Если пин открыт, то отправляет команду на воспроизведение ноты Note с использованием инструмента Instrument в канале Channel.

Модификация инициализированного заголовка:

PinMidiData.InstrumentByte[0] = 0xC0 | Channel;

PinMidiData.InstrumentByte[1] = Instrument;

PinMidiData.NoteOnByte[0] = 0x90 | Channel;

PinMidiData.NoteOnByte[1] = Note;

PinMidiData.NoteOnByte[2] = 0x7F;

// 24 байта отправляем, т.к. в одном

PinWriteHeader.FrameExtent = 24; // пакете 2 команды: устанавливаем

PinWriteHeader.DataUsed = 24; // инструмент и отправляем ноту

PinWriteData(&PinWriteHeader);

Функция работает только при IRQL = PASSIVE_LEVEL.

NTSTATUS PinMidiNoteOff(IN UCHAR Channel, IN UCHAR Note)

Выключает ноту Note в канале Channel.

Модификация инициализированного заголовка:

PinMidiData.NoteOffByte[0] = 0x80 | Channel;

PinMidiData.NoteOffByte[1] = Note;

PinWriteHeader.FrameExtent = 12; // 12 байт шлём, т.к. в одном пакете