Platform Specific Layer Implementation

Describes how to implement the Platform Specific Layer of the MMC Controller.

DMMCStack derived class

This class controls access to the MultiMediaCard stack. This class has a number of pure virtual functions that need to be implemented in your Variant DLL. The diagram at MultiMediaCard controller basic structure shows the class in context.

There is one virtual function with a default implementation that needs to be overridden.

Init()

DMMCStack::Init()

The function is intended to initialize the stack, and is called during initialization of the MultiMediaCard controller Variant DLL from DMMCSocket::Init():

You will almost certainly need to provide your own implementation to perform any platform-specific MultiMediaCard stack initialization. Whatever your implementation provides, it is important that you call the base class function from within your derived version.

Return KErrNone if initialization is successful, otherwise return one of the system-wide error codes to indicate initialization failure. Note that returning a value other than KErrNone will cause the kernel to panic and to fail to boot.

You will allocate a data transfer buffer here. The MultiMediaCard media driver needs a memory buffer to perform data transfer operations. Where supported, DMA is generally used to do this, and requires physically contiguous memory. However, the media driver is created each time a card is inserted into a machine and destroyed when the card is removed, and giving the media driver the responsibility for allocating the memory buffer means that it might not always be possible to allocate physically contiguous pages for it as memory becomes fragmented over time.

The MultiMediaCard media driver uses the GetBufferInfo() function each time it is created to get a pointer to the buffer, and to get its length.

Although the MultiMediaCard media driver only expects a single buffer, it actually uses this as two separate buffers:

  • a minor buffer which must have at least enough space for the MBR (512 bytes)

  • a cache buffer to cache data blocks from the card.

The ideal size of the cache buffer depends on the characteristics of the card present at the time, and it is possible to customize the MultiMediaCard controller at the platform specific layer for a particular card.

The following example code allocates a physically contiguous buffer - a minor buffer size of one block is allocated together with a cache buffer size of eight blocks. The whole buffer is then rounded up to a whole number of memory pages.

// The constant calculations could be explicitly folded, but this illustrates
// how the values are derived.
const TUint blkSzLog2 = 9;                    
const TUint blkSz = 1 << blkSzLog2;
const TInt minorBufLen = Max(KDiskSectorSize, blkSz);

const TInt KMinBlocksInBuffer = 8;
const TInt cchBufLen = KMinBlocksInBuffer << blkSzLog2;

TInt totalBufLen = minorBufLen + cchBufLen;

// Allocate contiguous physical memory
totalBufLen = Kern::RoundToPageSize(totalBufLen);

TPhysAddr physAddr = 0;
r = Epoc::AllocPhysicalRam(totalBufLen, physAddr);
__KTRACE_OPT(KHARDWARE, Kern::Printf("mmc:ini:physical = %08x", physAddr));
if (r != KErrNone)
    {
    return r;
    }

DPlatChunkHw* bufChunk = NULL;
r = DPlatChunkHw::New(bufChunk, physAddr, totalBufLen, EMapAttrCachedWBRA|EMapAttrSupRw);

if(r != KErrNone)
    {
    if (physAddr)
        {
        Epoc::FreePhysicalRam(physAddr, totalBufLen);
        }
    return r;
    }
          
iMDBuf = reinterpret_cast<TUint8*>(bufChunk->LinearAddress());
iMDBufLen = totalBufLen;

MachineInfo()

DMMCStack::MachineInfo()

The function returns configuration information for the MultiMediaCard stack.

The function takes a reference to a TMMCMachineInfo object, and your implementation must fill the public data members of the object.

ProgramPeriodInMilliSeconds()

DMMCStack::ProgramPeriodInMilliSeconds()

When a data block is written to a card, the data is read into an internal buffer on the card and is then programmed into the payload memory. While the card is in programming mode, it cannot be read from, or written to, but it is possible to query its status using CMD13.

Immediately after a block of data is written by CIMReadWriteBlocksSM(), the MultiMediaCard controller requests the card's state using CMD13. If the card is still in the programming state, then the state machine ProgramTimerSM() launches a timer with the period returned by ProgramPeriodInMilliSeconds(). The state of the card is periodically checked until it is no longer in programming mode.

For platforms that do not provide an interrupt to indicate when programming mode is finished, ProgramPeriodInMilliSeconds() should return the interval, in milliseconds, to be used by the poll timer.

AdjustPartialRead()

DMMCStack::AdjustPartialRead()

Some cards support a partial read feature, which is indicated by the READ_BL_PARTIAL bit in the CSD register. When this is the case, it is possible to read a section within a single physical block, without having to read the entire block.

The MultiMediaCard media driver uses this feature to read small amounts of data more quickly. However, many hardware implementations impose restrictions on the granularity of the data that can be read from the card. For example, they may use a 32-bit FIFO.

This function allows you to enforce the limits imposed by the hardware.

The aStart and aEnd arguments of AdjustPartialRead() define the range on the card from which the media driver would like to read. Your implementation should return in *aPhysStart and *aPhysEnd the range that the hardware will allow to be read.

For example, to word align data, the function would be implemented using the following code:

