[Previous] [Next]

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

В заголовочном файле, относящемся к первому варианту драйвера, описывается структура расширения объекта устройства, которую разработчик драйвера определяет самостоятельно. В этой структуре сохранены имена устройства, символьная ссылка, внутренние рабочие буферы для данных, подлежащих выдаче в параллельный порт, и для данных, уже прошедших через параллельный порт. Данные второго буфера можно передать по запросу Win32 вызова ReadFile, при условии завершения переноса, "заказанного" предшествующим Win32 вызовом WriteFile. Размер рабочих буферов задается макроопределением MAX_BUFFER_SIZE.

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

Здесь же, в структуре расширения, сохраняется указатель на создаваемый объект прерывания pIntObj и резервируется место под DPC объект DpcForIsr_Object.

Макроопределения WriteControlRegister, WriteDataRegister и ReadStatusRegister скрывают использование HAL определений, предназначенных для чтения и записи в порт ввода вывода, в данном случае в параллельный порт 378.

//===================================================================================
// Driver.h - заголовочный файл для драйвера обслуживания
// заглушки CheckIt ( Вариант 1 )

// By SVP, 20 June 2004
//===================================================================================
#pragma once

extern "C" {
#include <NTDDK.h>
}

#define MAX_BUFFER_SIZE (32)

typedef struct _DEVICE_EXTENSION
{
	PDEVICE_OBJECT  pDevice;

	UNICODE_STRING  ustrDeviceName;	 // внутреннее имя устройства
	UNICODE_STRING  ustrSymLinkName; // внешнее имя (символьная ссылка)

	UCHAR deviceOutBuffer[MAX_BUFFER_SIZE], // для вывода в устройство
	      deviceInBuffer [MAX_BUFFER_SIZE]; // для получения из устройства

	ULONG  xferCount, // текущий передаваемый байт
	       xferRest;  // остаток непереданных байт (индикатор завершенности)
	//=============================================
	PUCHAR portBase;  // адрес порта ввода/вывода
	ULONG Irq;        // Irq в терминах шины ISA для параллельного порта
	//=============================================
	PKINTERRUPT pIntObj;	        // interrupt object
	KDPC        DpcForIsr_Object;   // DPC object

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

// Маски для выделения бит в регистре управления:
#define CR_NOT_RST 0x04	// CR.2 - 0 Reset printer
#define CR_INT_ENB 0x10	// CR.4 - 1 Interrupt enable
#define CR_DEFAULT 0xC0	// неиспользуемые биты

// Макроопределения для записи и чтения в параллельный порт
#define DATA_REG          0
#define STATUS_REG        1
#define CONTROL_REG       2

#define WriteControlRegister( pDeviceExtension, byte )   \
     ( WRITE_PORT_UCHAR( pDeviceExtension->portBase + CONTROL_REG, byte ) )

#define WriteDataRegister( pDeviceExtension, byte )      \
     ( WRITE_PORT_UCHAR( pDeviceExtension->portBase + DATA_REG, byte ) )

#define ReadStatusRegister( pDeviceExtension )           \
     ( READ_PORT_UCHAR( pDeviceExtension->portBase + STATUS_REG ) )

Исполняемый код драйвера

В файле Driver.cpp размещен исходный текст всех функций драйвера.

В процедуре DriverEntry выполняется регистрация процедур DriverUnload (отвечает за завершающие операции при выгрузке драйвера), DispatchCreate (при получении клиентом дескриптора для доступа к драйверу), DispatchClose (при закрытии дескриптора, полученного для доступа к драйверу), DispatchWrite (обработка IRP пакета, поступившего вследствие вызова WriteFile в приложении-клиенте), DispatchRead (обработка IRP пакета, поступившего вследствие вызова ReadFile в приложении-клиенте).

Действия по созданию объекта устройства, символьной ссылки и подключению драйвера к прерыванию в данном Legacy драйвере тоже выполняются в DriverEntry, только лишь оформлены они в виде автономной функции CreateDevice. (В WDM драйвере реального PnP устройства эти операции следовало бы выполнять в процедуре AddDevice и обработчике IRP_MJ_PNP + IRP_MN_START_DEVICE, поскольку загрузка драйвера является только частью старта PnP устройства).

//=========================================================================
// Файл driver.c
// Драйвер обслуживания заглушки CheckIt (параллельный порт 378h)
// By SVP, 20 June 2004
//=========================================================================
#include "driver.h"

// Предварительные объявления функций
static NTSTATUS CreateDevice (   IN PDRIVER_OBJECT pDriverObject,
					IN ULONG          portBase,
					IN ULONG          Irq  );
static NTSTATUS DispatchCreate ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp );
static NTSTATUS DispatchClose  ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp );
static VOID     DriverUnload   ( IN PDRIVER_OBJECT pDriverObject );
static NTSTATUS DispatchWrite  ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp );
static NTSTATUS DispatchRead   ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp );

BOOLEAN Isr  ( IN PKINTERRUPT pInterruptObject, IN PVOID pServiceContext );

BOOLEAN DoNextTransfer ( IN PVOID pContext );

VOID DpcForIsr( IN PKDPC pDpc,
		   IN PVOID DeferredContext,
		   IN PVOID pArg1, IN PVOID pArg2 );
