Code Example: Parallel Port Loopback Driver

< BACK  NEXT >
[oR]

This 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 Driver

The 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.H

The 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.cpp

The basis for the code in this module is the same as from the last chapter. Noteworthy changes follow.

CREATEDEVICE

This 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; } 
DISPATCHWRITE

This 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; 
DISPATCHREAD

This function was not touched since it returns the device's pool buffer contents (now containing nibble data) to the user buffer.

STARTIO

This 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; 
ISR

The 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; } 
DPCFORISR

The 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 ); } 
< BACK  NEXT >


The Windows 2000 Device Driver Book(c) A Guide for Programmers
The Windows 2000 Device Driver Book: A Guide for Programmers (2nd Edition)
ISBN: 0130204315
EAN: 2147483647
Year: 2000
Pages: 156

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net