§ 24. Драйвер с обработкой прерывания от LPT порта (Часть 1)
|
Дмитрий Иванов, Январь 2007
|
Статья впервые опубликованна 16 Мая 2014
|
|
Драйвер, который мог бы обрабатывать прерывания порта LPT - вот что в принципе заставило меня садится за программирование драйверов. Я думаю, что этот вопрос по прежнему волнует много людей. Поэтому, в этой статье мы сделаем наш собственный драйвер, который может перехватывать прерывание и сообщать об этом пользовательскому приложению.
|
Скорее, это статья расскажет о том что надо сделать чтобы это все заработало, но хороших теоритических объяснений дать не сможет. Но я доволен и таким результатом, поскольку для того чтобы дойти до этого момента, я затратил довольно много времени, а над проблемой сигнализации со стороны драйвера прользовательскому приложению о прерывании бился еще дольше, но в конце концов решил и ее.
Ну ладно, давайте все же возьмемся за этот чудо драйвер. В файлах к этой статье Вы найдете проект для него. Рассмотрим подробнее код, расположенный в файле int2.c. Начнем с функции DriverEntry() - точки входа в драйвер. Можно обратить внимание, что в этом примере в качестве символического имени задано имя drvNAME. Именно по нему пользовательское приложение будет работать с этим драйвером. А вот далее идет вызов неизвестной нам ранее функции ядра HalGetInterruptVector(), необходимого для стыковки аппаратных систем прерываний с Windows системой. Далее, с помощью вызова функции IoInitializeDpcRequest() мы указали адрес на нашу собственную функцию, на которую в итоге перейдет управление после предварительной обработки и анализа сигнала о прерывании (это функция InterruptDpcRoutine). С помощью вызова IoConnectInterrupt() мы регестрируем обработчик прерывания, на который управление перейдет непосредственно после получения сигнала о прерывании.
#include "ntddk.h"
#define PortAddress 0x378
#define CONTROL PortAddress+2
#define outportb(ADDR,BYTE) outp(ADDR,BYTE)
#define inportb(ADDR) inp(ADDR)
typedef struct _LOCAL_DEVICE_INFO {
PKINTERRUPT InterruptObject;
ULONG Level;
ULONG Vector;
KAFFINITY Affinity;
PKEVENT pEventObject;
} LOCAL_DEVICE_INFO, *PLOCAL_DEVICE_INFO;
NTSTATUS InterruptCreateDispatch(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
BOOLEAN InterruptIsr(IN PKINTERRUPT Interrupt,
IN OUT PVOID Context)
{
PDEVICE_OBJECT DeviceObject = Context;
IoRequestDpc(DeviceObject,
DeviceObject->CurrentIrp,
NULL);
return TRUE;
}
VOID InterruptDpcRoutine(IN PKDPC Dpc,
PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)
{
PLOCAL_DEVICE_INFO DeviceExtension;
PIRP pIrp;
pIrp = DeviceObject->CurrentIrp;
DeviceExtension = DeviceObject->DeviceExtension;
outportb(PortAddress, 0);
outportb(PortAddress, 60);
KeSetEvent(DeviceExtension->pEventObject, 0, FALSE);
KeClearEvent(DeviceExtension->pEventObject);
return;
}
VOID InterruptUnload(IN PDRIVER_OBJECT DriverObject)
{
WCHAR DOSNameBuffer[] = L"\\??\\drvNAME";
UNICODE_STRING uniDOSString;
PLOCAL_DEVICE_INFO extension =
DriverObject->DeviceObject->DeviceExtension;
/* Disable Parallel Port IRQ's */
outportb(CONTROL, inportb(CONTROL) & 0xEF);
/* Disconnect Interrupt */
IoDisconnectInterrupt(extension->InterruptObject);
/* Delete Symbolic Link */
RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);
IoDeleteSymbolicLink (&uniDOSString);
/* Delete Device */
IoDeleteDevice(DriverObject->DeviceObject);
}
NTSTATUS DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
PDEVICE_OBJECT DeviceObject;
NTSTATUS status;
PLOCAL_DEVICE_INFO DeviceExtension;
ULONG MappedVector;
KIRQL Irql;
WCHAR NameBuffer[] = L"\\Device\\Interrupt";
WCHAR DOSNameBuffer[] = L"\\??\\drvNAME";
UNICODE_STRING uniNameString, uniDOSString;
UNICODE_STRING EventName;
HANDLE EventHandle;
RtlInitUnicodeString(&uniNameString, NameBuffer);
RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);
status = IoCreateDevice(DriverObject,// DriverObject
sizeof(LOCAL_DEVICE_INFO), // DeviceExtensionSize
&uniNameString, // DeviceName
FILE_DEVICE_UNKNOWN, // DeviceType
0, // DeviceCharacteristics
TRUE, // Exclusive
&DeviceObject); // *DeviceObject
if(!NT_SUCCESS(status)) return status;
DeviceExtension = DeviceObject->DeviceExtension;
status = IoCreateSymbolicLink(&uniDOSString, &uniNameString);
if (!NT_SUCCESS(status)) return status;
DeviceExtension->Level = 7;
DeviceExtension->Vector = DeviceExtension->Level;
MappedVector = HalGetInterruptVector(Isa,
0,
DeviceExtension->Level,
DeviceExtension->Vector,
&Irql,
&DeviceExtension->Affinity);
IoInitializeDpcRequest(DeviceObject,
InterruptDpcRoutine);
status = IoConnectInterrupt(
&DeviceExtension->InterruptObject, //InterruptObject
InterruptIsr, //ServiceRoutine
DeviceObject, // ServiceContext
NULL, // SpinLock
MappedVector, // Vector
Irql, // Irql
Irql, // SynchronizeIrql
Latched, // InterruptMode
FALSE, // ShareVector
DeviceExtension->Affinity, // ProcessorEnableMask
FALSE); // FloatingSave
/* Enable Parallel Port IRQ's */
outportb(CONTROL, inportb(CONTROL) | 0x10);
DriverObject->MajorFunction[IRP_MJ_CREATE] =
InterruptCreateDispatch;
DriverObject->DriverUnload = InterruptUnload;
RtlInitUnicodeString(&EventName, L"\\BaseNamedObjects\\SignalEvent");
DeviceExtension->pEventObject =
IoCreateNotificationEvent(&EventName, &EventHandle);
KeClearEvent(DeviceExtension->pEventObject);
return STATUS_SUCCESS;
}
В результате, после долгих путешествий сигнала о прерывании, управление перейдет на функцию InterruptDpcRoutine(), в которой мы уже будем вольны делать что за хотим. А чего мы хотим? Наверное, чтобы наше пользовательское приложение, которое запустило этот драйвер, узнало что произошло прерывание. В данном драйвере это реализуется на использовании глобальных системных событий, область видимости которых распространяется как на драйвер так и на пользовательское приложение. В простейшем случае это просто флажок, который может быть или 0 или 1. В пользовательском приложении мы будем отслеживать это событие, а в драйвере при возникновении прерывания этот флажок будет устанавливаться в 1. Для этого в функции DriverEntry с помощью вызова IoCreateNotificationEvent() мы создаем событие с символически именем SignalEvent. Именно это имя необходимо будет использовать в пользовательском приложении при отлавливании этого события (см. следующую сатью). Далее, с помощью вызова функции KeClearEvent() мы сбрасываем это событие в ноль.
Далее, обратимся к коду функции конечной обработки прерывания InterruptDpcRoutine(). Что там происходит? С помощью функции outportb() ( по сути это просто обычная Си функция outp()) в регистр DATA LPT порта я записываю число 60 (чтобы можно было визуально проследить на светодиодах факт обработки прерывания). Потом устанавливаем событие в взведенное состояние (флажок поднят) с помощью функции KeSetEvent(). В этот момент пользовательское приложение, которое следит за этим событием почти моментально получит сигнал о том что событие с именем SignalEvent произошло. Далее событие опять сбрасывается в 0, чтобы быть готовым к обработке следующего прерывания.
Должен предупредить, что знатаком в области программирования драйверов назвать себя не могу и объяснил что в нем происходит как смог. Но следует заметить, что не смотря на это, все коды рабочие и вполне справляются со своими функциями. Итак, теперь можно откомпилировать драйвер. В файлах к статье можно найти уже готовый. В следующей стаье заимемся программированием пользовательского приложения, которое будет запускать этот драйвер и реагировать на сигнал о прерывании.
© Дмитрий Иванов
Январь 2007
http://www.kernelchip.ru