Code Example: A Packet-Based Slave DMA Driver

< BACK  NEXT >
[oR]

This example is a skeleton of a packet-based driver for a generic slave DMA device. Although it doesn't actually manage a specific kind of hard-ware, it may help in understanding how these drivers work. The completecode for this example is included on the CD that accompanies this book and on the companion website www.W2KDriverBook.com.

DRIVER.H

This excerpt from the driver-specific header file shows the changes that need to be made to the Device Extension structure.

 typedef struct _DEVICE_EXTENSION { ... PDMA_ADAPTER pDmaAdapter; ULONG mapRegisterCount; ULONG dmaChannel; // This is the "handle" assigned to the map registers // when the AdapterControl routine is called back PVOID mapRegisterBase; ULONG bytesRequested; ULONG bytesRemaining; ULONG transferSize; PUCHAR transferVA; // This flag is TRUE if writing, FALSE if reading BOOLEAN bWriting; ... } DEVICE_EXTENSION, *PDEVICE_EXTENSION; #define MAX_DMA_LENGTH 4096 

GetDmaInfo Routine

The GetDmaInfo helper routine is responsible for making the call to IoGetDmaAdapter. Primarily, this is an example of setting up the DEVICE_ DESCRIPTION structure.

 NTSTATUS GetDmaInfo( IN INTERFACE_TYPE busType,                      IN PDEVICE_OBJECT pDevObj ) {      PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)           pDevObj->DeviceExtension;                   DEVICE_DESCRIPTION dd;            // Zero out the entire structure      RtlZeroMemory( &dd, sizeof(dd) );            dd.Version = DEVICE_DESCRIPTION_VERSION1;      dd.Master = FALSE; // this is a slave device      dd.ScatterGather = FALSE;      dd.DemandMode = FALSE;      dd.AutoInitialize = FALSE;      dd.Dma32BitAddresses = FALSE;            dd.InterfaceType = busType;         // as passed in            dd.DmaChannel = pDevExt->dmaChannel;      dd.MaximumLength = MAX_DMA_LENGTH;      dd.DmaWidth = Width16Bits;      dd.DmaSpeed = Compatible;            // Compute the maximum number of mapping regs      // this device could possibly need. Since the      // transfer may not be paged aligned, add one      // to allow the max xfer size to span a page.      pDevExt->mapRegisterCount =           (MAX_DMA_LENGTH / PAGE_SIZE) + 1;                 pDevExt->pDmaAdapter =           IoGetDmaAdapter( pDevObj,                           &dd,                           &pDevExt->mapRegisterCount);                                 // If the Adapter object can't be assigned, fail      if (pDevExt->pDmaAdapter == NULL)           return STATUS_INSUFFICIENT_RESOURCES;      else           return STATUS_SUCCESS; } 

Start I/O Changes

Start I/O no longer starts the device. Instead, it sets up the DMA operation and defers to the Adapter Control routine, called back when the Adapter Channel can be allocated. Nevertheless, the DMA setup is significant work.

 VOID StartIo( IN PDEVICE_OBJECT pDevObj,                 IN PIRP pIrp ) { PIO_STACK_LOCATION pStack =      IoGetCurrentIrpStackLocation( pIrp );       PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)      pDevObj->DeviceExtension;       // The IRP holds the MDL structure, already set up by //  the I/O Manager because DO_DIRECT_IO flag is set PMDL pMdl = pIrp->MdlAddress; ULONG mapRegsNeeded; NTSTATUS status; pDevExt->bWriting = FALSE;      // assume read operation switch ( pStack->MajorFunction ) { case IRP_MJ_WRITE:      pDevExt->bWriting = TRUE;  // bad assumption case IRP_MJ_READ:      pDevExt->bytesRequested =           MmGetMdlByteCount( pMdl );      pDevExt->transferVA = (PUCHAR)           MmGetMdlVirtualAddress( pMdl );      pDevExt->bytesRemaining =      pDevExt->transferSize =           pDevExt->bytesRequested;                 mapRegsNeeded =           ADDRESS_AND_SIZE_TO_SPAN_PAGES(                 pDevExt->transferVA,                 pDevExt->transferSize );                       if (mapRegsNeeded > pDevExt->mapRegisterCount) {           mapRegsNeeded = pDevExt->mapRegisterCount;           pDevExt->transferSize =                mapRegsNeeded * PAGE_SIZE -                MmGetMdlByteOffset( pMdl ); }      status = pDevExt->pDmaAdapter->DmaOperations->                AllocateAdapterChannel(                    pDevExt->pDmaAdapter,                    pDevObj,                    mapRegsNeeded,                    AdapterControl,                    pDevExt );                          if (!NT_SUCCESS( status )) {           // fail the IRP & don't continue with it           pIrp->IoStatus.Status = status;           // Show no bytes transferred           pIrp->IoStatus.Information = 0;           IoCompleteRequest( pIrp, IO_NO_INCREMENT );           IoStartNextPacket( pDevObj, FALSE);      }      break;     // nice job - AdapterControl takes it              //                  from here on            default:           // Shouldn't be here - ditch this strange IRP           pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;           pIrp->IoStatus.Information = 0;           IoCompleteRequest( pIrp, IO_NO_INCREMENT );           IoStartNextPacket( pDevObj, FALSE );      }      } 

AdapterControl Routine

This callback routine completes the work started with Start I/O. It programs the DMA hardware and starts the device itself. It is called by the I/O Manager after the Adapter object is assigned to the device and sufficient mapping registers are available to handle the request.

 IO_ALLOCATION_ACTION AdapterControl(                          IN PDEVICE_OBJECT pDevObj,                          IN PIRP pIrp,                          IN PVOID MapRegisterBase,                          IN PVOID pContext ) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)                                    pContext;                                     // Save the handle to the mapping register set pDevExt->mapRegisterBase = MapRegisterBase; // Flush the CPU cache(s), //      if necessary on this platform... KeFlushIoBuffers( pIrp->MdlAddress,                   !pDevExt->bWriting,          // inverted                   TRUE );               // yes DMA                    pDevExt->pDmaAdapter->DmaOperations->      MapTransfer( pDevExt->pDmaAdapter,                   pIrp->MdlAddress,                   MapRegisterBase,                   pDevExt->transferVA,                   &pDevExt->transferSize,                   pDevExt->bWriting );                    // Start the device StartTransfer( pDevExt ); return KeepObject; } 

