diff -r 578be2adaf3e -r 307f4279f433 Adaptation/GUID-A4C19890-2380-5498-A5F8-3B6D95CEFEF4.dita --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Adaptation/GUID-A4C19890-2380-5498-A5F8-3B6D95CEFEF4.dita Fri Oct 15 14:32:18 2010 +0100 @@ -0,0 +1,428 @@ + + + + + +PSL +ImplementationDescribes how to create the PSL implementation. +

To create a port of the DMA Framework, you must create an implementation +of the PSL for the DMA controller hardware.

+
Define and +implement PSL-specific channel classes

The choice of class to be +used for implementing DMA channels is governed by the mode of operation to +be supported:

    +
  • single-buffer channels +should use TDmaSbChannel

  • +
  • double-buffer channels +should use TDmaDbChannel

  • +
  • scatter-gather channels +should use TDmaSgChannel

  • +

These three classes are defined in dma.h.

In +addition, the PSL may need to store channel-specific data. One way of achieving +this is by deriving a PSL-specific class from one of the generic ones. For +example, the template scatter-gather implementation defines a TTemplateSgChannel class +derived from TDmaSgChannel for storing data when appending +new descriptors to a running channel:

class TTemplateSgChannel : public TDmaSgChannel + { +public: + TDmaDesc* iTmpDes; + TPhysAddr iTmpDesPhysAddr; + }; + +
+
Define and +implement the controller classes

A controller can either be a physical +controller, or a logical one depending on which DMA +mode you intend to support.

The PSL (platform-specific layer) +must define one concrete controller class per supported controller. A controller +class is derived from TDmac. The concrete class must implement +all the pure virtual functions defined in TDmac, and, optionally +the functions used to manipulate hardware descriptors.

Taking the +template port as an example, the class is defined as:

class TTemplateDmac : public TDmac + { +public: + TTemplateDmac(); + TInt Create(); +private: + // from TDmac (PIL pure virtual) + virtual void Transfer(const TDmaChannel& aChannel, const SDmaDesHdr& aHdr); + virtual void StopTransfer(const TDmaChannel& aChannel); + virtual TBool IsIdle(const TDmaChannel& aChannel); + virtual TInt MaxTransferSize(TDmaChannel& aChannel, TUint aFlags, TUint32 aPslInfo); + virtual TUint MemAlignMask(TDmaChannel& aChannel, TUint aFlags, TUint32 aPslInfo); + // from TDmac (PIL virtual) + virtual void InitHwDes(const SDmaDesHdr& aHdr, TUint32 aSrc, TUint32 aDest, TInt aCount, + TUint aFlags, TUint32 aPslInfo, TUint32 aCookie); + virtual void ChainHwDes(const SDmaDesHdr& aHdr, const SDmaDesHdr& aNextHdr); + virtual void AppendHwDes(const TDmaChannel& aChannel, const SDmaDesHdr& aLastHdr, + const SDmaDesHdr& aNewHdr); + virtual void UnlinkHwDes(const TDmaChannel& aChannel, SDmaDesHdr& aHdr); + // other + static void Isr(TAny* aThis); + inline TDmaDesc* HdrToHwDes(const SDmaDesHdr& aHdr); +private: + static const SCreateInfo KInfo; +public: + TTemplateSgChannel iChannels[KChannelCount]; + }; +

Notes:

    +
  • The array of channels +must be defined in the PSL controller class because only the PSL knows what +kind of channels are used.

  • +
  • The PSL controller class +must be allocated in the BSS section of the DMA kernel extension. The second +phase constructor must be called from the extension entry point.

  • +
  • The C++ constructor +must pass a TDmac::SCreateInfo structure to the TDmac constructor. +This structure defines what the PIL (platform-independent layer) needs to +know about the underlying DMA controller. The template TTemplateDmac constructor +just passes a TDmac::ScreateInfo structure, which defines +the layout of the (base class managed) descriptor pool to the base class constructor +through a ctor list:

    TTemplateDmac::TTemplateDmac() +// +// Constructor. +// + : TDmac(KInfo) + {} + +

    where KInfo is a TDmac::ScreateInfo type.

  • +
  • The PSL controller class +needs a second phase constructor. This is the Create() function, +which must:

      +
    1. call the second phase +constructor of the base class: TDmac::Create().

    2. +
    3. perform any hardware-specific +initialisation.

    4. +
    5. bind and enable the +DMA interrupt(s).

    6. +

    In the template port, Create() calls the base class TDmac::Create() function +and then initialises the template specific channel object members (the temporary +descriptor fields used for appending requests to live channels), and performs +platform specific initialisation for the interrupt dispatch of DMA interrupt +events:

