Copyright (c) Hyperion Entertainment and contributors.

DMA Resource

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

DMA Engine

Some hardware targets include a DMA engine which can be used for general purpose copying. This article describes the DMA engines available and how to use them.

Hardware Features

The Direct Memory Access (DMA) Engines found in the NXP/Freescale p5020, p5040 and p1022 System On a Chip (SoC)s, found in the AmigaONE X5000/20, X5000/40 and A1222 respectively, are quite flexible and powerful. Each of these chips contains two distinct engines with four data channels each. This provides the ability to have a total of eight DMA Channels working at once, with up to two DMA transactions actually being executed at the same time (one on each of the two DMA Engines).

Further, each of the four DMA Channels found in a DMA Engine may be individually programmed to handle either; a single transaction, a Chain of transactions, or even Lists of Chains of transactions. The DMA Engines automatically arbitrate between each DMA Channel following programmed bandwidth settings for each Channel (typically 1024 bytes).

This means that after completing a transfer of 1024 bytes (for example), the hardware will consider switching to the next Channel to allow it to move another block of data, and so on in a round-robin fashion. If all other DMA Channels on a given DMA Engine are idle when arbitration would take place, the hardware will not arbitrate and simply continue processing the transaction(s) for the Channel it is on.

fsldma.resource

The fsldma.resource API is provided automatically in the kernel for all supported machines (Currently the AmigaONE X5000/20, X5000/40 and A1222).

Example usage

#include <interfaces/fsldma.h>
// Obtain the fsldma.resource
struct fslDMAIFace *IfslDMA = IExec->OpenResource(FSLDMA_NAME);
if ( NULL != IfslDMA )
{
  uint32     lTestSize         = 1024;
  CONST_APTR pPhysicalSrcAddr  = NULL;
  APTR       pPhysicalDestAddr = NULL;
  // Allocate the Source Buffer (for DMA)
  // and set the contents to 0xB3 (just an example value)
  pPhysicalSrcAddr = (CONST_APTR)IfslDMA->DMAAllocPhysicalMemoryTags(lTestSize,
                                   FSLDMA_APM_ClearWithValue, 0xB3,
                                   TAG_END);
  if ( NULL != pPhysicalSrcAddr )
  {
    // Allocate the Destination Buffer (for DMA)
    //  - contents will by cleared by default
    pPhysicalDestAddr = IfslDMA->DMAAllocPhysicalMemory(lTestSize);
    if ( NULL != pPhysicalDestAddr )
    {
      // Call IfslDMA->DMAPhysicalCopyMem()
      // to perform the memory copy using the DMA hardware
      if ( TRUE == IfslDMA->DMAPhysicalCopyMem(pPhysicalSrcAddr,
                                               pPhysicalDestAddr,lTestSize) )
      {
        // Success - Do something with the copied data
      }
      else
      {
        // Fallback and use CPU Copy instead (or do something else)
        // Since we allocated the memory buffers using the FslDMA API,
        // which returns the Physical address to that memory, and the
        // IExec->CopyMemQuick() function expects the Virtual address,
        // we first need to obtain the Virtual address for the Physical
        // ones (using the FslDMA API again) before we can call our
        // fallback copy function.
        CONST_APTR pVirtualSrcAddr  = NULL;
        APTR       pVirtualDestAddr = NULL;
        pVirtualSrcAddr  = IfslDMA->DMAGetVirtualAddress((APTR)pPhysicalSrcAddr);
        pVirtualDestAddr = IfslDMA->DMAGetVirtualAddress(pPhysicalDestAddr);
        IExec->CopyMemQuick(pVirtualSrcAddr,pVirtualDestAddr,lTestSize);
      }
      // We *must* use DMAFreePhysicalMemory() to free the memory
      // that we allocated with DMAAllocPhysicalMemory()
      IfslDMA->DMAFreePhysicalMemory(pPhysicalDestAddr);
    }
    // We *must* use DMAFreePhysicalMemory() to free the memory
    // that we allocated with DMAAllocPhysicalMemory()
    IfslDMA->DMAFreePhysicalMemory((APTR)pPhysicalSrcAddr);
  }
}

Obtaining the fsldma.resource

Breaking this example down the first thing we do is include the interface header for the fsldma.resource and obtain the resource itself.

#include <interfaces/fsldma.h>
struct fslDMAIFace *IfslDMA = IExec->OpenResource(FSLDMA_NAME);

Allocating DMA Compliant Memory

Once we have successfully obtained the DMA resource we can directly use its API. The next step we need to do is to allocate some memory which we know is DMA compliant for use by the resource. The easiest way to accomplish this is to use the IfslDMA->DMAAllocPhysicalMemory() function. This will automatically take care of ensuring the memory that is returned is properly aligned, contiguous, cache-inhibited and coherent.

We use the Tags version of the allocate memory function first so we can allocate a block of memory for our source buffer and fill it with some test value (in this case the byte value 0xB3).

  pPhysicalSrcAddr = (CONST_APTR)IfslDMA->DMAAllocPhysicalMemoryTags(lTestSize,
                                   FSLDMA_APM_ClearWithValue, 0xB3,
                                   TAG_END);

We also need a destination buffer for our test. Since it only needs to start out being cleared (filled with zeroes), we can use the simplest form of the allocate memory function here as the allocated memory is cleared by default.

  pPhysicalDestAddr = IfslDMA->DMAAllocPhysicalMemory(lTestSize);

The core function - Copy Physical Memory

Now that we have two DMA Compliant memory buffers available we can get to the heart of the API usage and make the call to IfslDMA->DMAPhysicalCopyMem().

  IfslDMA->DMAPhysicalCopyMem(pPhysicalSrcAddr,pPhysicalDestAddr,lTestSize);

Looking at the full Example usage:Example usage source above you can see that the DMAPhysicalCopyMem() call returns a Boolean value to indicate success or failure. Therefore, TRUE will be returned after the DMA hardware had completed copying the requested data (blocking call) and FALSE will be returned if a problem occurred.

Since the memory buffers we are using were allocated using the FslDMA API and our transfer size was greater than zero and less than or equal to the total size of either buffer, (in others words a legal copy request), there is very little chance that the DMAPhysicalCopyMem() function will fail. In fact, about the only reason the DMA hardware would fail to handle a legal transaction would be if the physical RAM installed in the system was faulty.

So even though it is extremely unlikely for our DMA copy to have failed and returned FALSE, let's take a look at how we might handle the failure. In other words, falling back and using a CPU based copy function instead to complete the data move.

Since we are reverting back to using a normal system copy memory function here, we first need to obtain the Virtual Addresses to both our source and destination buffers. This is accomplished by using the DMAGetVirtualAddress() function.

  CONST_APTR pVirtualSrcAddr  = NULL;
  APTR       pVirtualDestAddr = NULL;
  pVirtualSrcAddr  = IfslDMA->DMAGetVirtualAddress((APTR)pPhysicalSrcAddr);
  pVirtualDestAddr = IfslDMA->DMAGetVirtualAddress(pPhysicalDestAddr);

Now that we have the Virtual Address equivalents to the Physical Addresses that were returned by DMAAllocPhysicalMemory(), we can proceed to call the Exec CopyMemQuick() function.

  IExec->CopyMemQuick(pVirtualSrcAddr,pVirtualDestAddr,lTestSize);