void AdjustPartialRead(const TMMCard* aCard, TUint32 aStart, TUint32 aEnd, TUint32* aPhysStart, TUint32* aPhysEnd);
    {
    ...
    const TUint32 KWordMask = 3;
    *aPhysStart = aStart & ~KWordMask;
    *aPhysEnd = (aEnd + KWordMask) & ~KWordMask;
    ...
    }

GetBufferInfo()

DMMCStack::GetBufferInfo()

The MultiMediaCard media driver needs a memory buffer to perform data transfer operations, and this is, typically, allocated once only by the Init() function when this stack object is initialized.

The MultiMediaCard media driver is created each time a card is inserted into a machine and destroyed when the card is removed, and it uses this function, each time it is created to get a pointer to the memory buffer, and to get its length. The MultiMediaCard media driver then uses this buffer, over its lifetime, for data transfer operations.

SetBusConfigDefaults()

DMMCStack::SetBusConfigDefaults()

The function returns information about the MultiMediaCard bus configuration for this platform.

The function takes a TUint value containing the bus speed that the controller intends to use, and a reference to a TMMCBusConfig object. The implementation of this function must fill the public data members of this object. See the class reference documentation for the data members.

DMMCStack has two private data members of type TMMCStackConfig:

  • iMasterConfig

  • iConfig

The information returned by the call to SetBusConfigDefaults() is stored in iMasterConfig's iBusConfig private data member.

iMasterConfig contains the master bus configuration settings for the platform. Each time a new session is made current, the master bus configuration settings are merged with the specific bus configuration settings for that session, (as set up in the public data member DMMCSession::iConfig), and the result is stored in iConfig. It is these merged bus configuration settings that are used to configure the hardware interface. The platform specific layer can access these settings with a call to MasterDMMCStack::BusConfig().

SetBusConfigDefaults() is called at two stages in the execution of the macro CIM_UPDATE_ACQ to update the iMasterConfig object.

  • First, it is called at the start of the card initialization stage with the bus speed argument, aClock, set to the fOD rate (400kHz).

  • Second, it is called after the CSD registers for each card have been read with the bus speed argument, aClock, set to the slowest maximum transfer rate (TRAN_SPEED) reported by any of the CSD registers.

InitClockOff()

DMMCStack::InitClockOff()

Switches from identification mode of operation to data transfer mode operation.

When this function is called, the clock information in the iBusConfig member (see SetBusConfigDefaults()) will not have been updated to the new data transfer rate.

This function should, in general, just switch from open drain to push-pull bus mode, with the clock rate being changed at the start of DMMCStack::IssueMMCCommandSM(), when iBusConfig will be valid.

ASSPDisengage()

DMMCStack::ASSPDisengage()

This function is called by the platform independent layer each time a session has completed or has been aborted.

The function gives the platform specific layer the chance to free resources or disable any activities that were required to perform the session.

The implementation should not turn off the clock to the hardware interface as this will be turned off by the inactivity timer. Typically, the implementation disables DMA and interface interrupts, and forces the hardware interface into idle.

At the end of your implementation, you must add a call DMMCStack::ReportASSPDisengaged() to report to the platform independent layer that platform specific layer resources have been disengaged.

ASSPReset()

DMMCStack::ASSPReset()

This function is called by the platform independent layer when the current session is being aborted, and platform specific asynchronous activity is to be cancelled. The function may also be called by the platform specific layer as part of the DoPowerDown() implementation.

The function gives the platform specific layer the chance to stop all activities on the host stack. It will, in general, perform the same operations as ASSPDisengage() but, in addition, will turn off the clock to the hardware interface and release any requested power requirements made on the power model, i.e. release any power requirements made by InitClockOnSM().

At the end of your implementation, you must add a call DMMCStack::ReportASSPDisengaged() to report to the platform independent layer that platform specific layer resources have been disengaged.

CardDetect()

DMMCStack::CardDetect()

Implement this function to report whether a card is present in a specified card socket.

This function takes a TUint value containing the card socket that is to be queried.

WriteProtected()

DMMCStack::WriteProtected()

Implement this function to report whether a card in a specified card socket is mechanically write protected.

This function takes a TUint value containing the card socket that is to be queried.

DoPowerDown()

DMMCStack::DoPowerDown()

This function is called as part of the bus power down sequence:

  • by the power model, in power standby and power emergency standby situations

  • when a door-open event occurs

  • when the bus inactivity timer has timed out

  • if a power supply unit (PSU) voltage check fails.

The function should stop all activities on the host stack, turn off the clock to the hardware interface and release any requested power requirements made on the power model. The function is very often implemented as a call of ASSPReset().

The function should not turn off the MultiMediaCard power supply unit as this will be performed immediately afterwards by a call to the DMMCPsu::DoSetState() derived class function from the platform independent layer.

DoPowerUpSM()

DMMCStack::DoPowerUpSM()

This is a state machine function, called as a child function at the start of the CIM_UPDATE_ACQ macro state machine.