//=========================================================================
// Функция:     DriverEntry
// Назначение:  Инициализирует драйвер, подключает объект устройства для
//              получения прерываний.
// Аргументы:   pDriverObject - поступает от Диспетчера ввода/вывода
//              pRegistryPath - указатель на Юникод-строку,
//              обозначающую раздел Системного Реестра, созданный
//              для данного драйвера.
// Возвращаемое значение:
//              NTSTATUS - в случае нормального завершения STATUS_SUCCESS
//                         или код ошибки STATUS_Xxx
//
extern "C" NTSTATUS DriverEntry (  IN PDRIVER_OBJECT  pDriverObject,
                                   IN PUNICODE_STRING pRegistryPath )
{
	NTSTATUS status;
	#if DBG==1
	DbgPrint("LPTPORT: in DriverEntry, RegistryPath is:\n     %ws. \n",
	               pRegistryPath->Buffer);
	#endif
	// Регистрируем рабочие процедуры драйвера:
	pDriverObject->DriverUnload = DriverUnload;

	pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
	pDriverObject->MajorFunction[IRP_MJ_CLOSE]  = DispatchClose;
	pDriverObject->MajorFunction[IRP_MJ_WRITE]  = DispatchWrite;
	pDriverObject->MajorFunction[IRP_MJ_READ]   = DispatchRead;
	
	// Работа по созданию объекта устройства, подключению
	// ресурсов, прерывания, созданию символьной ссылки:
	status = CreateDevice(pDriverObject, 0x378, 0x7);

return status;
}
//=========================================================================
// Функция:     CreateDevice
// Назначение:  Создание устройства с точки зрения системы
// Аргументы: pDriverObject - поступает от Диспетчера ввода/вывода
//            portBase - адрес базового регистра параллельного порта (378h)
//            Irq - прерывание (в терминах шины ISA) для обслуживания порта
// Возвращаемое значение:
//            NTSTATUS - в случае нормального завершения STATUS_SUCCESS
//                         или код ошибки STATUS_Xxx
//
NTSTATUS CreateDevice ( IN PDRIVER_OBJECT   pDriverObject,
                        IN ULONG            portBase,
                        IN ULONG            Irq   )
{
	NTSTATUS status;
	PDEVICE_OBJECT pDevObj;
	PDEVICE_EXTENSION pDevExt;
	// Создаем внутреннее имя устройства

	UNICODE_STRING devName;
	RtlInitUnicodeString( &devName, L"\\Device\\LPTPORT" );
	// Создаем объект устройства
	status= IoCreateDevice( pDriverObject,
	                        sizeof(DEVICE_EXTENSION),
	                        &devName,
	                        FILE_DEVICE_UNKNOWN,
	                        0,
	                        TRUE,
	                        &pDevObj
	                        );
	if (!NT_SUCCESS(status))  return status;

	// Будем использовать метод буферизации BUFFERED_IO
	pDevObj->Flags |= DO_BUFFERED_IO;
	// Заполняем данными структуру Device Extension
	pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
	pDevExt->pDevice        = pDevObj;   // сохраняем - это пригодится
	pDevExt->ustrDeviceName = devName;
	pDevExt->Irq            = Irq;
	pDevExt->portBase       = (PUCHAR)portBase;
	pDevExt->pIntObj        = NULL;
	pDevExt->xferRest       = 0; // сейчас нет неотправленных данных
	pDevExt->pIntObj        = NULL;
	//================================================
	// Инициализируем объект DPC для последующего использования
	// при обработки прерываний:
	KeInitializeDpc( &(pDevExt->DpcForIsr_Object),
	                 DpcForIsr,
	                 pDevExt  // <- pDeferredContext в функции DpcForIsr
	                 );
	//================================================
	// На всякий случай блокируем поступление прерываний:
	WriteControlRegister ( pDevExt, CR_DEFAULT );
	//================================================
	// Создаем и подключаем объект прерываний:
	KIRQL     kIrql;
	KAFFINITY kAffinity;

	ULONG kVector =
		HalGetInterruptVector(Isa, 0, pDevExt->Irq, pDevExt->Irq,
		                      &kIrql,    &kAffinity);
	// Замечание. Для Isa шины второй параметр (номер шины) обычно
	// равен 0, а третий и четвертый параметры равны.

	#if DBG==1
		DbgPrint( "LPTPORT: Interrupt %d converted to kIrql = %d, "
		"kAffinity = %d, kVector = %X(hex)\n",
		pDevExt->Irq, kIrql, kAffinity, kVector);
	#endif

	status =
  	 IoConnectInterrupt (
	             &pDevExt->pIntObj, // Здесь будет создан Interrupt Object
	             Isr,       // Наша функция ISR
	             pDevExt,   // Этот указатель ISR функция будет
	                        // получать при вызове (контекстный указатель)
	             NULL,      // Не будем использовать spin-блокировку для
	                        // безопасного доступа к совместно используемым
	                        // данным
	             kVector,   // транслированное значение прерывания
	             kIrql,     // DIRQL
	             kIrql,     // DIRQL
	             Latched,   // Прерывание по перепаду
	             TRUE,      // Совместно используемое (Shared) прерывание
	             kAffinity, // Поцессоров в мультипроцессорной системе
	             FALSE );   // Не сохранять значения регистров сопроцессора
	if (!NT_SUCCESS(status))
	{
		// В случае неудачи удаляем объект устройства
		IoDeleteDevice( pDevObj );
		return status;
	}	
	#if DBG==1
		DbgPrint("LPTPORT: Interrupt successfully connected.\n");
	#endif
	//================================================
	// Создаем символьную ссылку:
	UNICODE_STRING symLinkName;
	// Сформировать символьное имя:
	//#define SYM_LINK_NAME L"\\??\\LPTPORT0"
	//                        ^^ проходит только в NT
	// Для того, чтобы работало в Windows 98 & XP :
	#define SYM_LINK_NAME L"\\DosDevices\\LPTPORT0"

	RtlInitUnicodeString( &symLinkName, SYM_LINK_NAME );

	// Создать символьную ссылку:
	status = IoCreateSymbolicLink( &symLinkName, &devName );
	if (!NT_SUCCESS(status))
	{	// При неудаче - отключаемся от прерывания и
		// удаляем объект устройства:
		IoDisconnectInterrupt( pDevExt->pIntObj );
		IoDeleteDevice( pDevObj );
		return status;
	}

	pDevExt->ustrSymLinkName = symLinkName;
	#if DBG==1
		DbgPrint("LPTPORT: Symbolic Link is created: %ws. \n",
		                   pDevExt->ustrSymLinkName.Buffer);
	#endif
return STATUS_SUCCESS;
}