DpcForIsr Routine

The Interrupt Service Routine for a DMA device is usually straightforward. An interrupt is generated at the end of each partial transfer, or when a transfer error occurs. As usual, the ISR schedules a DPC, using IoRequestDpc. The DPC fires the registered routine, DpcForIsr.

DpcForIsr sets up the next partial transfer. If the entire transfer has completed, it marks the IRP for completion and starts the next.

 VOID DpcForIsr(IN PKDPC pDpc,                IN PDEVICE_OBJECT pDevObj,                IN PIRP pIrp,                IN PVOID pContext ) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)      pContext; ULONG mapRegsNeeded; PMDL pMdl = pIrp->MdlAddress; // Flush the Apapter buffer to system RAM or device. pDevExt->pDmaAdapter->DmaOperations-> FlushAdapterBuffers( pDevExt->pDmaAdapter,                      pMdl,                      pDevExt->mapRegisterBase,                      pDevExt->transferVA,                      pDevExt->transferSize,                      pDevExt->bWriting );                       // If the device is reporting errors, fail the IRP if (DEVICE_FAIL( pDevExt )) {      // An error occurred, the DMA channel is now free      pDevExt->pDmaAdapter->DmaOperations->           FreeAdapterChannel( pDevExt->pDmaAdapter );      pIrp->IoStatus.Status = STATUS_DEVICE_DATA_ERROR;      pIrp->IoStatus.Information =            pDevExt->bytesRequested -            pDevExt->bytesRemaining;      IoCompleteRequest( pIrp, IO_NO_INCRMENT );      IoStartNextPacket( pDevObj, FALSE); } // Device had no errors, see if another partial needed pDevExt->bytesRemaining -= pDevExt->transferSize; if (pDevExt->bytesRemaining > 0) {      // Another partial transfer needed      // Update the transferVA and try to finish it      pDevExt->transferVA += pDevExt->transferSize;      pDevExt->transferSize = pDevExt->bytesRemaining;      mapRegsNeeded =            ADDRESS_AND_SIZE_TO_SPAN_PAGES(                 pDevExt->transferVA,                 pDevExt->transferSize );      // If it still doesn't fit in one swipe,      // cut back the expectation      if (mapRegsNeeded > pDevExt->mapRegisterCount) {           mapRegsNeeded = pDevExt->mapRegisterCount;           pDevExt->transferSize =                mapRegsNeeded * PAGE_SIZE -                BYTE_OFFSET( pDevExt->transferVA );      }            // Now set up the mapping registers for another      pDevExt->pDmaAdapter->DmaOperations->           MapTransfer( pDevExt->pDmaAdapter,                        pMdl,                        pDevExt->mapRegisterBase,                        pDevExt->transferVA,                        &pDevExt->transferSize,                        pDevExt->bWriting );            // And start the device      StartTransfer( pDevExt ); } else {      // Entire transfer has now completed -      // Free the DMA channel for another device      pDevExt->pDmaAdapter->DmaOperations->           FreeAdapterChannel( pDevExt->pDmaAdapter );      // And complete the IRP in glory      pIrp->IoStatus.Status = STATUS_SUCCESS;      pIrp->IoStatus.Information =           pDevExt->bytesRequested;            // Choose a priority boost  appropriate for device      IoCompleteRequest( pIrp, IO_DISK_INCREMENT );      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