diff -r 578be2adaf3e -r 307f4279f433 Adaptation/GUID-E55B4FE5-517C-5A23-8ACA-E28EE202330B.dita --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Adaptation/GUID-E55B4FE5-517C-5A23-8ACA-E28EE202330B.dita Fri Oct 15 14:32:18 2010 +0100 @@ -0,0 +1,1012 @@ + + + + + +Platform +Specific Layer ImplementationDescribes 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()

  • +
  • MachineInfo()

  • +
  • ProgramPeriodInMilliSeconds()

  • +
  • AdjustPartialRead()

  • +
  • GetBufferInfo()

  • +
  • SetBusConfigDefaults()

  • +
  • InitClockOff()

  • +
  • ASSPDisengage()

  • +
  • ASSPReset()

  • +
  • CardDetect()

  • +
  • WriteProtected()

  • +
  • DoPowerDown()

  • +
  • DoPowerUpSM()

  • +
  • InitClockOnSM()

  • +
  • IssueMMCCommandSM()

  • +

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:

    +
  • it must call DMMCStack::ReportPowerUp().

  • +
  • it returns KMMCErrNone.

  • +

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:

    +
  • Issuing +commands

  • +
  • The +state machine.

  • +
+
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()

  • +
  • PsuInfo()

  • +
  • DoSetState()

  • +
  • DoCheckVoltage()

  • +

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:

    +
  • You can use the constant KMMCAdjustableOpVoltage to +set bit 31 in TPBusPsuInfo::iVoltageSupported.

  • +
  • Set TPBusPsuInfo::iNotLockedTimeOut to +0.

  • +

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()

  • +
  • MediaState()

  • +
  • DoDoorOpen()

  • +
  • DoDoorClosed()

  • +
  • ForceMediaChange()

  • +

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()

  • +
  • NewSocket()

  • +
  • NewStack()

  • +
  • MediaChangeID()

  • +
  • NewMediaChange()

  • +
  • VccID()

  • +
  • NewVcc()

  • +
  • Init()

  • +

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:

    +
  • the stack - use DMMCStack::Init() for +this.

  • +
  • the power supply unit +- use DMMCPsu::DoCreate() for +this.

  • +
  • dealing with media change +events - use DMMCMediaChange::Create() for +this.

  • +
+
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

  • +
  • Register support for physical addresses

  • +
  • Modify the data transfer phase to handle physical memory

  • +

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:

    +
  • you must tell the platform +independent layer that you support physical addresses in your implementation +of DMMCStack::MachineInfo(). +The function takes a TMMCMachineInfo type. Set the THardwareConfig::ESupportsDMA flags +into the iFlags member of TMMCMachineInfo.

    If +you set the THardwareConfig::ESupportsDMA flag, you also +set the THardwareConfig::ESupportsDoubleBuffering flag +automatically. This flag enables double buffer support. You must alsoimplement +double buffers if you use physical addresses.

  • +
  • You must tell the platform +independent layer the maximum transfer length that you support. This +can be a limit imposed on you by the hardware. For example, if you use DMA, +the DMA controller can impose a limit. You set one of the flags listed below +into the iFlags member of TMMCMachineInfo in +your implementation of DMMCStack::MachineInfo().

    Each flag represents a maximum transfer length. The MultiMediaCard +subsystem splits a data transfer request that is bigger than the maximum into +multiple data transfers.

      +
    • THardwareConfig::EMaxTransferLength_256K

    • +
    • THardwareConfig::EMaxTransferLength_512K

    • +
    • THardwareConfig::EMaxTransferLength_1M

    • +
    • THardwareConfig::EMaxTransferLength_2M

    • +
    • THardwareConfig::EMaxTransferLength_4M

    • +
    • THardwareConfig::EMaxTransferLength_8M,

    • +
    • THardwareConfig::EMaxTransferLength_16M

    • +
    • TMMCMachineInfoV4.iFlags

    • +
  • +
  • You must tell the platform +independent layer the address scheme that the hardware uses. Mis-alignment +of memory can corrupt data. You set one of the flags shown in the list +below into the iFlags member of TMMCMachineInfo in +your implementation of DMMCStack::MachineInfo(). Each flags represents a different address scheme. You can set only one +flag. If you set more than one of these flags, the creation of the media driver +fails.

      +
    • THardwareConfig::EDma8BitAddressing

    • +
    • THardwareConfig::EDma16BitAddressing

    • +
    • THardwareConfig::EDma32BitAddressing

    • +
    • THardwareConfig::EDma64BitAddressing

    • +
  • +

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 data member TMMCCommandDesc::iDataMemoryP contains +the source or target memory address for the current data transfer command. +Use the function TMMCCommandDesc::IsPhysicalAddress() to +determine if this address is a physical address or a virtual address. If you +do not register +support for physical addresses, this function always returns EFalse.

  • +
  • You do not need to perform +virtual address to physical address translation on physical addresses.

  • +
  • you do not need to perform +DMA synchronization for physical addresses, because the local media subsystem +performs DMA synchronization for you. You need to perform DMA synchronization +for virtual addresses. DMA synchronization is performed by calls to Cache::SyncMemoryBeforeDmaRead() or Cache::SyncMemoryBeforeDmaWrite().

  • +

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); +… + } +
+
\ No newline at end of file