Работа c DPC процедурами может проходить по двум существенно различающимся сценариям. В первом из них, который будет реализован в следующем варианте драйвера, DPC процедура соотносится с объектом устройства вызовом IoInitializeDpcRequest, и код драйвера может запланировать ее вызов путем применения IoRequestDpc со ссылкой на объект устройства. Таким образом, за объектом устройства можно закрепить одну DPC функцию. А сам DPC объект "обитает" в объекте устройства и его не рекомендуется "касаться" непосредственно.

Другой сценарий, реализуемый ниже, предлагает связывание DPC объекта (неинициализированный DPC объект — это просто область памяти под структурой типа KDPC) с одной из функций драйвера вызовом KeInitializeDpc, см. таблицу 10.23. Такой инициализированный DPC объект может быть вставлен в очередь DPC объектов с помощью вызова KeInsertQueueDpc — так можно запланировать к вызову связанную с ним DPC функцию драйвера в любом месте кода драйвера, правда, работающем при уровне IRQL не ниже DISPATCH_LEVEL. При использовании данного сценария, драйвер (в том числе, его процедура обработки прерывания) может планировать для последующего вызова разные DPC функции. Заметим, что, временно повысив IRLQ при помощи вызова KeRaiseIrql, драйвер может планировать вызовы DPC функций при помощи KeInsertQueueDpc даже внутри кода, работающего при IRQL, равном PASSIVE_LEVEL.

При работе по этому второму сценарию следует, однако помнить, что в условиях принудительной выгрузки драйвера в системной очереди не должно оставаться DPC объектов от выгружаемого драйвера. Эта мера безопасности реализована ниже в процедуре DriverUnload, когда для удаления DPC объекта из системной очереди используется вызов KeRemoveQueueDpc.