The function should perform the necessary platform specific actions associated with powering up the bus. This includes turning on the MultiMediaCard PSU. However, the hardware interface clock should not be turned on as part of this function.

If the controller has to request power resources from the power model, e.g. where a fast system clock is required all the time the bus is powered, then this state machine function can be used to wait asynchronously for this resource to become available.

If the activity performed by this function completes successfully:

The function should return KMMCErrNone if it completes successfully or one of the other TMMCErr error codes.

See the general background information on the state machine.

InitClockOnSM()

DMMCStack::InitClockOnSM()

This is a state machine function, called as part of the CIM_UPDATE_ACQ macro state machine.

The function should turn on the clock to the hardware interface. The function is so named because this clock is always first turned on at the identification mode frequency.

The function is implemented as a state machine function because it may be necessary to include a short delay after the clock has been turned on to allow it to stabilize.

If it is necessary for the MultiMediaCard controller to request any power resources from the power model on this platform, for example, requesting a necessary system clock, then it should be performed as part of this function. In some cases, it may be necessary to wait for this power resource to become available.

At the beginning of your implementation, you must add a call DMMCStack::ReportASSPEngaged() to report to the platform independent layer that platform specific layer resources have been engaged.

The function should return KMMCErrNone if it completes successfully or one of the other TMMCErr error codes.

Note:

  • the function is only called once for each invocation of the CIM_UPDATE_ACQ macro and the important thing to stress is that the interface clock is being turned on after a period when it has been off, and therefore often requires time to stabilize.

  • In the course of executing a session, the MultiMediaCard controller may switch the clock more than once between the identification mode frequency and the data transfer mode frequency, but this function only ever gets called once.

See the general background information on the state machine.

IssueMMCCommandSM()

DMMCStack::IssueMMCCommandSM()

This is a state machine function that executes a single command over the bus. The implementation of this function is an important part in the process of porting the MultiMediaCard controller.

The input parameters for the command are passed via the current command descriptor, an instance of the TMMCCommandDesc class, on the session’s command stack. The parameters contain information such as: the type of command, the response type, the command arguments, the data source/destination for data transfer commands etc. Use DMMCSession::Command() to get the current command descriptor.

Information about the command response, the number of bytes transferred etc., is passed back using the same command descriptor. Specifically, the platform independent layer relies on responses to the following commands being returned in the TMMCCommandDesc::iResponse member, in big-endian format:

  • Returns the OCR register value in response to a SEND_OP_COND command (CMD1). Note that there is no CRC with this response. Your code should ignore any CRC failure indication from the MultiMediaCard controller hardware, and just copy the response into TMMCCommandDesc::iResponse.

  • Returns the CID register value in response to an ALL_SEND_CID command (CMD2) and a SEND_CID command (CMD10).

  • Returns the CSD register value in response to a SEND_CSD command (CMD9).

  • Returns the card status in response to all R1 and R1b commands.

Note that you can use the functions TMMC::BigEndian4Bytes() and TMC::to help with conversion to big-endian format.

The function should return KMMCErrNone if it completes successfully or one of the other TMMCErr error codes.

See also background information:

DMMCPsu derived class

This class controls the MultiMediaCard socket's power supply. A class needs to be derived from this in the platform specific layer to handle the Variant specific functionality of the power supply.

This class has a number of pure virtual functions that need to be implemented in your Variant DLL. The diagram at MultiMediaCard controller basic structure shows the class in context.

There is one virtual function with an empty default implementation that needs to be overridden.

DoCreate()

DMMCPsu::DoCreate()

The function is intended to perform hardware initialization on the MultiMediaCard power supply, for example, setting port direction registers.

The function is called after creation of the DMMCPsu derived class instance, which is done during kernel initialization when the MultiMediaCard controller Variant DLL extension is loaded.

The function has a default implementation that just returns KErrNone.

Your implementation should KErrNone if the hardware initialization is successful, otherwise it should return one of the system-wide error codes to indicate initialization failure. Note that returning a value other than KErrNone will cause the kernel to panic and to fail to boot.

PsuInfo()

DMMCPsu::PsuInfo()

The function returns information about the MultiMediaCard power supply.

The function takes a reference to a TPBusPsuInfo object, and your implementation must fill the public data members of the object.

Note:

DoSetState()

DMMCPsu::DoSetState()

The function is called to turn the PSU on or off.

The requested state of the PSU depends on the TPBusPsuState value passed to it.

If the PSU supports voltage adjustment, rather than a single fixed value, then the required voltage setting is contained in the protected data member DMMCPsu::iVoltageSetting.

Note that the stack may call this function to request the power to be turned on when it is already on. You should check for this and do nothing if the power is already in the requested state.

DoCheckVoltage()

DMMCPsu::DoCheckVoltage()

The function is called to check that the voltage level of the PSU is as expected.

Checking the voltage level may be a long running operation (e.g. using an ADC), and it may not always be appropriate to perform and complete the check directly within this function.

When voltage checking is complete, either synchronously in this function, or asynchronously at some later stage, the result should be returned by calling the base class function DPBusPsuBase::ReceiveVoltageCheckResult(). Pass KErrNone to indicate a successful check; pass KErrGeneral to indicate a failed check.

