DMA Framework Technology

Describes the classes that the DMA Framework provides to transfer data using DMA.

The following diagram shows the general relationship between the various classes and structs that form the DMA Framework. The individual items are described in more detail below.

The DMA Software Controller

This is the TDmac object. It has two purposes:

  • It defines the main interface between the platform independent and platform specific layers.

  • it is a container for channels, descriptors and descriptor headers

The Channel Manager

The channel manager is a DmaChannelMgr object.

DmaChannelMgr is a static class defined by the platform independent layer but implemented in the platform specific layer. The functionality is used by the platform independent layer. It contains:

Descriptors

DMA controllers operating in scatter/gather mode are configured via a linked list of small data structures describing the data to be transferred. These data structures are called descriptors. (Note that the use of the term descriptor in the context of DMA should not be confused with the same term widely used in Symbian platform to refer to the family of TDesC derived classes).

The Symbian platform DMA Framework always uses descriptor data structures to store transfer-configuration-information, even if the underlying DMA controller does not support scatter/gather mode.

The following example illustrates the idea: assume that a device driver needs to transfer two disjoint blocks of memory, A and B, into another block C. Block A starts at address 1000 and is 300 bytes long. Block B starts at address 2000 and is 700 bytes long. The destination buffer C starts at address 5000 and is 1000 bytes long. Assume that the DMA descriptors are allocated in a pool starting at address 600. The following diagram shows the scatter/gather list that the device driver might create:

If the DMA controller supports the scatter/gather arrangement, then the framework uses a structure that will be specific to the hardware. This structure is defined in the platform specific layer.

If the DMA controller does not support scatter/gather, then the framework uses the generic structure SDmaPseudoDes containing the following information:

  • a set of generic flags that characterise the transfer. For example, is the source of the data memory or a peripheral; is the destination memory or peripheral; what addressing mode is to be used?

  • the source and destination location. This is in the form of the base virtual address for a memory buffer, and a 32-bit value (or cookie) for peripherals. The meaning of the 32-bit value is interpreted by the platform specific layer.

  • The number of bytes to be transferred.

  • A word that only has meaning for the platform specific layer, and passed by the client at request fragmentation time.

Descriptor headers

These are objects of type SDmaDesHdr.

A descriptor header allows additional information about a descriptor to be stored. The header is a separate object from the descriptor because it is difficult to embed additional data in a structure whose layout may be hardware-imposed.

Descriptors and descriptor headers are stored in two parallel arrays allocated at boot-time, and each descriptor is always associated with the header of same index, so that there is always a one-to-one relationship between the header and the descriptor.

In the current design, the only information in the descriptor header is a pointer, SDmaDesHdr::iNext, that is used to chain headers together on various lists. So, although the pool of headers is allocated as an array, they are almost always accessed by following the chain of pointers linking one header to the next.

Descriptors are always accessed through their associated header.

The platform independent layer never directly accesses hardware-specific descriptors. It just passes descriptor headers to the platform specific layer. However, the platform independent layer does directly manipulate the generic descriptors, SDmaPseudoDes.

Transfer Requests

A transfer request is the way in which a device driver sets up and initiates a DMA transfer, and is represented by a DDmaRequest object.

A transfer request has a number of characteristics:

  • it is always associated with exactly one channel.

  • it stores a client-provided callback function, which is invoked when the whole request completes, whether successfully or not.

  • it can be in one of four states:

    • not configured

    • idle

    • being transferred

    • pending; this state only occurs in streaming mode.

Internally, a transfer request is represented as a singly linked list of descriptor headers. Each header in the list is associated with a descriptor that specifies how to transfer one fragment of the whole request. Transfer requests have pointers to the first and last headers in the list.

When the request is idle, the header list ends with a NULL pointer. This is not always true when the request is queued (i.e. when the request is being transferred or is still pending). The following diagram shows an idle request with three fragments.

Splitting a request into fragments is useful because:

  • it insulates device drivers from the maximum transfer size supported by the underlying DMA controller.

  • the source and destination DMA buffers may not be physically contiguous and thus require fragmenting.

Both of these situations can be handled by using the generic fragmentation algorithm DDmaRequest::Fragment().

Some device drivers may have to create custom descriptors lists. For example, the USB section of the PXA250 manual describes how to build custom lists where a descriptor containing data transfer information is followed by another one poking an I/O port to issue a command to the USB controller.

To cover that case, DDmaRequest provides the DDmaRequest::ExpandDesList() member function to allocate a list of headers associated with blank descriptors whose content is then set by device drivers. Blank descriptors thus created can be accessed in the following way (this example was taken and simplified from the PXA250 USB controller driver):

TDmaChannel* channel;
TDmaChannel::SCreateInfo info;
info.x = y;
if (TDmaChannel::Open(info, channel) != KErrNone)
    ... return;
DDmaRequest* req = new DDmaRequest(*channel);
if (!req)
    ... return;
const TDmac* dmac = req->iChannel.Controller();
if (req->ExpandDesList(2) != KErrNone)
    ... return;
TDmaDesc* desc1 = static_cast<TDmaDesc*>(dmac->HdrToHwDes(*req->iFirstHdr));
TDmaDesc* desc2 = static_cast<TDmaDesc*>(dmac->HdrToHwDes(*req->iFirstHdr->iNext));
desc1->iSrcAddr  = ...;
desc1->iDestAddr = ...;
desc1->iDescAddr = ...;
desc1->iCmd      = ...;
desc2...
...