//=========================================================================
// Функция:     DriverUnload
// Назначение:  Останавливает и удаляет объекты устройств, отключает
//              прерывания, подготавливает драйвер к выгрузке.
// Аргументы:   pDriverObject - поступает от Диспетчера ввода/вывода
// Возвращаемое значение: нет
//
VOID DriverUnload ( IN PDRIVER_OBJECT pDriverObject )
{
	#if DBG==1
	DbgPrint("LPTPORT: in DriverUnload now\n");
	#endif
	PDEVICE_OBJECT	pNextObj = pDriverObject->DeviceObject;

	// Проход по всем устройствам, контролирумым драйвером
	for( ; pNextObj!=NULL; )
	{
		PDEVICE_EXTENSION pDevExt =
			(PDEVICE_EXTENSION)pNextObj->DeviceExtension;
		// Удаляем объект прерываний:
		if (pDevExt->pIntObj)
		{
			// На всякий случай блокируем поступление прерываний
			// и очищаем DPC очередь от нашего DPC объекта
			WriteControlRegister( pDevExt, CR_DEFAULT);
			KeRemoveQueueDpc( &(pDevExt->DpcForIsr_Object) );
			IoDisconnectInterrupt( pDevExt->pIntObj );
		}
		// Удаляем символьную ссылку:
		IoDeleteSymbolicLink(&pDevExt->ustrSymLinkName);
		#if DBG==1
		DbgPrint("LPTPORT: SymLink %ws deleted\n",
				pDevExt->ustrSymLinkName.Buffer);
		#endif
		// Сохраняем ссылку на следующее устройство и удаляем
		// текущий объект устройства:
		pNextObj = pNextObj->NextDevice;
		IoDeleteDevice( pDevExt->pDevice );
	}
	// Замечание. Поскольку мы использовали ресурс (параллельный порт)
	// объявленные не нами, то освобождение этого ресурса можно опустить.
}
//=========================================================================
// Функция:     DispatchCreate
// Назначение:  Обрабатывает запрос по поводу Win32 вызова CreateFile
// Аргументы: pDevObj - поступает от Диспетчера ввода/вывода
//            pIrp - поступает от Диспетчера ввода/вывода
// Возвращаемое значение: STATUS_SUCCESS
//
NTSTATUS DispatchCreate ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp )
{
	#if DBG==1
		DbgPrint("LPTPORT: in DispatchCreate now\n");
	#endif
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	pIrp->IoStatus.Information = 0; // ни одного байта не передано
	IoCompleteRequest( pIrp, IO_NO_INCREMENT );
	return STATUS_SUCCESS;
}
//=========================================================================
// Функция:      DispatchClose
// Назначение:   Обрабатывает запрос по поводу Win32 вызова CloseHandle
// Аргументы:    pDevObj - поступает от Диспетчера ввода/вывода
//               pIrp - поступает от Диспетчера ввода/вывода
// Возвращаемое значение: STATUS_SUCCESS
//
NTSTATUS DispatchClose ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp )
{
	#if DBG==1
		DbgPrint("LPTPORT: in DispatchClose now\n");
	#endif
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	pIrp->IoStatus.Information = 0; // ни одного байта не передано
	IoCompleteRequest( pIrp, IO_NO_INCREMENT );
return STATUS_SUCCESS;
}
//=========================================================================
// Функция:     DispatchWrite
// Назначение:  Обрабатывает запрос по поводу Win32 вызова  WriteFile
// Аргументы:   pDevObj - поступает от Диспетчера ввода/вывода
//              pIrp - поступает от Диспетчера ввода/вывода
// Возвращаемое значение:
//              NTSTATUS - в случае нормального завершения STATUS_SUCCESS
//                         или код ошибки STATUS_Xxx
//
NTSTATUS DispatchWrite ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp )
{
	#if DBG==1
		DbgPrint("LPTPORT: in DispatchWrite now\n");
	#endif
	PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp );
	PDEVICE_EXTENSION  pDevExt   =
				(PDEVICE_EXTENSION) pDevObj->DeviceExtension;
	ULONG              xferSize  = pIrpStack->Parameters.Write.Length;

	if( xferSize == 0 ) // Нет данных для передачи :
	{
		#if DBG==1
			DbgPrint("LPTPORT: DispatchWrite: no bytes to transfer.\n");
		#endif
		pIrp->IoStatus.Status = STATUS_SUCCESS;
		pIrp->IoStatus.Information = 0; // Нет переноса
		IoCompleteRequest( pIrp, IO_NO_INCREMENT );
		return STATUS_SUCCESS;
	}
	if( pDevExt->xferRest> 0 )
	{	// Не начинаем обрабатывать новый запрос, если остались
		// непереданные данные (в буфере deviceOutBuffer драйвера)
		#if DBG==1
			DbgPrint("LPTPORT: DispatchWrite: not all data transferred\n");
		#endif
		pIrp->IoStatus.Status = STATUS_DEVICE_BUSY;
		pIrp->IoStatus.Information = 0; // Нет переноса
		IoCompleteRequest( pIrp, IO_NO_INCREMENT );
		return STATUS_DEVICE_BUSY;
	}
	if( xferSize > MAX_BUFFER_SIZE )
	{
		// Слишком большой запрос. Завершаем обработку IRP пакета:
		#if DBG==1
			DbgPrint( "PLPTPORT: DispatchWrite: xferSize > MAX_BUFFER_SIZE\n" );
		#endif
		pIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
		pIrp->IoStatus.Information = 0; // Нет переноса
		IoCompleteRequest( pIrp, IO_NO_INCREMENT );
		return STATUS_INSUFFICIENT_RESOURCES;
	}
	// Буфер с данными, поступивший от клиента, переносим в
	// рабочий буфер:
	PUCHAR userBuffer = (PUCHAR)pIrp->AssociatedIrp.SystemBuffer;
	RtlCopyMemory( pDevExt->deviceOutBuffer, userBuffer, xferSize );

	pDevExt->xferRest  = xferSize;
	pDevExt->xferCount = 0;

	// Запускаем перенос данных в первый раз
	KeSynchronizeExecution( pDevExt->pIntObj,
	                        DoNextTransfer,
	                        pDevExt );
	// Формально -- передача завершена:
	pIrp->IoStatus.Information = xferSize;
	IoCompleteRequest( pIrp, IO_NO_INCREMENT );
return STATUS_SUCCESS;
}

Обработчик запросов от Win32 вызова WriteFile переносит данные во внутренний буфер deviceOutBuffer и инициирует процесс переноса вызовом DoNextTransfer при посредничестве KeSyncronizeExecution. Последний повышает текущий уровень IRQL работы до уровня, ассоциированного с объектом прерывания, указанного в качестве первого параметра pDevExt->pIntObj. В результате (это будет видно позже в распечатке log-файла из программы DebugView) код функции DoNextTransfer выполняется на уровне IRQL равном IRQL кода функции ReadDataSafely и кода функции Isr, которые равны 8 в данном тесте.