Note that this function is not called as part of DMMCStack::DoPowerUpSM() processing, which means that it is not possible to use this function to introduce a delay until power is stable when the PSU is turned on. If such a delay is required while the power lines stabilize, then it will be necessary to make this function part of the DoPowerUpSM state machine.

DMMCMediaChange derived class

This class provides support for dealing with media change events, i.e. the insertion and removal of removable media.

A class needs to be derived from this in the platform specific layer to handle the Variant specific functionality.

This class has a number of pure virtual functions that need to be implemented in your Variant DLL. The diagram at MultiMediaCard controller basic structure shows the class in context.

There is one virtual function with an empty default implementation that needs to be overridden.

Create()

DMMCMediaChange::Create()

The function is intended to perform hardware initialization on the MultiMediaCard media change hardware, for example, setting port direction registers, binding to the door open interrupt etc.

The function is called after creation of the DMMCMediaChange derived class instance, which is done during kernel initialization when the MultiMediaCard controller Variant DLL extension is loaded.

The function has a default implementation that just returns KErrNone.

Your implementation should return KErrNone if the hardware initialization is successful, otherwise it should return one of the system-wide error codes to indicate initialization failure. Note that returning a value other than KErrNone will cause the kernel to panic and to fail to boot.

MediaState()

DMMCMediaChange::MediaState()

The function should return the current state of the media, i.e. whether the media door is open or closed. To indicate the state, it should return one of the TMediaState enum values.

DoDoorOpen()

DMMCMediaChange::DoDoorOpen()

This function should handle a media door open event. What needs to be done depends on how door open and door closed events are detected.

The most common pattern is where the platform hardware is capable of generating an interrupt when a door open event occurs, but cannot generate an interrupt when a door closed event occurs. In this situation, the hardware provides a readable door status that can be checked for the door closed state on a periodic basis (i.e. polling).

Assuming this, DoDoorOpen() would need to enable a tick timer to poll for the door closing. The timer callback function would check the state of the door, and if this showed a closed door, the timer would be disabled and the function DMediaChangeBase::DoorClosedService() called. This results in a call to DoDoorClosed().

Note that the door open interrupt is cleared before this function is called. The interrupt results in a call to DMediaChangeBase::DoorOpenService(), which in turn results in a call to this function DoDoorOpen().

Your implementation would necessarily be different if an open door event could not be signalled by an interrupt and a tick timer were to be used to poll for an open door status.

DoDoorClosed()

DMMCMediaChange::DoDoorClosed()

This function should handle a media door closed event. What needs to be done depends on how door open and door closed events are detected.

The most common pattern is where the platform hardware is capable of generating an interrupt when a door open event occurs, but cannot generate an interrupt when a door closed event occurs. In this situation, the hardware provides a readable door status that can be checked for the door closed state on a periodic basis (i.e. polling).

Assuming this, DoDoorClosed() would be called by the timer callback function established by DoDoorOpen() when the door status indicates a closed door; the function would need to re-enable the door open interrupt.

Your implementation would necessarily be different if a closed door event were to be signalled by an interrupt.

ForceMediaChange()

DMMCMediaChange::ForceMediaChange()

This function is called by the local media device driver to force a remount of the media device. For example to reopen a media driver in secure mode.

It should result in the same sequence of operations as would occur if a door open event had taken place; for example, disabling the door open interrupt and calling DMediaChangeBase::DoorOpenService().

TMMCardControllerInterface derived class (The factory class)

This is a class, also known as the controller factory that is responsible for deciding which peripheral bus sockets are sockets that have been designated as a MultiMediaCard sockets on this platform. It is also responsible for creating the platform-specific layer objects associated with those MultiMediaCard sockets, i.e. the DMMCSocket, DMMCStack, DMMCMediaChange, and DMMCPsu objects.

This class defines a number of pure virtual functions that need to be implemented in your Variant DLL to provide the functionality that is specific to your platform.

An instance of your TMMCardControllerInterface derived class is created in the Variant DLL entry point code.

IsMMCSocket()

TMMCardControllerInterface::IsMMCSocket()

Implement this function to indicate whether the peripheral bus socket, as identified by the specified peripheral bus socket number, is designated as a MultiMediaCard socket on this platform. It should return ETrue if the socket has been so designated, and return EFalse if not.

The function is called from TMMCardControllerInterface::Create(), which passes a socket number that can fall into the range 0 to KMaxPBusSockets.

Internally, Symbian platform reserves space for an array of pointers to DPBusSocket objects, and this function allows the platform specific layer to identify which slot is to be used for the DMMCSocket object.

GLDEF_D DPBusSocket* TheSockets[KMaxPBusSockets];

(This array is internal to Symbian platform.)

If, on this platform, a socket has been designated as a MultiMediaCard stack, then the function not only returns ETrue, but also provides the media information for that socket, by filling in the members of the SMediaDeviceInfo object passed in.

NewSocket()

TMMCardControllerInterface::NewSocket()

