Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MS Windows. Элементы архитектуры и системное программирование..pdf
Скачиваний:
273
Добавлен:
01.05.2014
Размер:
1.98 Mб
Скачать

Менеджер PnP передаёт пакеты запросов PnP объекту устройства, который расположен на вершине стека объектов устройств. Однако первым пакет запроса должен обработать объект, расположенный на дне стека. Объект устройства, если он не последний в стеке, перед обработкой пакета должен передать его нижерасположенному

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

PEDVICE_EXTENSION dext = DeviceObject->DeviceExtension;

IoCopyCurrentIrpStackLocationToNext(Irp);

IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE)OnRequestComplete, (PVOID)hEvent,

TRUE, TRUE, TRUE);

NTSTATUS status = IoCallDriver(dext->NextDevice, Irp); KEVENT hEvent;

KeInitializeEvent(&hEvent, NotoficationEvent, FALSE); if (status == STATUS_PENDING) {

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

status = Irp->IoStatus.Status;

}

if (status == STATUS_SUCCESS) {

IO_STACK_LOCATION ioStack = GetCurrentIrpStackLocation(Irp);

//обработка Irp в нижерасположенных объектах успешно завершена. //продолжить обработку.

}

Функция IoCallDriver вызывает обработчик пакета запроса объекта расположенного ниже в стеке. Этот обработчик может сразу вернуть результат или начать асинхронную операцию. В последнем случае функция возвращает STATUS_PENDING и вызывающая программа (обработчик функционального объекта) переходит в режим ожидания окончания операции (KeWaitForSingleObject). Перед вызовом функции IoCallDriver создаётся объект синхронизации (KeInitializeEvent) и устанавливается процедура завершения операции OnRequestComplete. Эта процедура будет вызвана автоматически, когда нижерасположенный объект устройства завершит асинхронную операцию и вызовет функцию IoCompleteRequest. OnRequestComplete просто устанавливает объект синхронизации hEvent в состояние Signalled.

NTSTATUS OnRequestComplete(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PKEVENT hEvent)

{

KeSetEvent(hEvent, 0, FALSE);

return STATUS_MORE_PROCESSING_REQUIRED;

}

Как только hEvent будет установлен в состояние signalled, вызывающая программа продолжит работу. Она может получить результат обработки Irp нижерасположенными объектами из блока статуса Irp->IoStatus.Status и начать собственную обработку Irp.

Управление аппаратными ресурсами. Обработка IRP_MN_START_DЕVICE

В пакете запроса IRP_MN_START_DEVICE настройки аппаратуры передаются драйверу устройства в виде списка типа CM_RESOURCE_LIST. Указатель на список содержится в блоке параметров структуры IO_STACK_LOCATION

List = StackLocation->Parameters.StartDevice.AllocatedResourcesTranslated. Этот список содержит транслированные параметры настроек. Настройки аппаратуры могут зависеть от особенностей подключения аппаратных устройств к системе. Например, диапазоны адресов устройства на шине к которой подключается устройство, могут отличаться от диапазона адресов выделенных устройству на шине адреса процессора. Программный доступ к устройству осуществляется через системную шину адреса, поэтому специфические шинные адреса должны быть перетранслированы в системные. В

161

пакете запроса передаются оба списка. Указатель на список исходных настроек аппаратуры содержится в поле блока параметров

StackLocation->Parameters.StartDevice.AllocatedResources.

Структура CM_RESOURCE_LIST описывает заголовок массива переменной длинны который содержит полные (full) описатели ресурсов выделенных устройству. В заголовке указывается количество полных описателей - Count.

typedef struct _CM_RESOURCE_LIST { ULONG Count;

CM_FULL_RESOURCE_DESCRIPTOR List[1]; } CM_RESOURCE_LIST, *PCM_RESOURCE_LIST;

В массиве располагаются Count полных описателей типа

CM_FULL_RESOURCE_DESCRIPTOR;

typedef struct _CM_FULL_RESOURCE_DESCRIPTOR { INTERFACE_TYPE InterfaceType;

ULONG BusNumber;

CM_PARTIAL_RESOURCE_LIST PartialResourceList;

} CM_FULL_RESOURCE_DESCRIPTOR, *PCM_FULL_RESOURCE_DESCRIPTOR;

В полных описателях содержится массив переменной длинны в котором располагаются частичные (partial) описатели ресурсов. Заголовок этого массива описывается следующей структурой:

typedef struct _CM_PARTIAL_RESOURCE_LIST { USHORT Version;

USHORT Revision; ULONG Count;

CM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptors[1]; } CM_PARTIAL_RESOURCE_LIST, *PCM_PARTIAL_RESOURCE_LIST;

В поле Count структуры указано количество частичных описателей ресурсов. Частичный описатель ресурса описывается структурой CM_PARTIAL_RESOURCE_DESCRIPTOR. Эта структура имеет стандартный заголовок, в поле Type которого указывается тип ресурса. Интерпретация поля Flags в заголовке зависит от типа ресурса. Остальная часть структуры описывается объединением (union) u. Объединение включает несколько структур, применение которых так же зависит от типа ресурса.