//=========================================================================
// Функция:     DispatchRead
// Назначение:  Обрабатывает запрос по поводу Win32 вызова ReadFile
// Аргументы:   pDevObj - поступает от Диспетчера ввода/вывода
//              pIrp - поступает от Диспетчера ввода/вывода
// Возвращаемое значение:
//              NTSTATUS - в случае нормального завершения STATUS_SUCCESS
//                         или код ошибки STATUS_Xxx
//
NTSTATUS DispatchRead ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp )
{
	PDEVICE_EXTENSION  pDevExt =
	(PDEVICE_EXTENSION) pDevObj->DeviceExtension;
	PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp );
	ULONG              xferSize, xferredSize;
	#if DBG==1
		DbgPrint("LPTPORT: in DispatchRead now\n");
	#endif

	if( pDevExt->xferRest> 0 )
	{	// Не начинаем обрабатывать новый запрос, если остались
		// непереданные данные
		#if DBG==1
			DbgPrint("LPTPORT: DispatchRead: Exists nonprocessed request\n");
		#endif
		pIrp->IoStatus.Status = STATUS_DEVICE_BUSY;
		IoCompleteRequest( pIrp, IO_NO_INCREMENT );
		return STATUS_DEVICE_BUSY;
	}

	// Определяем размер запроса:
	xferSize = pIrpStack->Parameters.Read.Length;
	if( xferSize > MAX_BUFFER_SIZE ) xferSize = MAX_BUFFER_SIZE;

	// Передаем не более данных, чем размер числа переданных байт
	xferredSize = pDevExt->xferCount;
	xferSize = (xferSize < xferredSize ? xferSize : xferredSize );
	if(xferSize> 0)
	{	// Копируем содержимое внутреннего входного буфера
		// в буфер клиента
		PVOID userBuffer = pIrp->AssociatedIrp.SystemBuffer;
		RtlCopyMemory(userBuffer, pDevExt->deviceInBuffer, xferSize);
	}

	// Завершаем обработку IRP пакета:
	pIrp->IoStatus.Status      = STATUS_SUCCESS;
	pIrp->IoStatus.Information = xferSize;	// число переданных байт
	IoCompleteRequest( pIrp, IO_NO_INCREMENT );
	#if DBG==1
		DbgPrint("LPTPORT: DispatchRead: %d byted transferred.\n", xferSize);
	#endif
return STATUS_SUCCESS;
}

//=========================================================================
// Процедура обслуживания прерывания:
//
BOOLEAN Isr (IN PKINTERRUPT pInterruptObject, IN PVOID pServiceContext )
{
	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pServiceContext;
	KIRQL             currentIrql = KeGetCurrentIrql();
	#if DBG==1
		DbgPrint("LPTPORT: In Isr procedure, ISR_Irql=%d\n", currentIrql);
	#endif
	//===============================================================
	// Строго говоря, следовало бы проверить, имело ли место
	// прерывание и наше ли это прерывание:
	// UCHAR status = ReadStatusRegister( pDevExt );
	// if( status & 0x04 ) return FALSE; // прерывания не было
	// Однако в силу упомянутых накладок с использованием бита SR.2,
	// не проверяем это вовсе, делая допущение, что если Isr получила
	// управление, то прерывание √наше.
	//===============================================================
	// Общей практикой является блокирование поступления прерываний
	// в этом месте:

	// WriteControlRegister( pDevExt, CR_DEFAULT);

	// Однако, мы не будем этого делать, чтобы не испортить данные,
	// находящиеся сейчас в Status Register. Полагаем, что кроме
	// нашего драйвера такие прерывания никто не генерирует, а драйвер
	// защищен тем, что Write запросы отвергаются до полного переноса
	// данных.
	//================================================================
	// Планируем вызов DPC процедуры для обработки прерывания позднее
	//
	KeInsertQueueDpc( &(pDevExt->DpcForIsr_Object),
	                  (VOID *)NULL,  // <- Arg1 in DpcForIsr
	                  (VOID *)NULL); // <- Arg2 in DpcForIsr
return TRUE;    // нормальное завершение обработки прерывания
}

Процедура обработки прерывания Isr планирует вызов DpcForIsr DPC функции через размещение DPC объекта в системной очереди. Этим ее функции и ограничиваются в столь простом драйвере.

