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.HThis 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 RoutineThe 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 ChangesStart 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 RoutineThis 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 RoutineThe 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 ); } }
|