Writing a Packet-Based Slave DMA Driver

< BACK  NEXT >
[oR]

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 Works

Although 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 Handler

Along with its usual duties, this PnP handler performs the following DMA preparation tasks:

  1. Locates the DMA channel used by the device. The DMA resources would normally be sent with the requesting IRP in the stack's Parameters.StartDevice.AllocatedResourcesTranslated field.

  2. The DEVICE_DESCRIPTION structure is built. IoGetDmaAdapter is invoked to identify the Adapter object associated with the device.

  3. The DMA_OBJECT pointer returned from IoGetDmaAdapter is saved in the Device Extension.

  4. The DO_DIRECT_IO bit in the Flags field of the Device object is set, causing the I/O Manager to lock user buffers in memory and create MDLs for them.

START I/O ROUTINE

Unlike 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:

  1. It calls KeFlushIoBuffers to flush data from the CPU cache out to main memory (RAM).

  2. Start I/O decides how many mapping registers to request. Initially, it calculates the number of registers needed to cover the entire user buffer. If this number turns out to be more mapping registers than the Adapter object has available, it will ask for the maximum available.

  3. Based on the number of mapping registers and the size of the user buffer, Start I/O calculates the number of bytes to transfer in the first device operation. This may be the entire buffer or it may be only the first portion of a split transfer.

  4. Next, Start I/O calls MmGetMdlVirtualAddress to recover the virtual address of the user buffer from the MDL. It stores this address in the Device Extension. Subsequent parts of the driver use this address as an offset in the MDL to set up the actual DMA transfer.

  5. Start I/O then calls AllocateAdapterChannel to request ownership of the Adapter object. If this function succeeds, the rest of the setup work is performed by the Adapter Control routine, so Start I/O simply returns control to the I/O Manager.

  6. If AllocateAdapterChannel returns an error, Start I/O fails the requesting IRP, calls IoCompleteRequest, and starts processing the next IRP.

ADAPTER CONTROL ROUTINE

The 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:

  1. It stores the value of the MapRegisterBase argument it receives in the Device Extension for subsequent use.

  2. The Adapter Control routine then calls MapTransfer to load the Adapter object's mapping registers. To make this call, it uses the buffer's virtual address and the transfer size calculated by the Start I/O routine.

  3. Next, it sends appropriate commands to the device to begin the transfer operation.

  4. Finally, the Adapter Control routine returns the value KeepObject to retain ownership of the Adapter object.

At this point, the transfer is in progress, and other code is executing in parallel until an interrupt arrives from the device.

INTERRUPT SERVICE ROUTINE

Compared 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:

  1. It issues commands as necessary to acknowledge the device and prevent it from generating additional interrupts.

  2. The ISR then stores device status (and any relevant error information) in the Device Extension.

  3. It calls IoRequestDpc to continue processing the request in the driver's DpcForIsr routine.

  4. The ISR returns the value of TRUE to indicate that it serviced the interrupt.

DpcForIsr ROUTINE

The 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:

  1. It calls FlushAdapterBuffers, a method of the Adapter object, to force any remaining data from the Adapter object's cache.

  2. The DpcForIsr routine checks the Device Extension to see if there were any errors during the operation. If so, it completes the request with an appropriate status code and length, and starts the next request.

  3. Otherwise, it decrements the count of bytes remaining by the size of the last transfer. If the whole buffer has been processed, it completes the current request and starts the next.

  4. If more data remains, the DpcForIsr routine increments the user-buffer address pointer (stored in the Device Extension) by the size of the last operation. It then calculates the number of bytes transferred in the next device operation, calls MapTransfer to reset the mapping registers, and starts the device.

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 Transfers

When 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 TRANSFER

The 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 TRANSFERS

After 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 ); } 
< 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