//=========================================================================
// Код, который посылает в порт данные, вызывающие (при наличии
// CheckIt заглушки) сигнал прерывания:
//
VOID ForceInterrupt( PDEVICE_EXTENSION pDevExt, UCHAR bits )
{
	// Генерируем сигнал прерывания
	WriteControlRegister( pDevExt, bits | CR_INT_ENB | CR_DEFAULT );
	KeStallExecutionProcessor(50); // Удерживаем состояние 50 мкс
	// Удерживая информационные биты, снимаем импульс ACK#
	WriteControlRegister( pDevExt, bits | CR_INT_ENB | CR_NOT_RST | CR_DEFAULT  );
	KeStallExecutionProcessor(50); // Удерживаем состояние 50 мкс
	// Удерживая информационные биты, снимаем импульс ACK#
	WriteControlRegister( pDevExt, bits | CR_INT_ENB | CR_DEFAULT );
}
//=========================================================================
// Функция DoNextTransfer безопасно (от вмешательства кода прерывания, ISR
// функции) записывает данные в параллельный порт:
//
BOOLEAN DoNextTransfer ( IN PVOID pContext )
{

	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pContext;
	UCHAR nextByte =
		0x0F & ( pDevExt->deviceOutBuffer[pDevExt->xferCount] );
	#if DBG==1
		DbgPrint("LPTPORT: DoNextTransfer:\n");
		DbgPrint("LPTPORT: Sending 0x%02X to port %X\n",
		                   nextByte, pDevExt->portBase);
	#endif
	// Отправка полубайта данных.
	//= 1 ===============================================================
	// Заглушка CheckIt работает не самым простым образом.
	// Бит 0 отсылаемого полубайта нужно отправить как бит 0
	// в Data Register
	WriteDataRegister ( pDevExt, nextByte & 0x01);
	//
	// Это бит будет считан как бит 3 из Status Register

	//= 2 ===============================================================
	// Биты 1-3 отсылаемого полубайта нужно отправить как
	// биты 0, 1 и 3 в Control Register
	UCHAR bits = (nextByte & 0x8) + ((nextByte & 0x6)>> 1);
	// Таким образом бит 2 всегда равен 0

	bits ^= 0x3; // Инвертируем биты (0 & 1) перед
	             // записью в Control Register
	// Эти биты будут считаны в Status Register как биты 4,5 и 7
	#if DBG==1
		DbgPrint("LPTPORT: generating next interrupt...\n");
	#endif
	// Собственно отправляем данные вместе с генерацией
	// сигнала прерывания:
	ForceInterrupt( pDevExt, bits );
return TRUE;
}

//=========================================================================
// Функция ReadDataSafely выполняет чтение данных из устройства
// без опасения быть прерванной кодом ISR функции:
//
BOOLEAN ReadDataSafely ( IN PVOID pContext )
{
	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pContext;
	UCHAR status = ReadStatusRegister( pDevExt );
	// Преобазуем полученные через Status register биты в понятную форму
	UCHAR readByte =
		((status & 0x8)<< 1) | ((status & 0x30)<< 1) | (status & 0x80);
	readByte >>= 4;
	pDevExt->deviceInBuffer[pDevExt->xferCount++] = readByte;

	#if DBG==1
		KIRQL currentIrql = KeGetCurrentIrql();
		DbgPrint( "LPTPORT: ReadDataSafely, currentIrql=%d ReadStatus = %02X"
	              " ReadByte = %02X\n",     currentIrql,    status,    readByte );
		DbgPrint( "LPTPORT: \n");
	#endif

	pDevExt->xferRest--;  // Число непереданных байт уменьшилось
return (pDevExt->xferRest< 1 ? FALSE : TRUE ); // это значение возвратится
				// через вызов KeSynchronizeExecution
}

//=========================================================================
// Функция:    DpcForIsr
// Назначение: Данная функция начинает работу по "заказу" ISR функции
//             и выполняет ввод/вывод путем безопасного  вызова функций
//             ReadDataSafely и DoNextTransfer, то есть реализует
//             низкоуровневый ввод/вывод
// Аргументы:  Указатель на текущий DPC объект (не используется)
//             pDeferredContext - контекстный указатель - так передается
//             указатель на структуру Device Extension
// Возвращаемое значение: нет
//
VOID
DpcForIsr( IN PKDPC pDpc,             // не используется
 	   IN PVOID pDeferredContext,
	   IN PVOID pArg1,                 // не используется
	   IN PVOID pArg2                  // не используется
	   )
{
	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDeferredContext;
	#if DBG==1
		KIRQL currentIrql = KeGetCurrentIrql();
		DbgPrint("LPTPORT: We are now in DpcForIsr, currentIrql=%d xferCount = %d\n",
		         currentIrql, pDevExt->xferCount );
	#endif
	// Безопасно читаем данные из устройства:
	BOOLEAN dataNotTransferrered =
			KeSynchronizeExecution( pDevExt->pIntObj,
			                        ReadDataSafely,
			                        pDevExt  );
	if ( dataNotTransferrered )   // остались непереданные данные :
	{
		// Если остались данные, то записываем следующую порцию
		// данных в порт и запускаем прерывание:
		KeSynchronizeExecution( pDevExt->pIntObj,
		                        DoNextTransfer,
		                        pDevExt
		                        );
	}
	else
	{
		#if DBG==1
			DbgPrint("LPTPORT: We are now in DpcForIsr, all data transmitted.\n");
		#endif
	}
}
//=========================================================================

Приложение для тестирования драйвера

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

Сборку приложения можно осуществлять при помощи такого файла Sources:

TARGETNAME=test
TARGETTYPE=PROGRAM
UMTYPE=console
UMENTRY=main
UMBASE=0x400000
TARGETPATH=.
INCLUDES= $(BASEDIR)\inc

SOURCES=test.cpp
А это, собственно, исходный код тестового приложения:
//=======================================================================
// Файл тестовой программы test.cpp
//=======================================================================

#include <windows.h>
#include <stdio.h>

// Предварительное объявление
int ReadWrite(HANDLE devHandle);

#define BUFFSIZE (17)
static unsigned char outBuffer[BUFFSIZE], inBuffer[BUFFSIZE*2];

