In packet-based slave DMA, the device transfers data to or from the locked down pages of the caller's buffer using a shared DMA controller on the mainboard. The system is also responsible for providing scatter/gather support. How Packet-Based Slave DMA WorksAlthough the specifics depend on the nature of the device, most packet-based slave DMA drivers conform to a very similar pattern. The following subsections describe the routines of these drivers. IRP_MN_START_DEVICE HandlerAlong with its usual duties, this PnP handler performs the following DMA preparation tasks:
START I/O ROUTINEUnlike its counterpart in a programmed I/O driver, the DMA Start I/O routine doesn't actually start the device. Instead, it requests ownership of the Adapter object and leaves the remainder of the work to the Adapter Control callback routine. Specifically, the Start I/O routine does the following:
ADAPTER CONTROL ROUTINEThe I/O Manager calls back the Adapter Control routine when the necessary Adapter resources become available. Its job is to initialize the DMA controller for the transfer and start the device itself. In essence, it is the second half of Start I/O that occurs with the Adapter object in hand. This routine does the following:
At this point, the transfer is in progress, and other code is executing in parallel until an interrupt arrives from the device. INTERRUPT SERVICE ROUTINECompared to a programmed I/O driver, the ISR in a packet-based DMA driver is not very complicated. Unless hardware limitations force the driver to split a large transfer request across several device operations, there is only a single interrupt service when the whole transfer completes. When this interrupt arrives, the ISR does the following:
DpcForIsr ROUTINEThe DpcForIsr routine is triggered by the ISR at the end of each partial data transfer operation. Its job is to start the next partial transfer (if there is one) or to complete the current request. Specifically, the DpcForIsr routine in a packet-based DMA driver does the following:
If the DpcForIsr routine started another partial transfer, the I/O Manager will return control to the driver when the device generates an interrupt. Splitting DMA TransfersWhen a packet-based DMA driver receives a buffer, it may not be able to transfer all the data in a single device operation. It could be that the Adapter object doesn't have enough mapping registers to handle the whole request at once, or there could be limitations on the device itself. In any event, the driver has to be prepared to split the request across multiple data transfer operations. There are two solutions to this problem. One is to have the driver reject any requests that it can't handle in a single I/O. With this approach, any user of the driver is responsible for breaking the request into chunks small enough to process. Of course, the driver will have to provide some mechanism for letting its clients know the maximum allowable buffer size (an IOCTL, for example). For this approach, it might make sense to write a higher-level driver that sits on top of the DMA device driver and splits the requests. This has the advantage of shielding application programs from the details of splitting the request. Another approach is to write a single, monolithic driver that accepts requests of any size and splits them into several I/O operations. This is the strategy used by the sample driver in the next section of this chapter. This second method requires maintenance of a pointer that tracks position within the user buffer as successive chunks of data are transferred. There may also be a need to maintain a count of the number of bytes left to process. The following sections explain how to initialize and update these data items during an I/O request. FIRST TRANSFERThe Start I/O routine normally sets things up for the first transfer. Initially, it tries to grab enough mapping registers to do everything in one I/O. If the Adapter object doesn't have enough mapping registers for this to work, Start I/O asks for as many as it can get and sets up the current transfer accordingly. The following code fragment shows how this is done: pDevExt->transferVA = (PUCHAR) MmGetMdlVirtualAddress( pIrp->MdlAddress ); pDevExt->bytesRemaining = MmGetMdlByteCount( pIrp->MdlAddress ); pDevExt->transferSize = pDevExt->bytesRemaining; mapRegsNeeded = ADDRESS_AND_SIZE_TO_SPAN_PAGES( pDevExt->transferVA, pDevExt->transferSize ); if ( mapRegsNeeded > pDevExt->mapRegsAvailable ) { mapRegsNeeded = pDevExt->mapRegsAvailable; pDevExt->transferSize = mapRegsNeeded * PAGE_SIZE - MmGetMdlByteOffset( pIrp->MdlAddress ); } // Note the use of the Adapter object - the DmaAdapter // pointer returned by IoGetDmaAdapter contains // function pointers for Adapter operations. pDevExt->pDmaAdapter->DmaOperations-> AllocateAdapterChannel(pDevExt->pDmaAdapter, pDevObj, mapRegsNeeded, AdapterControl, pDevExt ); ADDITIONAL TRANSFERSAfter each interrupt, the DpcForIsr checks to see if there is any data left to process. If there is, it calculates the number of mapping registers needed to transfer all the remaining bytes in a single I/O operation. If there are not enough mapping registers available, it sets up another partial transfer. The following code fragment illustrates the procedure: pDevExt->bytesRemaining -= pDevExt->transferSize; if (pDevExt->bytesRemaining > 0) { pDevExt->transferVA += pDevExt->transferSize; pDevExt->transferSize = pDevExt->bytesRemaining; mapRegsNeeded = ADDRESS_AND_SIZE_TO_SPAN_PAGES( pDevExt->transferVA, pDevExt->transferSize ); if (mapRegsNeeded > pDevExt->mapRegsAvailable ) { mapRegsNeeded = pDevExt->mapRegsAvailable; pDevExt->transferSize = mapRegsNeeded * PAGE_SIZE - BYTE_OFFSET( pDevExt->transferVA ); } pDevExt->pDmaAdapter->DmaOperations-> MapTransfer( pDevExt->pDmaAdapter, pMdl, pDevExt->mapRegisterBase, pDevExt->transferVA, transferSize, pDevExt->bWriting ); }
|