Implement this function to create, and return a pointer to, an instance of the DMMCSocket class. This can be a class derived from DMMCSocket, but this should rarely be necessary.

The function is called from TMMCardControllerInterface::Create().

If you create a DMMCSocket object, simply forward the peripheral bus socket number and pointer to the password store; there is no need to do anything with them.

If you create an instance of a DMMCSocket derived class, then just pass the socket number and pointer to the DMMCSocket constructor in your constructor's ctor list.

Note:

  • The socket number can fall into the range 0 to KMaxPBusSockets, and is a value for which IsMMCSocket() returned ETrue.

  • This function is only called for sockets that are associated with MultiMediaCard devices as reported by the function IsMMCSocket().

NewStack()

TMMCardControllerInterface::NewStack()

Implement this function to create, and return a pointer to, an instance of a DMMCStack derived class.

The function is called from TMMCardControllerInterface::Create().

The peripheral bus socket number and pointer to the socket object should be forwarded to the DMMCStack constructor in your class constructor's ctor list.

Note:

  • The socket number can fall into the range 0 to KMaxPBusSockets, and is a value for which IsMMCSocket() returned ETrue.

  • The socket is the object created by NewSocket().

  • This function is only called for sockets that are associated with MultiMediaCard devices as reported by the function IsMMCSocket().

MediaChangeID()

TMMCardControllerInterface::MediaChangeID()

Implement this function to report which media change object is to be associated with the specified peripheral bus socket number.

The function is called from TMMCardControllerInterface::Create().

The media change object is represented by a number, which is simply an index value that ranges from 0 to KMaxMediaChanges. Internally, Symbian platform reserves space for an array of pointers to DMediaChangeBase objects, and this function allows the platform specific layer to identify which slot is to be used for the DMMCMediaChange object that will correspond to the specified socket number.

GLDEF_D DMediaChangeBase* TheMediaChanges[KMaxMediaChanges];

(This array is internal to Symbian platform.)

Note:

  • The socket number can fall into the range 0 to KMaxPBusSockets, and is a value for which IsMMCSocket() returned ETrue.

  • This function is only called for sockets that are associated with MultiMediaCard devices as reported by the function IsMMCSocket().

NewMediaChange()

TMMCardControllerInterface::NewMediaChange()

Implement this function to create, and return a pointer to, an instance of a DMMCMediaChange derived class.

The function is called from TMMCardControllerInterface::Create().

The media change number should be forwarded to the DMMCMediaChange constructor in your class constructor's ctor list.

Note:

  • The media change number can fall into the range 0 to KMaxMediaChanges, and is the value returned by MediaChangeID().

  • This function is only called for sockets that are associated with MultiMediaCard devices as reported by the function IsMMCSocket().

VccID()

TMMCardControllerInterface::VccID()

Implement this function to report which power supply unit (PSU) object is to be associated with the specified peripheral bus socket number.

The function is called from TMMCardControllerInterface::Create().

The PSU object is represented by a number, which is simply an index value that ranges from 0 to KMaxPBusVccs. Internally, Symbian platform reserves space for an array of pointers to DPBusPsuBase objects, and this function allows the platform specific layer to identify which slot is to be used for the DMMCPsu object that will correspond to the specified socket number.

GLDEF_D DPBusPsuBase* TheVccs[KMaxPBusVccs];

(This array is internal to Symbian platform.)

Note:

  • The socket number can fall into the range 0 to KMaxPBusSockets, and is a value for which IsMMCSocket() returned ETrue.

  • This function is only called for sockets that are associated with MultiMediaCard devices as reported by the function IsMMCSocket().

NewVcc()

TMMCardControllerInterface::NewVcc()

The function should create, and return a pointer to, an instance of a DMMCPsu derived class.

The function is called from TMMCardControllerInterface::Create().

The Power Supply Unit (PSU) number and the media change number should be forwarded to the DMMCPsu constructor in your class constructor's ctor list.

Note:

  • The PSU number can fall into the range 0 to KMaxPBusVccs, and is the value returned by VccID().

  • The media change number can fall into the range 0 to KMaxMediaChanges, and is the value returned by MediaChangeID().

  • This function is only called for sockets that are associated with MultiMediaCard devices as reported by the function IsMMCSocket().

Init()

TMMCardControllerInterface::Init()

Implement this function to perform any initialization that the platform specific layer needs to do.

It should return KErrNone to indicate successful completion, or return one of the other system-wide error codes to indicate initialization failure.

Note that you should not do any initialization that is specifically associated with:

Variant DLL entry point code

The platform-specific layer as implemented in the Variant DLL is a standard kernel extension. The entry point for all standard kernel extensions is declared by a

DECLARE_STANDARD_EXTENSION()

statement, followed by the block of code that runs on entry to the DLL.

Initialization of the MultiMediaCard DLL is done at this point, and follows the pattern shown below. It needs to create an instance of your TMMCardControllerInterface derived class, followed by a call to Create() on this object. This starts a cascade of effects resulting in calls to your implementation of the TMMCardControllerInterface functions, which in turn result in the creation of the platform-specific layer objects associated with the MultiMediaCard sockets, i.e. the DMMCSocket, DMMCStack, DMMCMediaChange, and DMMCPsu objects.

