Code Example: Parallel Port Loopback DriverThis example shows how to write a basic programmed I/O driver for the parallel port. The code for this example is in the Chap8 directory on the disk that accompanies this book. Several code fragments follow. Purpose of DriverThe purpose of this driver is to allow a test program to output nibbles (4-bit quantities) to the parallel port with the loopback connector attached. The data returned by the connector is stored in a temporary pool buffer within the driver. Thus, subsequent reads to the device should return the same nibble data that was output. Just to keep things interesting, the driver returns the nibble data shifted left by four bits. Since the loopback connector is wired in a non-straightforward manner, the driver code must assemble the nibble data from various data and status bits. Driver.HThe main header file for this driver builds on the two seen in previous chapters. Changes were made to the DEVICE_EXTENSION structure to support the parallel port hardware and driver-specific functionality.
typedef struct _DEVICE_EXTENSION {
PDEVICE_OBJECT pDevice;
ULONG DeviceNumber;
CUString ustrDeviceName; // internal name
CUString ustrSymLinkName; // external name
PUCHAR deviceBuffer; // temporary pool buffer
ULONG deviceBufferSize;
ULONG xferCount; // current transfer count
ULONG maxXferCount; // requested xfer count
ULONG portBase; // I/O register address
ULONG Irq; // Irq for parallel port
PKINTERRUPT pIntObj; // the interrupt object
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
Additionally, macros were added to the file for convenience in reading the parallel port device registers. #define PPORT_REG_LENGTH 4 #define DATA_REG 0 #define STATUS_REG 1 #define CONTROL_REG 2 // // Define access macros for registers. Each macro takes // a pointer to a Device Extension as an argument // #define ReadStatus( pDevExt ) \ (READ_PORT_UCHAR( (PUCHAR) \ pDevExt->portBase + STATUS_REG )) #define ReadControl( pDevExt ) \ (READ_PORT_UCHAR( (PUCHAR) \ pDevExt->PortBase + CONTROL_REG )) #define WriteControl( pDevExt, bData ) \ (WRITE_PORT_UCHAR( (PUCHAR) \ pDevExt->portBase + CONTROL_REG, bData )) #define WriteData( pDevExt, bData ) \ (WRITE_PORT_UCHAR( (PUCHAR) \ pDevExt->portBase + DATA_REG, bData )) Driver.cppThe basis for the code in this module is the same as from the last chapter. Noteworthy changes follow. CREATEDEVICEThis code excerpt demonstrates a necessary technique for device detection prior to Plug and Play. A device resource is claimed directly and forcibly through use of a helper function, ClaimResources. This function accepts as arguments the I/O port base address and IRQ level. The port address and IRQL are converted to system-wide port and interrupt numbers through use of an obsolete function, HalGetInterruptVector. This function and overall technique is replaced in the next chapter.
// Since this driver controlls real hardware,
// the hardware controlled must be discovered.
// Chapter 9 will discuss auto-detection,
// but for now we will hard-code the hardware
// resource for the common printer port.
// We use IoReportResourceForDetection to mark
// PORTs and IRQs as "in use."
// This call will fail if another driver
// (such as the standard parallel driver(s))
// already control the hardware
status =
ClaimHardware(pDriverObject,
pDevObj,
0x378, // fixed port address
PPORT_REG_LENGTH,
0x7); // fixed irq
if (!NT_SUCCESS(status)) {
// if it fails now, must delete Device object
IoDeleteDevice( pDevObj );
return status;
}
// We need a DpcForIsr registration
IoInitializeDpcRequest(
pDevObj,
DpcForIsr );
// Create & connect to an Interrupt object
// To make interrupts real, we must
translate irq into
// a HAL irq and vector (with processor affinity)
KIRQL kIrql;
KAFFINITY kAffinity;
ULONG kVector =
HalGetInterruptVector(Internal, 0, pDevExt->Irq, 0,
&kIrql, &kAffinity);
status =
IoConnectInterrupt(
&pDevExt->pIntObj, // the Interrupt object
Isr, // our ISR
pDevExt, // Service Context
NULL, // no spin lock
kVector, // vector
kIrql, // DIRQL
kIrql, // DIRQL
LevelSensitive, // Latched or Level
TRUE, // Shared?
-1, // processors in an MP set
FALSE ); // save FP registers?
if (!NT_SUCCESS(status)) {
// if it fails now, must delete Device object
IoDeleteDevice( pDevObj );
return status;
}
DISPATCHWRITEThis function was cut down considerably since its main purpose is no longer to perform the I/O transfer. Instead, it simply queues the IRP for the Start I/O routine. // Start the I/O IoMarkIrpPending( pIrp ); IoStartPacket( pDevObj, pIrp, 0, NULL); return STATUS_PENDING; DISPATCHREADThis function was not touched since it returns the device's pool buffer contents (now containing nibble data) to the user buffer. STARTIOThis new routine is called by the I/O Manager each time an IRP is dequeued. The routine completes the work initiated by DispatchWrite. It transmits the first character from the user's output buffer by a call to a helper function, TransmitByte. The helper function constructs the output nibble and sends it to the physical device.
VOID StartIo(
IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp
) {
PIO_STACK_LOCATION pIrpStack =
IoGetCurrentIrpStackLocation( pIrp );
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pDevObj->DeviceExtension;
PUCHAR userBuffer;
ULONG xferSize;
switch( pIrpStack->MajorFunction ) {
// Use a SynchCritSection routine to
// start the write operation...
case IRP_MJ_WRITE:
// Set up counts and byte pointer
pDevExt->maxXferCount =
pIrpStack->Parameters.Write.Length;
pDevExt->xferCount = 0;
// Since we processing a new Write request,
// free up any old buffer
if (pDevExt->deviceBuffer != NULL) {
ExFreePool(pDevExt->deviceBuffer);
pDevExt->deviceBuffer = NULL;
pDevExt->deviceBufferSize = 0;
}
// Determine the length of the request
xferSize =
pIrpStack->Parameters.Write.Length;
// Obtain user buffer pointer
userBuffer = (PUCHAR)
pIrp->AssociatedIrp.SystemBuffer;
// Allocate the new buffer
pDevExt->deviceBuffer = (PUCHAR)
ExAllocatePool( PagedPool, xferSize );
if (pDevExt->deviceBuffer == NULL) {
// buffer didn't allocate???
// fail the IRP
pIrp->IoStatus.Status =
STATUS_INSUFFICIENT_RESOURCES;
pIrp->IoStatus.Information = 0;
IoCompleteRequest( pIrp,
IO_NO_INCREMENT );
IoStartNextPacket( pDevObj, FALSE );
}
pDevExt->deviceBufferSize = xferSize;
//
// Try to send the first byte of data.
//
TransmitByte( pDevExt );
break;
ISRThe interrupt service routine for this driver is quite simple. It relies on a DpcForIsr routine to mark a completed IRP when the last byte of the user's output buffer has been sent to the printer port.
BOOLEAN Isr (
IN PKINTERRUPT pIntObj,
IN PVOID pServiceContext ) {
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pServiceContext;
PDEVICE_OBJECT pDevObj = pDevExt->pDevice;
PIRP pIrp = pDevObj->CurrentIrp;
UCHAR status = ReadStatus( pDevExt );
if (!(status & STS_NOT_IRQ))
return FALSE;
// its our interrupt, deal with it
// transmit another character
if (!TransmitByte( pDevExt ))
// if no more bytes, complete the request
IoRequestDpc( pDevObj, pIrp, (PVOID)pDevExt );
return TRUE;
}
DPCFORISRThe final interesting routine for this driver completes an I/O request when so ordered by the ISR.
VOID
DpcForIsr(
IN PKDPC pDpc,
IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp,
IN PVOID pContext
)
{
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pContext;
pIrp->IoStatus.Information =
pDevExt->xferCount;
// This loopback device always works
pIrp->IoStatus.Status =
STATUS_SUCCESS;
//
// If we're being called directly
from Start I/O,
// don't give the user any priority boost.
//
if( pDpc == NULL )
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
else
IoCompleteRequest( pIrp, IO_PARALLEL_INCREMENT );
//
// This one's done. Begin working on
the next
IoStartNextPacket( pDevObj, FALSE );
}
|
Code Example: Parallel Port Loopback Driver
Similar products
Similar pages





