Writing a Packet-Based Bus Master DMA Driver

< BACK  NEXT >
[oR]

In packet-based bus master DMA, the device transfers data to or from the locked down pages of the caller's buffer, using DMA hardware that is part of the device itself. Depending on the capabilities of the device, it might be providing its own scatter/gather support as well.

The architecture of a packet-based bus master driver is almost identical to that of a driver for a slave device. The only difference is the way the driver sets up the bus master hardware. The following sections describe these differences.

Setting Up Bus Master Hardware

A bus master device is more complicated to program because Windows 2000 doesn't know how to program the device's onboard DMA controller. The most the I/O Manager can do is provide the driver with two pieces of information.

  • An address in DMA logical space, where a contiguous segment of the buffer begins

  • A count indicating the number of bytes in that segment

It then becomes the driver's responsibility to load this information into the address and length registers of the device and start the transfer.

The function that performs this work is familiar MapTransfer. If the DEVICE_DESCRIPTION supplied in the call to IoGetDmaAdapter described a bus mastering device, then MapTransfer returns a physical address that can be used to program the device's DMA address register. The physical address corresponds to the MDL and CurrentVa arguments of the call.

There is little chance that the user buffer, if it spans more than a page, resides in contiguous physical RAM. Therefore, MapTransfer returns the physical starting address and the length of the "first" block that can be transferred contiguously. In all likelihood, the length returned by MapTransfer is the length of a page no part of the user buffer is likely to be contiguous. Should two or more pages of the user buffer happen to be contiguous, then MapTransfer correctly reports such a length.

After the device transfers what it can from the contiguous buffer, it must be reprogrammed to transfer the next part of the buffer from another contiguous block. Figure 12.4 shows how this process needs to work, with the somewhat optimistic case of some pages of the user buffer actually residing in contiguous memory.

Figure 12.4. MapTransfer points to contiguous buffer segments.
graphics/12fig04.gif

Supporting bus master devices requires some changes to the driver's AdapterControl and DpcForIsr routines. The following sections contain fragments of these routines. Compare them with the corresponding routines in the packet-based slave DMA driver in the previous section of this chapter.

AdapterControl ROUTINE

Being optimistic, the AdapterControl routine asks MapTransfer to map the entire buffer at the start of the first transfer. MapTransfer reports to the driver how much contiguous memory is actually available in the first segment of the buffer.

 PHYSICAL_ADDRESS DmaAddress; PMDL pMdl = pIrp->MdlAddress; pDevExt->transferVA = (PUCHAR)      MmGetMdlVirtualAddress( pMdl );       PDevExt->transferSize = pDevExt->bytesRemaining =      MmGetMdlByteCount( pMdl );       DmaAddress =      pDevExt->pDmaAdapter->DmaOperations->           MapTransfer( pDevExt->pDmaAdapter,                        pMdl,                        pDevExt->mapRegisterBase,                        pDevExt->transferVA,                        &pDevExt->transferSize,                        pDevExt->bWriting );                         // transferSize has been reset to the maximum //      contiguous length for the first segment // WriteDmaAddress is a device-specific routine that //      programs the device's DMA address register WriteDmaAddress( pDevExt, DmaAddress.LowPart ); // Similarly, WriteDmaCount is device-specific WriteDmaCount( pDevExt, pDevExt->transferSize ); // Now the device is started StartDevice( pDevExt ); // must return indicating bus master at work return DeallocateObjectKeepRegisters; 
DpcForIsr ROUTINE

After each partial transfer, the DpcForIsr routine increments the CurrentVa pointer by the previously returned Length value. It then calls MapTransfer again with this updated pointer and asks to map all the bytes remaining in the buffer. MapTransfer returns another logical address and a new Length value indicating the size of the next contiguous buffer segment. This process continues until the whole buffer has been processed.

 PHYSICAL_ADDRESS DmaAddress; PMDL pMdl = pIrp->MdlAddress; // Clear out the adapter object buffer (if any) pDevExt->pDmaAdapter->DmaOperations->      FlushAdapterBuffers( pDevExt->pDmaAdapter,                           pMdl,                           pDevExt->mapRegisterBase,                           pDevExt->transferVA,                           pDevExt->transferSize,                           pDevExt->bWriting );                            pDevExt->bytesRemaining -= pDevExt->transferSize; if (pDevExt->bytesRemaining > 0) {      pDevExt->transferVA += pDevExt->transferSize;      pDevExt->transferSize = pDevExt->bytesRemaining;      DmaAddress =          pDevExt->pDmaAdapter->DmaOperations->               MapTransfer( pDevExt->pDmaAdapter,                            pMdl,                            pDevExt->mapRegisterBase,                            pDevExt->transferVA,                            &pDevExt->transferSize,                            pDevExt->bWriting );                                  WriteDmaAddress( pDevExt, DmaAddress.LowPart );      WriteDmaCount( pDevExt, pDevExt->transferSize );      // Now the device is re-started      StartDevice( pDevExt ); } 

Hardware with Scatter/Gather Support