DECLARE_STANDARD_EXTENSION()
//
// Extension Entry Point
//
    {
    __KTRACE_OPT(KPBUS1,Kern::Printf("Starting MMC interface"));

    TInt r=KErrNoMemory;
    TVARMMCardControllerInterface* pI=new TVARMMCardControllerInterface;
    if (pI)
        {
        r=pI->Create();
        }

    __KTRACE_OPT(KPBUS1,Kern::Printf("MMC: Returns %d",r));
    return r;
    }

In this example, TVARMMCardControllerInterface is your class derived from TMMCardControllerInterface

Direct memory addressing

To transfer data between a user side process and the media device, the Platform Specific Layer allocates a DMA-safe buffer at initialization. This buffer is allocated from physical memory. The memory in the user side process is virtual and you perform an inter-process copy of data between the user side process and the buffer allocated by the Platform Specific Layer.

Data transfer is faster if the MultiMediaCard controller knows that an address passed in an I/O request is a physical address. The File caching and Demand Paging features in the file server and kernel can pass physical addresses. A physical address avoids the need for an inter-process copy operation.

If you use a mechanism like DMA to transfer data, and your platform specific layer can deal with physical addresses, you need to make changes to the platform specific layer listed below.

Implement double buffers

If you enable double buffer behavior, the MultiMediaCard subsystem can perform multiple data transfers in a single bus transaction. The double buffer implementation performs many data transfers in a single bus transaction. The MultiMediaCard subsystem logically splits the buffer allocated by the platform specific layer into two segments. Data transfer to the media device is in progress from one segment - this is the active segment. Concurrently, the media driver can prepare data in the other segment.

To implement double buffers, you need to make changes to the platform specific layer.

Use the command descriptor functions

  • Use the function TMMCCommandDesc::BlockLength() to get the block length of the transaction. Find all direct references in the source code of the platform specific layer to TMMCCommandDesc::iBlockLength. Replace each reference with a call to TMMCCommandDesc::BlockLength()

  • Use the function TMMCCommandDesc::BufferLength() to get the length of the next active segment. Find all references to TMMCCommandDesc::iTotalLength. There are two areas in the code where this data member can be referenced:

    • code where you test the progress of the data transfer operation and set up the MMC command. Do not change this code, because TMMCCommandDesc::iTotalLength still represents the total amount of data to be transferred.

    • code where you set up the DMA controller to transfer a number of blocks of data. Replace references to TMMCCommandDesc::iTotalLength with calls to TMMCCommandDesc::BufferLength(). This describes the size of the current segment. Note that if double buffers are not enabled, the value returned by this function is the same as TMMCCommandDesc::iTotalLength.

  • You can use the function TMMCCommandDesc::IsDoubleBuffered() to determine if the current transaction uses double buffers.

Separate the command and data phases

Without double buffer behavior, a single MMC command is always associated with a single buffer into which the hardware transfers data. With double buffer behavior, multiple buffers or segments are used to transfer data within a single command. You need to separate the command and data transfer phases.

This code fragment is a simplified example of a platform specific layer that sets up the command and the data transfers in separate stages:

   
   TMMCErr DExampleMMCStack::IssueMMCCommandSM()
    {
    enum states
        {
        EStBegin=0,
        EStSetUpCommand,
        EStWaitComplete,
        EStEnd
        };
    
       TMMCCommandDesc& cmd = Command();
    
    SMF_BEGIN

       /** ...omitted for clarity */
         
    SMF_STATE(EStSetUpCommand)
                 
       /**
        * Set up the controller to issue the command.  Depending on
        * the command type, this will prepare DMA transfers and wait
        * for a response to be received before unblocking the stack.
        */
       BlockCurrentSession(KMMCBlockOnASSPFunction);
    
       SetupCommand(cmd);
        
       If(iDataTransfer)
          SetupDataTransfer(cmd);

        /**
        * Wait for all events to be received
        *  - command sent, data transferred, response received
        */
       SMF_WAITS(EStWaitComplete);
         
    SMF_STATE(EStWaitComplete)

       /**
        * Command issued, data transferred and response received.
        *  - check for and report any errors
        *
        *  - Note, functionality omitted for clarity – in practice this will
        *    check the controller status and wait for more events as appropriate.
        */
       TMMCErr err = KMMCErrNone;

       if(iResponseExpected)
          err = ExtractResponse();

       if(iDataTransfer && err == KMMCErrNone)
          err = CheckDataTransferErrors();

       if(err)
           SMF_RETURN(err);

    SMF_END
    }

If you depend on the MMC controller to signal the completion of data transfer after all blocks have been transmitted or received, change the DMA controller. Change the code to block the stack when DMA transfer starts, and unblock the stack when the current DMA transfer finishes. Do this operation while you wait for the final interrupt that signals the end of the data transfer.