    TInt TTemplateDmac::Create() +// +// Second phase construction. +// + { + TInt r = TDmac::Create(KInfo); // Base class Create() + if (r == KErrNone) + { + __DMA_ASSERTA(ReserveSetOfDes(KChannelCount) == KErrNone); + for (TInt i=0; i < KChannelCount; ++i) + { + TDmaDesc* pD = HdrToHwDes(*iFreeHdr); + iChannels[i].iTmpDes = pD; + iChannels[i].iTmpDesPhysAddr = DesLinToPhys(pD); + iFreeHdr = iFreeHdr->iNext; + } + r = Interrupt::Bind(EAsspIntIdDma, Isr, this); + if (r == KErrNone) + { + // TO DO: Map DMA clients (requests) to DMA channels here. + + r = Interrupt::Enable(EAsspIntIdDma); + } + } + return r; + } +
  • +
+
Implement the +channel allocator

Channel allocation is implemented in the PSL +(platform-specific layer) because this is a hardware-specific operation. There +are two basic options:

    +
  • Preallocate, at design +time, one channel per DMA-aware peripheral. This is the simplest approach, +and it should be acceptable for most Symbian platform devices because the +set of supported peripherals is closed. In this case, cookies passed by client +device drivers map uniquely to DMA channels.

  • +
  • Full dynamic allocation. +This is a simple approach too, but DMA channels are, in general, not completely +identical. For example, some channels may have greater priorities than others.

  • +

Mixed schemes are also possible, for example, client device driver +cookies could be used to select a subset of channels, and dynamic allocation +used inside this subset.

Whatever option is chosen, the PSL must provide +an implementation for the function DmaChannelMgr::Open(). +The template implementation is:

TDmaChannel* DmaChannelMgr::Open(TUint32 aOpenId) +// +// +// + { + __KTRACE_OPT(KDMA, Kern::Printf(">DmaChannelMgr::Open aOpenId=%d", aOpenId)); + + __DMA_ASSERTA(aOpenId < static_cast<TUint32>(KChannelCount)); + + TDmaChannel* pC = Controller.iChannels + aOpenId; + if (pC->IsOpened()) + pC = NULL; + else + { + pC->iController = &Controller; + pC->iPslId = aOpenId; + } + + return pC; + } +

The template DMA channel manager returns a pointer to +a DMA channel if the channel has not been previously allocated. Note that +since channels possess preset priorities, the device drivers must be aware +of which channel(s) they require DMA service from and configure the DMA controller +to route sources to allocated channels accordingly.

The platform-specific +cookies passed by client device drivers to the PSL must be defined somewhere +so that client device drivers can access them.

+
[Optionally] +implement channel cleanup

In the template PSL (platform-specific +layer), the function DmaChannelMgr::Close() is a no-operation. +This function is called by the PIL (platform-independent layer) when client +device drivers call TDmaChannel::Close(). If the PSL needs +to perform any hardware-specific operation when the channel is closed, then +the implementation of DmaChannelMgr::Close() should be updated +to reflect that.

+
Implement the +mandatory controller virtual functions

The TDmac class +contains several pure virtual functions that must be implemented by the PSL +(platform-specific layer):

    +
  • TDmac::Transfer()

  • +
  • TDmac::StopTransfer()

  • +
  • TDmac::IsIdle()

  • +

These functions start and stop transfers on a DMA channel and are +the main interface between the PIL (platform-independent layer) and the PSL. +The implementation of these functions depends on the hardware available for +performing DMA, and on the characteristics used to specify a DMA transfer:

    +
  • the source and destination +addresses

  • +
  • the burst size

  • +
  • the maximum transfer +size

  • +
  • the transfer width, +i.e. number of bits per memory access

  • +
  • the memory alignment +and endianness.