typedef struct _CM_PARTIAL_RESOURCE_DESCRIPTOR { UCHAR Type;

UCHAR ShareDisposition; USHORT Flags;

union {

...

}u;

}CM_PARTIAL_RESOURCE_DESCRIPTOR, *PCM_PARTIAL_RESOURCE_DESCRIPTOR;

Ниже рассматриваются типы ресурсов и описывающие их структуры, входящие в объединение u:

Диапазон адресов памяти (Type = CmResourceTypeMemory):

struct {

//8-байтовый базовый физический адрес диапазона.

PHYSICAL_ADDRESS Start;

ULONG Length; //размер диапазона

} Memory;

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

MmMapIoSpace.

162

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

PHYSICAL_ADDRESS PhysAddr = ...u.Memory.Start;

ULONG Length = ...u.Memory.Length;

PVOID VirtualAddr = MmMapIoSpace(PhysAddr, Length, MmNonCached);

При выгрузке драйвера выделенное виртуальное адресное пространство следует освободить:

MmUnmapIoSpace(VirtualAddr, Length);

Диапазон портов ввода-вывода(Type = CmResourceTypePort):

struct {

PHYSICAL_ADDRESS Start; // 8-ми байтовый базовый адрес группы портов ULONG Length; //количество портов

} Port;

Если поле Flags заголовка описателя ресурса этого типа содержит значение CM_RESOURCE_PORT_MEMORY указанный диапазон портов отображается в память. В этом случае для обращение к портам следует создать диапазон виртуальных адресов, так же как это делается для памяти.

Линия аппаратного прерывания (Type = СmResourceTypeInterrupt):

struct {

ULONG Level;

ULONG Vector; ULONG Affinity;

} Interrupt;

Параметр Level задаёт уровень приоритета, на котором будет исполнятся код обработчика прерываний. Подробнее эта тема обсуждается в параграфе «Уровни приоритетов». В поле Vector передаётся номер вектора прерывания. Значение этого параметра составляется из номера IRQ и кода шины, к которой подключено устройство. Поле Affinity содержит маску, определяющую набор процессоров, которые имеют право обрабатывать прерывание. Для каждого процессора в маске выделяется один бит, начиная с младшего. Если бит установлен, обработка прерываний разрешена. Для однопроцессорной системы значение этого поля равно 1. Дополнительно для этого типа ресурса в поле Flags указывается тип прерывания

CM_RESOURCE_INTERRUPT_LEVEL_SENSITIVE - по уровню, или CM_RESOURCE_INTERRUPT_LATCHED по фронту.

Драйвер устанавливает обработчик прерывания с помощью функции IoConnectInterrupt.

PKINTERRUPT InterruptObject;

PDEVICE_EXTENSION de = DeviceObject->DeviceExtension; ULONG vector = ...u.Interrupt.Vector;

ULONG level = ...u.Interrupt.Level;

KINTERRUPT_MODE mode = (...Flags == CM_RESOURCE_INTERRUPT_LATCHED) ? Latched : LevelSensitive;

KAFFINITY affinity = ...u.Interrupt.Affinity;

NTSTATUS Status = IoConnectInterrupt(&InterruptObject, (PKSERVICE_ROUTINE)IrqHandler, (PVOID)de, NULL, vector, level,

level, mode, FALSE, affinity, FALSE);

Функция инициализирует объект прерывания и возвращает указатель на него в переменную, адрес которой передаётся в первом параметре. Объект прерывания в дальнейшем используется для управления линией прерывания. Во втором параметре

163

функции передаётся указатель на процедуру обработчик прерывания. Эта процедура имеет следующий интерфейс:

BOOLEAN IrqHandler( IN PKINTERRUPT Interrupt,

IN PVOID ServiceContext );

Через параметры обработчику передаётся указатель на объект прерывания (Interrupt) и значение (ServiceContext) которое указывается разработчиком в третьем параметре функции IoConnectInterrupt. Таким способом можно, например, передать обработчику указатель на расширение устройства (PVOID)de.

Для снятия обработчика прерывания используется функция IoDisconnectInterrupt.

IoDisconnectInterrupt(InterruptObject);

Обработчик аппаратного прерывания вызывается на повышенном уровне приоритета IRQL. Некоторые функции, например IoCompleteRequest не могут быть вызваны на повышенном IRQL, т.е. непосредственно из обработчика IrqHandler. Необходимость вызова этой функции возникает, например, в случае если прерывание применяется как индикатор завершения асинхронной операции. Для решения проблемы следует использовать механизм отложенных вызовов процедур Deferred Procedure Call (DPC). Из обработчика прерывания можно заказать отложенный вызов заданной процедуры DPC. Процедура будет вызвана, после того как текущий уровень приоритета в системе снизится до значения DISPATCH_LEVEL. Это произойдёт после завершения обработчика прерывания. Таким образом, можно сказать, что обработчик перывания состоит из как бы из двух фрагментов кода. Первый выполняется на повышенном уровне IRQL - это собственно процедура обработчика прерывания. Следом за ним на уровне DISPATCH_LEVEL выполняется второй - отложенная процедура DPC. Запросы вызовов отложенных процедур помещаются в очередь в обработчике прерывания. Когда текущий IRQL снизится до уровня DISPATCH_LEVEL запросы последовательно извлекаются из очереди и выполняются. Программист может использовать стандартную очередь запросов вызовов DPC или создавать свои собственные очереди. Для инициализации стандартной очереди используется функция IoInitializeDpcRequest.