The following code fragment shows how to set the KMMCBlockOnASSPFunction blocking condition before the start of DMA transfer. After DMA transfer has finished, unblock the stack in the DMA complete service routine, DmaService().


   void DExampleMMCStack::SetupDataTransfer(const TMMCCommandDesc& aCmd)
      {
      TUint8* bufPtr = reinterpret_cast<TUint8*>(aCmd.iDataMemoryP);
      TUint32 bufLen = aCmd.BufferLength();

      /** ...omitted for clarity */

      BlockCurrentSession(KMMCBlockOnASSPFunction);
      iDmaController::Start(aCmd.Direction(), bufPtr, bufLen);
      }


   void DExampleDmaController::DmaService()
      {
      /** ...omitted for clarity */

      Session().iState |= KMMCSessStateDoDFC;
      UnBlockCurrentSession(KMMCBlockOnASSPFunction, KErrNone);
      }

Implement the double buffer state machine

Update the platform specific layer to implement the double buffer state machine. You use the function DMMCSession::RequestMoreData(). The platform specific layer uses this function to tell the MMC subsystem to prepare the next segment for data transfer. You call this function when the hardware is busy performing a DMA transfer for the current segment. This allows the MMC Media Driver to copy data to/from the client process ready for the next transfer while the MMC card is transferring it’s current payload.

This function sets the static KMMCBlockOnMoreData blocking condition. The platform specific layer must use SMF_WAITS (or equivalent) to suspend the platform specific layer state machine until the media driver has processed the current segment. When finished, the command descriptor is populated with the details of the next segment to be transferred. The KMMCBlockOnMoreData block condition set by this function can be set with the KMMCBlockOnASSPFunction condition. It allows the hardware to perform useful work, (for example, transfer the current buffer to or from the card) while the media driver is busy preparing the next buffer. In this case, the platform specific layer is unblocked when both the hardware and media driver have completed their tasks.

The following code fragment shows how you do this:

TMMCErr DExampleMMCStack::IssueMMCCommandSM()
    {
    enum states
       {
       EStBegin=0,
       EStSetUpCommand,
   EStWaitDataTransfer
       EStWaitComplete,
       EStEnd
       };
    
       TMMCCommandDesc& cmd = Command();
    
    SMF_BEGIN

       /** ...omitted for clarity */
         
    SMF_STATE(EStSetUpCommand)
                 
       /**
        * Set up the controller to issue the command.  Depending on
        * the command type, this will prepare DMA transfers and wait
        * for a response to be received before unblocking the stack.
        */
       BlockCurrentSession(KMMCBlockOnASSPFunction);
    
       SetupCommand(cmd);
        
       If(iDataTransfer)
      {
          /**
       * Kick off DMA transfer for the first buffer.
           * …the stack will be blocked on KMMCBlockOnASSPFunction until DMA complete
       */
          SetupDataTransfer(cmd);
      
      /**
       * DMA is now active. Now request the Media Driver to prepare the next 
       * buffer in parallel. While active, the stack will be blocked with
       * the KMMCBlockOnMoreData blocking condition and unblocked when complete.
       */
      Session().RequestMoreData();      
      }

        /**
        * Wait for DMA and Media Driver completion.
        */
       SMF_WAITS(EStWaitDataTransfer);
         
    SMF_STATE(EStWaitDataTransfer)

       /**
        * DMA is complete and the Media Driver has prepared the next buffer.
        *  - Start the next DMA transfer and request more data from the media driver.
        */

       if(cmd.BufferLength() > 0)
          {
          /**
           * There is more data to transfer.
           * ..start DMA transfer, prepare the next buffer and wait for completion.
           */
          SetupDataTransfer(cmd);
      Session().RequestMoreData();      
          SMF_WAITS(EStWaitDataTransfer);
          }

       /**
        * There is no more data to transfer.
        * ..do whatever we need to do to wait for hardware completion
        */

       // …omitted for clarity

   SMF_WAITS(EStWaitComplete);

    SMF_STATE(EStWaitComplete)

       /**
        * Command issued, data transferred and response received.
        *  - check for and report any errors
        *
        *  - Note, functionality omitted for clarity – in practice this will
        *    check the controller status and wait for more events as appropriate.
        */
       TMMCErr err = KMMCErrNone;

       if(iResponseExpected)
          err = ExtractResponse();

       if(iDataTransfer && err == KMMCErrNone)
          err = CheckDataTransferErrors();

       if(err)
          SMF_RETURN(err);

    SMF_END
    }

Register support for double buffers with the platform independent layer

You must tell the platform independent layer that you support double buffers. Set TMMCMachineInfo::ESupportsDoubleBuffering into the TMMCMachineInfo object that you pass to DMMCStack::MachineInfo().

Choose the size of the buffer

To choose the optimum size of buffer, you must perform benchmark tests on your system. A small buffer gives you a lower command setup latency, but DMA transfers and calls to the callback function DMMCSession::RequestMoreData() occur more frequently. The time taken to set up the DMA transfers can exceed the time taken to transfer the data into or out of the active segment.

Testing