Channels

A channel is a TDmaChannel object, and there is one of these for each hardware DMA channel.

A channel can be in one of 4 states:

  • closed

  • open and idle

  • open and transferring data

  • suspended following an error.

On opening a channel, the client device driver specifies:

  • A 32-bit value (cookie) that is used by the platform specific layer to select which channel is to be opened

  • The number of descriptors that must be reserved for this channel.

  • A DFC to be used by the framework to service DMA interrupts.

A channel maintains a queue of transfer requests. If the channel is being used for one-shot transfers, the queue will always contain an idle or a transferring request. In streaming mode, the queue may contain several requests, the first one being transferred and the remaining ones pending. When a request is completely transferred, it is removed from the queue. The first request is always the one being transferred.

A transferring channel has a pointer to the header associated with the descriptor being transferred. The headers of all queued requests are linked together on one linked list.

The following diagram shows a DMA channel with a three-fragment request being transferred and a two-fragment one pending. The fragment currently being transferred is the second one of the first request.

The TDmaChannel class contains the code and data that is common to all of the channel types. The code and data for the specific channel types: single buffer, double buffer, and scatter/gather channels, are implemented in the derived classes: TDmaSbChannel, TDmaDbChannel, and TDmaSgChannel respectively.

TDmaSbChannel State Machine

For reference purposes, the following diagram shows the state machine that TDmaSbChannel implements to deal with a single buffer channel.

TDmaDbChannel State Machine

For reference purposes, the following diagram shows the state machine that TDmaDbChannel implements to deal with a double buffer channel.

TDmaSgChannel State Machine

For reference purposes, the following diagram shows the state machine that TDmaSgChannel implements to deal with a scatter/gather channel.

Streaming and Scatter/Gather DMA Controllers

When a transfer request is queued onto a channel that is already transferring data, the header descriptor for the new list of descriptors is appended to the header of the previous request on the queue. If the underlying DMA controller supports hardware descriptors, they must also be linked together.

The following diagram shows how headers and descriptors for a new request are appended to the existing ones for a DMA controller. The dashed arrows represent the new links.

Note that hardware descriptors are linked together using physical addresses, not virtual ones.

Linking hardware descriptors together is implemented in the platform specific layer. There are two issues to consider:

  • The channel may go idle before the linking is complete, which means that the data for the new request would not be transferred.

  • If the channel is transferring the last descriptor in the list, then updating the “next” field in the descriptor will have no effect because the content of the descriptor will already have been loaded in the controller registers. Again the channel would go idle without transferring the data associated with the new request.

The solutions available when porting the DMA Framework are:

  • If the DMA controller has hardware support for appending descriptors while transferring data, then there is no problem.

  • If the peripheral attached to the channel can withstand a short disruption in the transfer flow, then the channel can be suspended while the appending takes place. This should be done with interrupts disabled to minimise disruption.

  • The alternative technique is to append the new list with interrupts disabled and set a flag. When the next interrupt occurs, if the flag is set and the channel idle, then the interrupt service routine must restart the transfer.

See Channels.

Interrupt Service Routine (ISR) and DFC Issues

The platform specific layer must notify the platform independent layer when the following events occur:

  • when an error occurs.

  • when each fragment completes, for single-buffer and double-buffer controllers.

  • when the last fragment of a request completes, for scatter/gather controllers.

The ISR, as implemented in the platform specific layer, must:

  • determine which channel the interrupt is for. If the DMA controller uses dedicated interrupt lines per channel, the ASSP/variant interrupt dispatcher will do this. See Interrupt Dispatcher Tutorial.

  • determine whether the interrupt was asserted following a successful transfer completion or a failure. If the DMA controller uses different interrupt lines for completion and failure, the ASSP/variant interrupt dispatcher will do this.

  • Call TDmac::HandleIsr() in the platform specific layer. This function queues the DFC associated with the relevant channel. It takes care of the case where several interrupts occur before the DFC gets a chance to run.

The DFC updates the state of the channel and, for single and double buffer DMA controllers, configures the next fragment to transfer, if any. When all fragments making up a request have been transferred, the DFC calls the client-supplied callback associated with the completed request. The callback is also called if an error occurs during transfer.

Locking Strategy

For a given device driver, TDmaChannel and DDmaRequest instances can be accessed concurrently from the device driver thread and from a DFC queue thread. To avoid race conditions, each TDmaChannel instance has a fast mutex, the protected data member TDmaChannel::iLock. This lock is acquired when queuing a new request, cancelling requests and executing the DFC.

TDmaChannel and DDmaRequest instances are not protected against concurrent access from several client threads because this is assumed to be an exceptional case. A device driver with such need would have to implement its own locking scheme around calls to TDmaChannel and DDmaRequest member functions.

Each TDmac instance includes a fast mutex, the private member TDmac::iLock to protect descriptor reservation and allocation, because several device drivers may try to reserve or allocate descriptors concurrently.

The DmaChannelMgr static class includes a fast mutex which is used to protect channel opening as several device drivers may compete for the same channel. This lock is also used at channel closing time.