Some bus master devices contain multiple pairs of address and length registers, each one describing a single contiguous buffer segment. This allows the device to perform I/O using buffers that are scattered throughout DMA address space. These multiple address and count registers are often referred to as a scatter/gather list, but they can be considered devices with their own built-in mapping registers. Yet another way to consider scatter/gather is as a device with its own private page table hardware. Figure 12.5 shows a bus master device with scatter/gather hardware.

Figure 12.5. Bus master device with scatter/gather hardware.
graphics/12fig05.gif

Before each transfer, the driver loads as many pairs of the address and count registers as there are segments in the buffer. When the device is started, it walks through the scatter/gather list entries in sequence, reading or writing each segment of the buffer and then moving on to the next. When all the list entries have been processed, the device generates an interrupt.

Building Scatter/Gather Lists with MapTransfer

Once again, MapTransfer is used to find contiguous segments of the DMA buffer. In this case, however, the driver calls it several times before each data transfer operation once for each entry in the hardware scatter/gather list. These fragments of an AdapterControl and DpcForIsr routine demonstrate the process.

AdapterControl Routine

Before the first transfer operation, the AdapterControl routine loads the hardware scatter/gather list and starts the device. The remainder of the buffer is handled by the ISR and DpcForIsr routines.

For scatter/gather devices, the state of each address/counter pair may need to be persisted somewhere, perhaps in the device extension. An array or linked list would be appropriate structures.

 PHYSICAL_ADDRESS DmaAddress; ULONG bytesLeftInBuffer; ULONG segmentSize; ULONG mapRegisterIndex; PUCHAR segmentVA; PMDL pMdl = pIrp->MdlAddress; pDevExt->transferVA =      MmGetMdlVirtualAddress( pMdl ); pDevExt->bytesRemaining =      MmGetMdlByteCount( pMdl ); pDevExt->transferSize = 0; bytesLeftInBuffer = pDevExt->bytesRemaining; segmentVA = pDevExt->transferVA; mapRegisterIndex = 0; while (mapRegisterIndex > pDevExt->mapRegisterCount &&           bytesLeftInBuffer > 0) {      // Try for the whole enchilada      segmentSize = bytesLeftInBuffer;      DmaAddress =          pDevExt->pDmaAdapter->DmaOperations->               MapTransfer( pDevExt->pDmaAdapter,                            pMdl,                            pDevExt->mapRegisterBase,                            segmentVA, &segmentSize,                            pDevExt->bWriting );      // WriteMapRegister is a device-specific method.      // It programs one pair of scatter/gather regs.      WriteMapRegister( pDevExt, mapRegisterIndex,                          DmaAddress.LowPart,                          segmentSize );      mapRegisterIndex++;      // Move on the next scatter/gather pair      pDevExt->transferSize += segmentSize;      segmentVA += segmentSize;      bytesLeftInBuffer -= segmentSize; } // Now start the device StartDevice( pDevExt ); // And indicate that scatter/gather regs are in use return DeallocateObjectKeepRegisters; 
DpcForIsr ROUTINE

After each transfer is finished, the ISR issues a DPC request. The DpcForIsr routine flushes the previous request, and if there are more bytes left to transfer, it rebuilds the scatter/gather list.

 PHYSICAL_ADDRESS DmaAddress; ULONG bytesLeftInBuffer; ULONG segmentSize; ULONG mapRegisterIndex; PUCHAR segmentVA; PMDL pMdl = pIrp->MdlAddress; // Clear out the adapter object buffer (if any) pDevExt->pDmaAdapter->DmaOperations->      FlushAdapterBuffers( pDevExt->pDmaAdapter,                           pMdl,                           pDevExt->mapRegisterBase,                           pDevExt->transferVA,                           pDevExt->transferSize,                           pDevExt->bWriting );                            pDevExt->bytesRemaining -= pDevExt->transferSize; if (pDevExt->bytesRemaining > 0) {      pDevExt->transferVA += pDevExt->transferSize;      pDevExt->transferSize = 0;      bytesLeftInBuffer = pDevExt->bytesRemaining;      segmentVA = pDevExt->transferVA;            mapRegisterIndex = 0;            while (mapRegisterIndex >                pDevExt->mapRegisterCount &&                bytesLeftInBuffer > 0) {          segmentSize = bytesLeftInBuffer;          DmaAddress =               pDevExt->pDmaAdapter->DmaOperations->                    MapTransfer(pDevExt->pDmaAdapter,                            pMdl,                            pDevExt->mapRegisterBase,                            segmentVA,                            &segmentSize,                            pDevExt->bWriting );                                      WriteMapRegister( pDevExt, mapRegisterIndex,                              DmaAddress.LowPart,                              segmentSize );          mapRegisterIndex++;                    // Move on the next scatter/gather pair          pDevExt->transferSize += segmentSize;          segmentVA += segmentSize;          bytesLeftInBuffer -= segmentSize;      }      // Then re-start the device      StartDevice( pDevExt ); } else {      // Last device operation completed transfer      // Free up the mapping registers:      pDevExt->pDmaAdapter->DmaOperations->          FreeMapRegisters(...);      // And complete the IRP      IoCompleteRequest(...);      IoStartNextPacket(...); } 
< 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