IoInitializeDpcRequest(DeviceObject, DpcRoutine);

В первом параметре функции передаётся указатель на объект устройства, а во втором адрес отложенной процедуры DPC. Функция IoInitializeDpcRequest вызывается как правило в процессе инициализации устройства вместе с установкой обработчика прерывания. Например в обработчике IRP_MN_START_DEVICE.

Ниже приводится интерфейс процедуры DPC.

void DpcRoutine(IN PKDPC Dpc,IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context);

В параметрах процедуры передаётся указатель на объект DPC, указатель на объект устройства, указатель на Irp и данные определяемые разработчиком (Context).

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

IoRequestDpc (DeviceObject, Irp, Context);

В параметрах функции указывается адрес объекта устройства, адрес Irp и определяемые пользователем данные которые будут переданы процедуре DPC через параметр Context.

Ниже приводится пример процедуры установки обработчика прерывания и процедуры DPC, а так же их фрагменты.

//Установка

...

PDEVICE_EXTENSION de = DeviceObject->DeviceExtension;

164

NTSTATUS Status = IoConnectInterrupt(&InterruptObject,

(PKSERVICE_ROUTINE)IrqHandler, (PVOID)de, ...);

...

IoInitializeDpcRequest(DeviceObject, DpcRoutine);

...

//обработчик прерывания

 

BOOLEAN IrqHandler (

 

IN PKINTERRUPT

InterruptObject,

IN PVOID

ServiceContext)

{

 

PDEVICE_EXTENSION de = (PDEVICE_EXTENSION) ServiceContext; PDEVICE_OBJECT DeviceObject = de->DeviceObject;

PIRP Irp = DeviceObject->CurrentIrp;

...

IoRequestDpc (DeviceObject, Irp, NULL); return TRUE;

}

//Отложенная процедура DPC

void DpcRoutine(IN PKDPC Dpc,IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)

{

...

IoStartNextPacket(DeviceObject, FALSE);

IoCompleteRequest(Irp, IO_NO_INCREMENT);

}

Канал или порт ПДП (Type = CmResourceTypeDma)

struct {

ULONG Channel; ULONG Port; ULONG Reserved1;

} Dma;

В структуре для системного контроллера ПДП указывается номер канала (Channel). Для контроллера подключённого к шине MCA указывается номер порта (Port).

Для инициализации канала ПДП используется функция IoGetDmaAdapter.

DEVICE_DESCRIPTON decsDma;

ULONG MaxMapReg;

PDMA_ADAPTER DmaObject1 = IoGetDmaAdapter(DeviceObject, &descDma, &MaxMapReg);

Функция возвращает указатель на структуру данных, которая в дальнейшем используется для управления каналом ПДП. В параметрах функции передаётся указатель на физический объект устройства DeviceObject, указатель на структуру описатель устройства decsDma и указатель на переменную (MaxMapReg) в которую возвращается максимальное количество так называемых регистров отображения (map registers), которые драйвер может использовать в операциях ПДП. Регистры отображения используются для отображения системного буфера ПДП в виртуальное адресное пространство.

Структура descDma инициализируется перед вызовом функции IoGetDmaAdapter. Эта структура содержит разнообразную информацию о канале ПДП: разрядность, тип шины, к которой подключён контроллер, номер канала и т.п. Ниже приводится пример иницализации структуры DEVICE_DESCRIPTON для 5-го канала системного контроллера ПДП.

#define DMA_CHANNEL 5

165

#define DMA_BUF_SIZE 1024

DEVICE_DESCRIPTION descDma;

RtlZeroMemory(&descDma1, sizeof(&descDma1));

descDma.Version

=

0;

descDma.Master

=

FALSE;

descDma.ScatterGather

 

= FALSE;

descDma.AutoInitialize

 

= FALSE;

descDma.Dma32BitAddresses

= FALSE;

descDma.DmaChannel

 

= DMA_CHANNEL;

descDma.InterfaceType

 

= Isa;

descDma.DmaWidth

=

Width16Bits;

descDma.DmaSpeed

=

Compatible;

descDma.MaximumLength

=

DMA_BUF_SIZE;

descDma.DmaPort

=

0;

Структура DMA_ADAPTER содержит поле DmaOperations. Это поле (типа DMA_OPERATIONS) указывает на таблицу адресов функций, которые используются для управления операциями ПДП. В эту таблицу, например, входят адреса функций использующихся для размещения буфера ПДП (AllocateCommonBuffer), захвата канала ПДП (AllocateAdapterChannel), чтения счетчика (ReadDmaCounter) и т.п. Для большинства функций в заголовочном файле wdm.h имеются макроопределения. Например:

#define HalReadDmaCounter(DmaAdapter) \ ((*(DmaAdapter)->DmaOperations->ReadDmaCounter)(DmaAdapter))

166