  • +

The DMA Framework manages the transfer descriptors according to the +descriptor parameter passed into the TDmac constructor +by the derived class constructor; the descriptor parameter is a TDmac::SCreateInfo structure. +The per-request transfer parameters are passed into the descriptors for each +request issued by a driver.

The +transfer function: Transfer()

This function initiates a previously +constructed request on a specific channel. This is the template implementation:

void TTemplateDmac::Transfer(const TDmaChannel& aChannel, const SDmaDesHdr& aHdr) +// +// Initiates a (previously constructed) request on a specific channel. +// + { + const TUint8 i = static_cast<TUint8>(aChannel.PslId()); + TDmaDesc* pD = HdrToHwDes(aHdr); + + __KTRACE_OPT(KDMA, Kern::Printf(">TTemplateDmac::Transfer channel=%d des=0x%08X", i, pD)); + + // TO DO (for instance): Load the first descriptor address into the DMAC and start it + // by setting the RUN bit. + (void) *pD, (void) i; + + } +

The +stop transfer function: StopTransfer()

This function requires +that the RUN mode is cleared. This is the template implementation:

void TTemplateDmac::StopTransfer(const TDmaChannel& aChannel) +// +// Stops a running channel. +// + { + const TUint8 i = static_cast<TUint8>(aChannel.PslId()); + + __KTRACE_OPT(KDMA, Kern::Printf(">TTemplateDmac::StopTransfer channel=%d", i)); + + // TO DO (for instance): Clear the RUN bit of the channel. + (void) i; + + } +

The +function: IsIdle()

IsIdle() returns the state +of a given channel. This is the template implementation:

TBool TTemplateDmac::IsIdle(const TDmaChannel& aChannel) +// +// Returns the state of a given channel. +// + { + const TUint8 i = static_cast<TUint8>(aChannel.PslId()); + + __KTRACE_OPT(KDMA, Kern::Printf(">TTemplateDmac::IsIdle channel=%d", i)); + + // TO DO (for instance): Return the state of the RUN bit of the channel. + // The return value should reflect the actual state. + (void) i; + + return ETrue; + } +
+
Implement the +non-mandatory controller virtual functions

The following auxiliary +functions are used to implement the scatter-gather transfer mode behaviour +by creating and manipulating the linked list of transfer fragment headers +that describe a given scatter-gather transaction. They are called by the TDmac base +class functions when the instance of the TDmac derived class +reports itself as being capable of scatter-gather operations.

    +
  • TDmac::InitHwDes()

  • +
  • TDmac::ChainHwDes()

  • +
  • TDmac::AppendHwDes()

  • +

First +scatter-gather support function: InitHwDes()

This is a function +for creating a scatter-gather list. From the information in the passed-in +request, the function sets up the descriptor with that fragment's:

    +
  • source and destination +address

  • +
  • size

  • +
  • driver/DMA controller +specific transfer parameters: memory/peripheral, burst size, transfer width.

  • +

This is the template implementation:

void TTemplateDmac::InitHwDes(const SDmaDesHdr& aHdr, TUint32 aSrc, TUint32 aDest, TInt aCount, + TUint aFlags, TUint32 aPslInfo, TUint32 /*aCookie*/) +// +// Sets up (from a passed in request) the descriptor with that fragment's source and destination address, +// the fragment size, and the (driver/DMA controller) specific transfer parameters (mem/peripheral, +// burst size, transfer width). +// + { + TDmaDesc* pD = HdrToHwDes(aHdr); + + __KTRACE_OPT(KDMA, Kern::Printf("TTemplateDmac::InitHwDes 0x%08X", pD)); + + // Unaligned descriptor? Error in generic layer! + __DMA_ASSERTD(IsHwDesAligned(pD)); + + pD->iSrcAddr = (aFlags & KDmaPhysAddrSrc) ? aSrc : Epoc::LinearToPhysical(aSrc); + pD->iDestAddr = (aFlags & KDmaPhysAddrDest) ? aDest : Epoc::LinearToPhysical(aDest); + pD->iCmd = DcmdReg(aCount, aFlags, aPslInfo); + pD->iDescAddr = TDmaDesc::KStopBitMask; + } + +

Second +scatter-gather support function: ChainHwDes()

If the framework +needs to fragment the client’s request, for transfer size or memory discontiguousness +reasons, then the framework calls this function. It chains hardware descriptors +together by setting the next pointer of the original descriptor to the physical +address of the descriptor to be chained. It assumes that the DMAC channel +is quiescent when called.

This is the template implementation:

void TTemplateDmac::ChainHwDes(const SDmaDesHdr& aHdr, const SDmaDesHdr& aNextHdr) +// +// Chains hardware descriptors together by setting the next pointer of the original descriptor +// to the physical address of the descriptor to be chained. +// + { + TDmaDesc* pD = HdrToHwDes(aHdr); + TDmaDesc* pN = HdrToHwDes(aNextHdr); + + __KTRACE_OPT(KDMA, Kern::Printf("TTemplateDmac::ChainHwDes des=0x%08X next des=0x%08X", pD, pN)); + + // Unaligned descriptor? Error in generic layer! + __DMA_ASSERTD(IsHwDesAligned(pD) && IsHwDesAligned(pN)); + + // TO DO: Modify pD->iCmd so that no end-of-transfer interrupt gets raised any longer. + + pD->iDescAddr = DesLinToPhys(pN); + } + +

Third +scatter-gather support function: AppendHwDes()

This function is +called by the TDmac base class when a driver request is +called for a channel that is still active, i.e. where the intent is to provide +data streaming so that the target device is constantly provided with data; +for example, an audio device playing a track. In this case, the function provided +by the derived class must:

    +
  • stop the DMAC to prevent +any corruption of the scatter-gather list while appending the new fragment +descriptor

  • +
  • append the new descriptor

  • +
  • re-enable the channel, +ideally before the target has detected the gap in service.

  • +

This is the template implementation:

void TTemplateDmac::AppendHwDes(const TDmaChannel& aChannel, const SDmaDesHdr& aLastHdr, + const SDmaDesHdr& aNewHdr) +// +// Appends a descriptor to the chain while the channel is running. +// + { + const TUint8 i = static_cast<TUint8>(aChannel.PslId()); + + TDmaDesc* pL = HdrToHwDes(aLastHdr); + TDmaDesc* pN = HdrToHwDes(aNewHdr); + + __KTRACE_OPT(KDMA, Kern::Printf(">TTemplateDmac::AppendHwDes channel=%d last des=0x%08X new des=0x%08X", + i, pL, pN)); + // Unaligned descriptor? Error in generic layer! + __DMA_ASSERTD(IsHwDesAligned(pL) && IsHwDesAligned(pN)); + + TPhysAddr newPhys = DesLinToPhys(pN); + + const TInt irq = NKern::DisableAllInterrupts(); + StopTransfer(aChannel); + + pL->iDescAddr = newPhys; + const TTemplateSgChannel& channel = static_cast<const TTemplateSgChannel&>(aChannel); + TDmaDesc* pD = channel.iTmpDes; + + // TO DO: Implement the appropriate algorithm for appending a descriptor here. + (void) *pD, (void) i; + + NKern::RestoreInterrupts(irq); + + __KTRACE_OPT(KDMA, Kern::Printf("<TTemplateDmac::AppendHwDes")); + } +
+
Implement an +interrupt service routine

The interrupt service routine needs to +do the following:

    +
  • identify the channel +that raised the interrupt

  • +
  • decide whether the interrupt +was raised because of a successful data transfer or because of an error

  • +
  • call the base class +function TDmac::HandleIsr(), which queues a DFC, or increments +the number of pending interrupts if a DFC is already queued.

  • +

This is the template implementation:

void TTemplateDmac::Isr(TAny* aThis) +// +// This ISR reads the interrupt identification and calls back into the base class +// interrupt service handler with the channel identifier and an indication whether the +// transfer completed correctly or with an error. +// + { + TTemplateDmac& me = *static_cast<TTemplateDmac*>(aThis); + + // TO DO: Implement the behaviour described above, call HandleIsr(). + + HandleIsr(me.iChannels[5], 0); // Example + + } +
+
Implement the +test support table and function

The DMA Framework comes with a +test harness that can be used to validate the port if the underlying DMA controller +supports memory to memory transfers.

The test harness needs to know +about the capabilities of the port being tested. The PSL provides the global +function DmaTestInfo() that returns a TDmaTestInfo object +that contains this information. In the template PSL, this structure is initialised +to binary zeroes. Before using the test harness, it must be initialised with +valid values.

See TDmaTestInfo, DmaTestInfo() and Validation.

+
Optimise the +performance of critical functions

You can optionally optimise critical +functions by writing them in assembler. The two candidates for an assembler +rewrite are:

    +
  • The interrupt service +routine

  • +
  • In scatter-gather mode, +the TDmac::AppendHwDes() function if it needs to suspend +a transfer when appending a new descriptor chain to an existing one.

  • +
+
Extend the +framework with platform-specific functionality

There are two ways +of extending the DMA Framework:

    +
  1. to provide platform-specific +functionality on a per-channel basis.

  2. +
  3. to provide platform-specific +functionality on a channel independent basis.

  4. +

In the first case, the PSL provides an implementation of the virtual +function TDmac::Extension(). The default implementation +just returns KErrNotSupported.

TDmaChannel::Extension() calls TDmac::Extension().

In the second case, the PSL provides an implementation of the static +function DmaChannelMgr::StaticExtension(). The template +PSL implementation just returns KErrNotSupported.

TDmaChannel::StaticExtension() calls DmaChannelMgr::StaticExtension().

+
\ No newline at end of file