You need to do the standard E32 and F32 automated tests to check the operation of the MMC subsystem. You also need to perform the MMC specific manual test, T_MMCDRV. The test listed below performs data transfers in excess of the PSL buffer size to make sure that double buffer behavior is exercised.

   
   /**
   @SYMTestCaseID PBASE-T_MMCDRV-0558
   @SYMTestCaseDesc Test Long Read/Write Boundaries
   @SYMTestPriority High

   @SYMTestActions  

   Perform and Write/Read/Verify for the given length (L) of data across the following boundaries.
   Depending on the length, this will also perform a partial write/read at the end sector.

             --------------
            | Start    | End    |
            |--------------|
            | 0    | L    |
            | 507    | L-507    |
            | 10    | L    |
            | 0    | L-3    |
            | 27    | L-512    |
            | 0    | L-509    |
            | 3    | L-3    |
             --------------

   For each combination, the write/read/verify operations are performed in the following sequence:

a: Write and Read in single 512-byte blocks.
b: Write in a single operation (multiple blocks), Read in 512-Byte blocks.
c: Write in 512-Byte blocks, Read in a single operation (multiple-blocks).
d: Write and Read in a single operation (multiple-blocks).

   In the cases where a partial read/write operation occurs (ie - the start and/or end position
   don't lie within a sector boundary), the original contents of the start and/or end sectors are
   read and stored at the start of the test, and compared with the contents of the sectors at the
   end of the test to ensure that unwritten data within the sectors remain unaffected.
  
   @SYMTestExpectedResults All tests must pass

   @SYMPREQ1389 REQ6951 Double Buffering and SD Switch
   *
   */

The test T_MMCDRV must be performed on versions of the platform specific layer that has: double buffers enabled, double buffers disabled, and with a number of different buffer sizes (for example, from 32k to 256k).

The test cannot dynamically set the size of the buffer. You must do manual configuration of the buffer to test all configurations.

To measure performance, use T_FSYSBM, with and without double buffers enable.

Register support for physical addresses

There are three items to do:

The following code is an example implementation of DMMCStack::MachineInfo().

void DVariantMmcHwStack::MachineInfo(TMMCMachineInfoV4& aMachineInfo)
/**
 * Gets the machine info for the hardware variant
 *
 * @param aMachineInfo    Info structure to populate
 */
    {
    aMachineInfo.iTotalSockets=MMC_DRIVECOUNT;
    aMachineInfo.iTotalMediaChanges=0;          // Not used at present
    aMachineInfo.iTotalPrimarySupplies=0;        // Not used at present
    
    aMachineInfo.iBaseBusNumber=0;
    aMachineInfo.iVersion = TMMCMachineInfoV4::EVersion4;
    aMachineInfo.iMaxBusWidth = EBusWidth4;

    // Report support for Physical Addressing
    aMachineInfo.iFlags  = TMMCMachineInfo::ESupportsDMA;
    aMachineInfo.iFlags |= TMMCMachineInfo::EMaxTransferLength_1M; 
    aMachineInfo.iFlags |= TMMCMachineInfo::EDma16BitAddressing;

    // High voltage (3.6V) power class. Set to maximum = 2000mA RMS
    aMachineInfo.iHighVoltagePowerClass = TMMCMachineInfoV4::EHi200mA;

    // Low voltage (1.95V) power class. Set to maximum = 200mA RMS
    aMachineInfo.iLowVoltagePowerClass = TMMCMachineInfoV4::ELo200mA;
    
    // 52 Mhz clock supported
    aMachineInfo.iMaxClockSpeedInMhz = TMMCMachineInfoV4::EClockSpeed52Mhz;    
    }

Modify the data transfer phase to handle physical memory

The implementation of double buffers has separated the command setup and the data transfer phases. You need to change the data transfer phase to handle physical memory.

The following code is an example of the changes needed for a read operation.

TInt DMMCDmaRx::Start(const TMMCCommandDesc& aCmd)
/**
 *  Queues a DMA request after checking the buffer alignment constraints.
 *
 *  @param aCmd The command structure containing the details about the command.
 */
{
   …
   TUint flags = KDmaMemDest;

   // Check if a physical address has been provided with this request
   if(aCmd.IsPhysicalAddress())
{
       // …if so, set the appropriate flag for this DDmaRequest
flags |= KDmaPhysAddrDest;
}

   TInt retval = iRequest->Fragment(    KMMC_Buf_Dt_Reg,
                                        (TUint32)aCmd.iDataMemoryP,
                aCmd.BufferLength(), 
                                 flags,
                                 0 /* no HW Flags*/);

   if(retval != KErrNone)
Kern::Fault("MMC DMA RX Start Fragment", retval);

   …
   return KErrNone;
}
void DMMCRxDmaHelp::ChannelTransfer(const SDmaPseudoDes& aDes)
    {
…
    TPhysAddr dest;

// Don’t perform Linear to Physical translation if we 
    // have already been supplied with a Physical Address
    if (aDes.iFlags & KDmaPhysAddrDest)
        dest = (TPhysAddr) aDes.iDest;
    else
        dest = Epoc::LinearToPhysical(aDes.iDest);

    TPlatDma::SetDMARegister(KHoDMA_CDSA(iChno), dest);
…
    }