int __cdecl main()
{
	printf("\n\n\n\n\nParallel Port CheckIt Loopback Device Test Program.\n" );

	HANDLE devHandle;
	devHandle = CreateFile(  "\\\\.\\LPTPORT0",
	                         GENERIC_READ | GENERIC_WRITE,
	                         0,         // share mode none
	                         NULL,	    // no security
	                         OPEN_EXISTING,
	                         FILE_ATTRIBUTE_NORMAL,
	                         NULL );    // no template

	if ( devHandle == INVALID_HANDLE_VALUE )
	{
		printf("Error: can not open device PLPTPORT0. Win32 errno %d\n",
		       GetLastError() );
		return -1;
	}
	printf("Congratulation. LPTPORT0 device is open.\n\n");
	//==========================================	
	DWORD i=3,j=0;
	for ( ; j<sizeof(outBuffer); )  outBuffer[j++] = (unsigned char)i++;
	//==========================================
	//for(i=0; i< 100000; i++)
	//{
		int result = ReadWrite(devHandle);
	//	if(result) break;
	//}
	//==========================================
	// Завершение работы
	if ( ! CloseHandle(devHandle) )
	{
		printf("\n Error during CloseHandle: errno %d.\n", GetLastError() );
		return 5;
	}
	printf("\n\n\n Device LPTPORT0 successfully closed. Normal exit.\n");
return 0;
}

//==========================================================================
// Выделим запись и чтение данных в отдельную функцию:
//
int ReadWrite(HANDLE devHandle)
{
	//==========================================
	// Передача данных драйверу
	printf("Writing to LPTPORT0 device...\n");

	DWORD bytesWritten, outCount = sizeof(outBuffer);
	if ( !WriteFile(devHandle, outBuffer, outCount, &bytesWritten, NULL) )
	{
		printf("Error during WriteFile: errno %d.\n", GetLastError() );
		return 1;
	}
	if ( outCount != bytesWritten )      // если не все передалось:
	{
		printf("Error: while wrote %d bytes, WriteFile reported %d bytes.\n",
		       outCount, bytesWritten);
		return 2;
	}
	printf("Successfully written %d bytes.\n Buffer content was: \n",
	        outCount);
	for (DWORD i=0; i<bytesWritten; i++ ) printf("%02X ",outBuffer[i]);
	//==========================================
	//Sleep(10); // Ожидание 10 миллисекунд
	//==========================================
	// Получение данных из драйвера
	printf("\n\nReading from device LPTPORT0...\n");

	DWORD bytesRead, inCount = sizeof(inBuffer);
	if ( !ReadFile(devHandle, inBuffer, inCount, &bytesRead, NULL) )
	{
		printf("Error during ReadFile: errno %d.\n", GetLastError() );
		return 3;
	}
	if ( bytesRead != bytesWritten )
	{   // размер записанных и прочитанных данных не совпадает
		printf("Error: is to read %d bytes, but ReadFile reported %d bytes.\n",
		        bytesWritten, inCount);
		return 4;
	}
	printf("Succesfully read %d bytes.\n Buffer content is: \n",
	        bytesRead);
	for ( i=0; i<bytesRead; i++ ) printf( "%02X ", (UCHAR)inBuffer[i] );
return 0; // Нормальное завершение
}

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

L:\#ex_lpt\test\>test
Parallel Port CheckIt Loopback Device Test Program.
Congratulation. LPTPORT0 device is open. Writing to LPTPORT0 device...
Successfully written 17 bytes.
Buffer content was:
03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 Reading from device LPTPORT0... Successfully read 17 bytes. Buffer content is:
03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03
Device LPTPORT0 successfully closed. Normal exit.

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

Ниже приводится информация из отладочного вывода, перехваченного программой DebugView (log-файл этой программы). Средняя часть этого файла, сообщения с 28 по 90, опущена, поскольку в них содержится однообразная и малоинтересная информация.

00000000  0.00000000 LPTPORT: in DriverEntry, RegistryPath is:
00000001 0.00000223 \REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\LPTPort.
00000002 0.00003911 LPTPORT: Interrupt 7 converted to kIrql = 8,
kAffinity = 1, kVector = 191(hex)
00000003 0.00004833 LPTPORT: Interrupt successfully connected.
00000004 0.00007878 LPTPORT: Symbolic Link is created: \DosDevices\LPTPORT0.
00000005 5.38805379 LPTPORT: in DispatchCreate now
00000006 5.38822867 LPTPORT: in DispatchWrite now
00000007 5.38823342 LPTPORT: DoNextTransfer:
00000008 5.38823985 LPTPORT: SendingOxO3 to port 378
00000009 5.38824487 LPTPORT: generating next interrupt...
00000010 5.38835690 LPTPORT: In Isr procedure, ISR_Irql=8
00000011 5.38836388 LPTPORT: We are now in DpcForIsr, currentIrql=2 xferCount = 0
00000012 5.38837115 LPTPORT: ReadDataSafely, currentIrql=8 ReadStatus=1F
ReadByte=03
00000013 5.38837422 LPTPORT:
00000014 5.38837785 LPTPORT: DoNextTransfer:
00000015 5.38838260 LPTPORT: Sending 0x04 to port 378
00000016 5.38838763 LPTPORT: generating next interrupt...
00000017 5.38849854 LPTPORT: In Isr procedure, ISR_Irql=8
00000018 5.38850524 LPTPORT: We are now in DpcForIsr, currentIrql=2 xferCount = 1
00000019 5.38851167 LPTPORT: ReadDataSafely, currentIrql=8 ReadStatus = 27
ReadByte = 04
00000020 5.38851446 LPTPORT:
00000021 5.38851781 LPTPORT: DoNextTransfer:
00000022 5.38852228 LPTPORT: Sending 0x05 to port 378
00000023 5.38852731 LPTPORT: generating next interrupt...
00000024 5.38863822 LPTPORT: In Isr procedure, ISR_Irql=8
00000025 5.38864465 LPTPORT: We are now in DpcForIsr, currentIrql=2 xferCount = 2
00000026 5.38865135 LPTPORT: ReadDataSafely, currentIrql=8 ReadStatus = 2F
ReadByte = 05
00000027 5.38865442 LPTPORT: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
00000091 5.38991464 LPTPORT: DoNextTransfer:
00000092 5.38991939 LPTPORT: Sending 0x0F to port 378
00000093 5.38992442 LPTPORT: generating next interrupt...
00000094 5.39003533 LPTPORT: In Isr procedure, ISR_Irql=8
00000095 5.39004175 LPTPORT: We are now in DpcForIsr, currentIrql=2 xferCount = 12
00000096 5.39004874 LPTPORT: ReadDataSafely, currentIrql=8 ReadStatus = BF
ReadByte = 0F
00000097 5.39005181 LPTPORT:
00000098 5.39005516 LPTPORT: DoNextTransfer:
00000099 5.39005963 LPTPORT: Sending 0x00 to port 378
00000100 5.39006494 LPTPORT: generating next interrupt...
00000101 5.39017557 LPTPORT: In Isr procedure, ISR_Irql=8
00000102 5.39018199 LPTPORT: We are now in DpcForIsr, currentIrql=2
xferCount = 13
00000103 5.39018870 LPTPORT: ReadDataSafely, currentIrql=8 ReadStatus = 07
ReadByte = 00
00000104 5.39019177 LPTPORT:
00000105 5.39019512 LPTPORT: DoNextTransfer: 00000106 5.39019959 LPTPORT: Sending 0x01 to port 378
00000107 5.39020434 LPTPORT: generating next interrupt...
00000108 5.39031469 LPTPORT: In Isr procedure, ISR_Irql=8
00000109 5.39032112 LPTPORT: We are now in DpcForIsr, currentIrql=2 xferCount = 14
00000110 5.39032754 LPTPORT: ReadDataSafely, currentIrql=8 ReadStatus = 0F ReadByte = 01
00000111 5.39033061 LPTPORT:
00000112 5.39033425 LPTPORT: DoNextTransfer:
00000113 5.39033872 LPTPORT: Sending 0x02 to port 378
00000114 5.39034374 LPTPORT: generating next interrupt...
00000115 5.39045493 LPTPORT: In Isr procedure, ISR_Irql=8
00000116 5.39046136 LPTPORT: We are now in DpcForIsr, currentIrql=2 xferCount = 15
00000117 5.39046778 LPTPORT: ReadDataSafely, currentIrql=8 ReadStatus = 17 ReadByte = 02
00000118 5.39047058 LPTPORT:
00000119 5.39047393 LPTPORT: DoNextTransfer:
00000120 5.39047840 LPTPORT: Sending 0x03 to port 378
00000121 5.39048315 LPTPORT: generating next interrupt...
00000122 5.39059350 LPTPORT: In Isr procedure, ISR_Irql=8
00000123 5.39059992 LPTPORT: We are now in DpcForIsr, currentIrql=2 xferCount = 16
00000124 5.39060663 LPTPORT: ReadDataSafely, currentIrql=8 ReadStatus = 1F ReadByte = 03
00000125 5.39060942 LPTPORT:
00000126 5.39061333 LPTPORT: We are now in DpcForIsr, all data transmitted.
00000127 5.39104691 LPTPORT: in DispatchRead now
00000128 5.39105166 LPTPORT: DispatchRead: 17 byted transferred.
00000129 5.39139639 LPTPORT: in DispatchClose now
00000130 10.93612205 LPTPORT: in DriverUnload now
00000131 10.93615557 LPTPORT: SymLink \DosDevices\LPTPORT0 deleted

Дополнительный тест на скорость переноса

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

for(i=0; i<100000; i++)
{
	int result = ReadWrite(devHandle);
	if(result) break;
}

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

Рис. 11.3
График числа прерываний в секунду при интенсивном использовании драйвера, обслуживающего заглушку CheckIt

Если обратится к Диспетчеру задач Windows и посмотреть на график разделения времени процессора, расходуемого на обслуживание кода режима ядра и пользовательского режима, то увидим, что доля кода ядра в процентном отношении непривычно велика, см. рис. 11.4. Можно сразу же предположить, что все пользовательские процессы будут притормаживаться. И в самом деле, пока работает тестовое приложение в указанном интенсивном режиме "жизнь" в системе замирает — очень долго открываются и перемещаются окошки (время отклика на процессоре с тактовой частотой ЗГГц до 10 секунд), слегка "зависает" курсор мышки и т.п.

Рис. 11.4
Окно Диспетчера задач в момент запуска тестирующего приложения в интенсивном режиме (работа в режиме ядра занимает более 50% времени процессора)

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