diff -r 000000000000 -r a41df078684a kernel/eka/drivers/pbus/mmc/stack.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kernel/eka/drivers/pbus/mmc/stack.cpp Mon Oct 19 15:55:17 2009 +0100 @@ -0,0 +1,6797 @@ +// Copyright (c) 1999-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of the License "Eclipse Public License v1.0" +// which accompanies this distribution, and is available +// at the URL "http://www.eclipse.org/legal/epl-v10.html". +// +// Initial Contributors: +// Nokia Corporation - initial contribution. +// +// Contributors: +// +// Description: +// \e32\drivers\pbus\mmc\stack.cpp +// +// + +#include +#include +#include +#include "stackbody.h" + +#ifdef __SMP__ +TSpinLock MMCLock(TSpinLock::EOrderGenericIrqHigh0); +#endif + +#define ASSERT_NOT_ISR_CONTEXT __ASSERT_DEBUG(NKern::CurrentContext()!=NKern::EInterrupt,DMMCSocket::Panic(DMMCSocket::EMMCUnblockingInWrongContext)); + +#if !defined(__WINS__) +#define DISABLEPREEMPTION TUint irq = __SPIN_LOCK_IRQSAVE(MMCLock); +#define RESTOREPREEMPTION __SPIN_UNLOCK_IRQRESTORE(MMCLock,irq); +#else +#define DISABLEPREEMPTION +#define RESTOREPREEMPTION +#endif + +//#define ENABLE_DETAILED_SD_COMMAND_TRACE + +// default length of minor buffer - must have at least enough space for one sector +const TInt KMinMinorBufSize = 512; + +// MultiMedia Card Controller - Generic level code for controller, intermediate +// level code for media change and power supply handling + +EXPORT_C TUint TCSD::CSDField(const TUint& aTopBit, const TUint& aBottomBit) const +/** + * Extract bitfield from CSD + */ + { + const TUint indexT=KMMCCSDLength-1-aTopBit/8; + const TUint indexB=KMMCCSDLength-1-aBottomBit/8; + return(((indexT==indexB ? iData[indexT] + : (indexT+1)==indexB ? ((iData[indexT]<<8) | iData[indexT+1]) + : ((iData[indexT]<<16) | (iData[indexT+1]<<8) | iData[indexT+2]) + ) >> (aBottomBit&7)) & ((1<<(aTopBit-aBottomBit+1))-1)); + } + + +// -------- class TCSD -------- +// Raw field accessor functions are defined in mmc.inl. These functions return +// values that require extra computation, such as memory capacity. + + +EXPORT_C TUint TCSD::DeviceSize() const +/** + * + * Calculate device capacity from CSD + * + * Section 5.3, MMCA Spec 2.2 (Jan 2000) + * + * memory capacity = BLOCKNR * BLOCK_LEN + * where + * BLOCKNR = (C_SIZE + 1) * MULT; MULT = 2 ** (C_MULT_SIZE + 2); + * BLOCK_LEN = 2 ** (READ_BL_LEN) + * + * memory capacity = (C_SIZE + 1) * (2 ** (C_MULT_SIZE + 2)) * (2 ** READ_BL_LEN) + * = (C_SIZE + 1) * (2 ** (C_MULT_SIZE + 2 + READ_BL_LEN)) + * + * @return Device Capacity + */ + { + __KTRACE_OPT(KPBUS1, Kern::Printf("csd:ds:0x%x,0x%x,0x%x", ReadBlLen(), CSize(), CSizeMult())); + + const TUint blockLog = ReadBlLen(); + if( blockLog > 11 ) + return( 0 ); + + const TUint size = (CSize() + 1) << (2 + CSizeMult() + blockLog); + + if( size == 0 ) + return( 0xFFF00000 ); + + return( size ); + } + +EXPORT_C TMMCMediaTypeEnum TCSD::MediaType() const +/** + * This function makes a rough approximation if media type based on supported + * command classes (CCC). + * + * @return TMMCMediaTypeEnum describing the type of media. + */ + { + struct mediaTableEntry + { + TUint iMask; + TUint iValue; + TMMCMediaTypeEnum iMedia; + }; + + const TUint testMask = (KMMCCmdClassBlockRead|KMMCCmdClassBlockWrite|KMMCCmdClassErase|KMMCCmdClassIOMode); + static const mediaTableEntry mediaTable[] = + { + {KMMCCmdClassBasic, 0, EMultiMediaNotSupported}, + {testMask, (KMMCCmdClassBlockRead|KMMCCmdClassBlockWrite|KMMCCmdClassErase), EMultiMediaFlash}, + {testMask, KMMCCmdClassBlockRead|KMMCCmdClassBlockWrite, EMultiMediaFlash}, + {testMask, KMMCCmdClassBlockRead|KMMCCmdClassErase, EMultiMediaROM}, + {testMask, KMMCCmdClassBlockRead, EMultiMediaROM}, + {KMMCCmdClassIOMode,KMMCCmdClassIOMode, EMultiMediaIO}, + {0, 0, EMultiMediaOther} + }; + + const TUint ccc = CCC(); + const mediaTableEntry* ptr = mediaTable; + + while( (ccc & ptr->iMask) != (ptr->iValue) ) + ptr++; + + if (ptr->iMedia == EMultiMediaFlash) + { + // Further check PERM_WRITE_PROTECT and TMP_WRITE_PROTECT bits + if (PermWriteProtect() || TmpWriteProtect()) + return EMultiMediaROM; + } + + return( ptr->iMedia ); + } + +EXPORT_C TUint TCSD::ReadBlockLength() const +/** + * Calculates the read block length from the CSD. + * READ_BL_LEN is encoded as a logarithm. + * + * @return The read block length + */ + { + const TUint blockLog = ReadBlLen(); + + //SD version 2.0 or less the range is 0-11 + //MMC version 4.1 or less the range is 0-11 + //MMC version 4.2 the range is 0-14 (15 is reserved for future use) + //But we cannot differentiate among 4.x + //Hence , 0-14 is supported for 4.x + if (SpecVers() < 4) + { + if( blockLog > 11 ) + return( 0 ); + } + else + { + if(blockLog > 14) + return ( 0 ); + } + + return( 1 << blockLog ); + } + +EXPORT_C TUint TCSD::WriteBlockLength() const +/** + * Calculates the write block length from the CSD. + * WRITE_BL_LEN is encoded as a logarithm. + * + * @return The write block length + */ + { + const TUint blockLog = WriteBlLen(); + if( blockLog > 11 ) + return( 0 ); + + return( 1 << blockLog ); + } + +EXPORT_C TUint TCSD::EraseSectorSize() const +/** + * Calculates the erase sector size from the CSD. + * SECTOR_SIZE is a 5 bit value, which is one less than the number of write + * + * @return The erase sector size + */ + { + if (SpecVers() < 3) + { + // V2.2 and earlier supports erase sectors. Read sector size from CSD(46:42) - confusingly now reassigned as + // erase group size. + return( (EraseGrpSize()+1) * WriteBlockLength() ); + } + else + { + // Support for erase sectors removed from V3.1 onwards + return(0); + } + + } + +EXPORT_C TUint TCSD::EraseGroupSize() const +/** + * Calculates the erase group size from the CSD. + * ERASE_GRP_SIZE is a 5 bit value, which is one less than the number of erase + * sectors in an erase group. + * + * @return The erase group size + */ + { + if (SpecVers() < 3) + { + // For V2.2 and earlier, the erase group size is held in CSD(41:37) - confusingly now reassigned as the erase + // group multiplier. The units for this are erase sectors, so need to convert to write blocks and then bytes. + TUint erSecSizeInBytes=(EraseGrpSize()+1) * WriteBlockLength(); + return( (EraseGrpMult()+1) * erSecSizeInBytes ); + } + else + { + // For V3.1 onwards, the erase group size is determined by multiplying the erase group size - CSD(41:37) by the + // erase group multiplier - CSD(46:42)). The units for this are write blocks, so need to convert to bytes. + TUint erGrpSizeInWrBlk = (EraseGrpSize()+1) * (EraseGrpMult()+1); + return(erGrpSizeInWrBlk * WriteBlockLength()); + } + } + +EXPORT_C TUint TCSD::MinReadCurrentInMilliamps() const +/** + * Calculates the minimum read current from the CSD. + * VDD_R_CURR_MIN is a three bit value which is mapped to a number of mA. + * 0 actually maps to 0.5mA, but has been rounded up. + * + * @return The minimum read current, in Milliamps + */ + { + static const TUint8 minConsumptionTable[] = {1,1,5,10,25,35,60,100}; + return( minConsumptionTable[VDDRCurrMin()] ); + } + +EXPORT_C TUint TCSD::MinWriteCurrentInMilliamps() const +/** + * Calculates the minimum write current from the CSD. + * VDD_W_CURR_MIN is a three bit value which is mapped to a number of mA. + * + * @return The minimum write current, in Milliamps + */ + { + static const TUint8 minConsumptionTable[] = {1,1,5,10,25,35,60,100}; + return( minConsumptionTable[VDDWCurrMin()] ); + } + +EXPORT_C TUint TCSD::MaxReadCurrentInMilliamps() const +/** + * Calculates the maximum read current from the CSD. + * VDD_R_CURR_MAX is a three bit value which is mapped to a number of mA. + * 0 actually maps to 0.5mA, but has been rounded up. + * + * @return The maximum read current, in Milliamps + */ + { + static const TUint8 maxConsumptionTable[] = {1,5,10,25,35,45,80,200}; + return( maxConsumptionTable[VDDRCurrMax()] ); + } + +EXPORT_C TUint TCSD::MaxWriteCurrentInMilliamps() const +/** + * Calculates the maximum write current from the CSD. + * VDD_W_CURR_MAX is a three bit value which is mapped to a number of mA. + * + * @return The maximum write current, in Milliamps + */ + { + static const TUint8 maxConsumptionTable[] = {1,5,10,25,35,45,80,200}; + return( maxConsumptionTable[VDDWCurrMax()] ); + } + +EXPORT_C TUint TCSD::MaxTranSpeedInKilohertz() const +/** + * TRAN_SPEED is an eight bit value which encodes three fields. + * Section 5.3, MMCA Spec 2.2 (Jan 2000) + * + * 2:0 transfer rate unit values 4 to 7 are reserved. + * 6:3 time value + * + * @return Speed, in Kilohertz + */ + { + // tranRateUnits entries are all divided by ten so tranRateValues can be integers + static const TUint tranRateUnits[8] = {10,100,1000,10000,10,10,10,10}; + static const TUint8 tranRateValues[16] = {10,10,12,13,15,20,25,30,35,40,45,50,55,60,70,80}; + const TUint ts = TranSpeed(); + return( tranRateUnits[ts&7] * tranRateValues[(ts>>3)&0xF] ); + } + +// -------- class TMMCard -------- + +TMMCard::TMMCard() +: iIndex(0), iUsingSessionP(0), iFlags(0), iBusWidth(1) + { + // empty. + } + +EXPORT_C TBool TMMCard::IsReady() const +/** + * Predicate for if card is mounted and in standby/transfer/sleep state. + * + * @return ETrue if ready, EFalse otherwise. + */ + { + const TUint state = iStatus.State(); + __KTRACE_OPT(KPBUS1, Kern::Printf("=mcc:ir:%d,0x%08x", IsPresent(), state)); + return IsPresent() && (state == ECardStateStby || state == ECardStateTran || state == ECardStateSlp); + } + +EXPORT_C TBool TMMCard::IsLocked() const +/** + * Predicate for if card is locked + * + * It would be useful to check if the CSD supports the password protection + * feature. Password protection was introduced in c3.1, 05/99 and SPEC_VERS + * is encoded 0 |-> 1.0 - 1.2, 1 |-> 1.4, 3 |-> 2.2. Some cards support + * password locking but their CSD reports SPEC_VERS == 1. + * + * @return ETrue if locked, EFalse otherwise. + */ + { + if ( !IsPresent() ) + return( EFalse ); + + return( (TUint32(iStatus) & KMMCStatCardIsLocked) != 0 ); + } + +TInt64 TMMCard::DeviceSize64() const +/** + * Returns the size of the MMC card in bytes + * @return The size of the MMC card in bytes. + */ + { + const TBool highCapacity = IsHighCapacity(); + const TUint32 sectorCount = ExtendedCSD().SectorCount(); + + return ((highCapacity && sectorCount) ? (((TInt64)ExtendedCSD().SectorCount()) * 512) : (TInt64)CSD().DeviceSize()); + } + +TUint32 TMMCard::PreferredWriteGroupLength() const +/** + * Returns the write group length. Provided by the variant. + * Default implementation returns a multiple of the write block length, as indicated by the CSD. + * @return The preferred write group length. + */ + { + return(CSD().WriteBlockLength() << 5); // 16K for a standard 512byte block length + } + +TInt TMMCard::GetFormatInfo(TLDFormatInfo& /*aFormatInfo*/) const +/** + * Returns the preferred format parametersm for the partition. + * Implemented at the Variant layer. + * @return Standard Symbian OS error code. + */ + { + return KErrNotSupported; + } + +TUint32 TMMCard::MinEraseSectorSize() const +/** + * Returns the minimum erase sector size. Provided by the variant. + * Default implementation returns the erase sector size, as indicated by the CSD. + * @return The minimum erase sector size. + */ + { + return CSD().EraseSectorSize(); + } + +TUint32 TMMCard::EraseSectorSize() const +/** + * Returns the recommended erase sector size. Provided by the variant. + * Default implementation returns the erase sector size, as indicated by the CSD. + * @return The recommended erase sector size. + */ + { + return CSD().EraseSectorSize(); + } + +LOCAL_C TBool IsPowerOfTwo(TInt aNum) +// +// Returns ETrue if aNum is a power of two +// + { + return (aNum != 0 && (aNum & -aNum) == aNum); + } + +TInt TMMCard::GetEraseInfo(TMMCEraseInfo& aEraseInfo) const +/** + * Return info. on erase services for this card + * @param aEraseInfo A reference to the TMMCEraseInfo to be filled in with the erase information. + * @return Symbian OS error code. + */ + { + + // Check whether this card supports Erase Class Commands. Also, validate the erase group size + if ((CSD().CCC() & KMMCCmdClassErase) && IsPowerOfTwo(CSD().EraseGroupSize())) + { + // This card supports erase cmds. Also, all versions of MMC cards support Erase Group commands (i.e. CMD35, CMD36). + aEraseInfo.iEraseFlags=(KMMCEraseClassCmdsSupported|KMMCEraseGroupCmdsSupported); + + // Return the preferred size to be used as the unit for format operations. We need to return a sensible + // multiple of the erase group size - as calculated by the CSD. A value around 1/32th of the total disk + // size generally results in an appropriate number of individual format calls. + const TInt64 devSizeDividedBy32=(DeviceSize64()>>5); + aEraseInfo.iPreferredEraseUnitSize=CSD().EraseGroupSize(); + while (aEraseInfo.iPreferredEraseUnitSize < devSizeDividedBy32) + aEraseInfo.iPreferredEraseUnitSize<<=1; + + // Return the smallest size that can be used as the unit for erase operations. For erase group commands, this + // is the erase group size. + aEraseInfo.iMinEraseSectorSize=CSD().EraseGroupSize(); + } + else + aEraseInfo.iEraseFlags=0; + + return(KErrNone); + } + +TUint TMMCard::MaxTranSpeedInKilohertz() const +/** + * Returns the maximum supported clock rate for the card, in Kilohertz. + * @return Speed, in Kilohertz + */ + { + // Default implementation obtains the transaction speed from the CSD + TUint32 highSpeedClock = HighSpeedClock(); + return(highSpeedClock ? highSpeedClock : iCSD.MaxTranSpeedInKilohertz()); + } + + +TInt TMMCard::MaxReadBlLen() const +/** + * Returns the maximum read block length supported by the card encoded as a logarithm + * Normally this is the same as the READ_BL_LEN field in the CSD register, + * but for high capacity cards (>- 2GB) this is set to a maximum of 512 bytes, + * if possible, to try to avoid compatibility issues. + */ + { + const TInt KDefaultReadBlockLen = 9; // 2^9 = 512 bytes + const TCSD& csd = CSD(); + + TInt blkLenLog2 = csd.ReadBlLen(); + + if (blkLenLog2 > KDefaultReadBlockLen) + { + __KTRACE_OPT(KPBUS1, Kern::Printf("=mmc:mrbl %d", blkLenLog2)); + if (csd.ReadBlPartial() || CSD().SpecVers() >= 4) + { + // + // MMC System Spec 4.2 states that 512 bytes blocks are always supported, + // regardless of the state of READ_BL_PARTIAL + // + blkLenLog2 = KDefaultReadBlockLen; + __KTRACE_OPT(KPBUS1, Kern::Printf("=mmc:mrbl -> %d", blkLenLog2)); + } + } + + return blkLenLog2; + + } + +TInt TMMCard::MaxWriteBlLen() const +/** + * Returns the maximum write block length supported by the card encoded as a logarithm + * Normally this is the same as the WRITE_BL_LEN field in the CSD register, + * but for high capacity cards (>- 2GB) this is set to a maximum of 512 bytes, + * if possible, to try to avoid compatibility issues. + */ + { + const TInt KDefaultWriteBlockLen = 9; // 2^9 = 512 bytes + const TCSD& csd = CSD(); + + TInt blkLenLog2 = csd.WriteBlLen(); + + if (blkLenLog2 > KDefaultWriteBlockLen) + { + __KTRACE_OPT(KPBUS1, Kern::Printf("=mmc:mrbl %d", blkLenLog2)); + if (csd.WriteBlPartial() || CSD().SpecVers() >= 4) + { + // + // MMC System Spec 4.2 states that 512 bytes blocks are always supported, + // regardless of the state of READ_BL_PARTIAL + // + blkLenLog2 = KDefaultWriteBlockLen; + __KTRACE_OPT(KPBUS1, Kern::Printf("=mmc:mrbl -> %d", blkLenLog2)); + } + } + + return blkLenLog2; + + } + +// -------- class TMMCardArray -------- + +EXPORT_C TInt TMMCardArray::AllocCards() +/** + * Allocate TMMCard objects for iCards and iNewCardsArray. + * This function is called at bootup as part of stack allocation so there + * is no cleanup if it fails. + * + * @return KErrNone if successful, Standard Symbian OS error code otherwise. + */ + { + for (TUint i = 0; i < KMaxMMCardsPerStack; ++i) + { + // zeroing the card data used to be implicit because embedded in + // CBase-derived DMMCStack. + if ((iCards[i] = new TMMCard) == 0) + return KErrNoMemory; + iCards[i]->iUsingSessionP = 0; + if ((iNewCards[i] = new TMMCard) == 0) + return KErrNoMemory; + iNewCards[i]->iUsingSessionP = 0; + } + + return KErrNone; + } + +void TMMCardArray::InitNewCardScan() +/** + * Prepare card array for new scan. + */ + { + iNewCardsCount=0; + } + +void TMMCardArray::MoveCardAndLockRCA(TMMCard& aSrcCard,TMMCard& aDestCard,TInt aDestIndex) +/** + * Copy card object and lock RCA. + */ + { + __KTRACE_OPT(KPBUS1, Kern::Printf("=mca:mclr:%d", aDestIndex)); + + aDestCard.iCID=aSrcCard.iCID; + aDestCard.iRCA=aSrcCard.iRCA; + aDestCard.iCSD=aSrcCard.iCSD; + aDestCard.iIndex=aDestIndex; // Mark card as being present + aDestCard.iFlags=aSrcCard.iFlags; + aDestCard.iBusWidth=aSrcCard.iBusWidth; + aDestCard.iHighSpeedClock = aSrcCard.iHighSpeedClock; + + iOwningStack->iRCAPool.LockRCA(aDestCard.iRCA); + + // Now that we have transferred ownership, reset the source card + aSrcCard.iRCA = aSrcCard.iIndex = aSrcCard.iFlags = 0; + aSrcCard.iBusWidth = 1; + aSrcCard.iHighSpeedClock = 0; + + aSrcCard.iUsingSessionP = NULL; + } + +EXPORT_C void TMMCardArray::AddNewCard(const TUint8* aCID,TRCA* aNewRCA) +/** + * Found a new card to add to the array. Add it to a separate array for now + * since we need to know all the cards present before we start replacing old + * entries. + */ + { + // Store the CID in the next free slot + NewCard(iNewCardsCount).iCID = aCID; + + *aNewRCA=0; + + // Now let's look if we've seen this card before + for ( TUint i=0 ; iiMaxCardsInStack ; i++ ) + { + if ( Card(i).iCID==NewCard(iNewCardsCount).iCID ) + { + *aNewRCA=Card(i).iRCA; + NewCard(iNewCardsCount).iIndex=(i+1); + break; + } + } + + if ( *aNewRCA==0 ) + { + // Not seen this one before so get a new RCA for the card + NewCard(iNewCardsCount).iIndex=0; + __ASSERT_ALWAYS( (*aNewRCA=iOwningStack->iRCAPool.GetFreeRCA())!=0,DMMCSocket::Panic(DMMCSocket::EMMCNoFreeRCA) ); + } + + __KTRACE_OPT(KPBUS1, Kern::Printf("mca:adn: assigning new card %d rca 0x%04x", iNewCardsCount, TUint16(*aNewRCA) )); + NewCard(iNewCardsCount).iRCA=*aNewRCA; + iNewCardsCount++; + } + +TInt TMMCardArray::MergeCards(TBool aFirstPass) +/** + * This function places newly acquired cards from the new card array into free + * slots of the main card array. + * Returns KErrNotFound if not able to successfully place all the new cards. + */ + { + + __KTRACE_OPT(KPBUS1,Kern::Printf("=mca:mc:%d,%d", aFirstPass, iNewCardsCount)); + TUint i; // New card index + TUint j; // Main card index + + // Only do this on first pass. Setup any new cards which were already there + if (aFirstPass) + { + for ( i=0 ; iiMaxCardsInStack ) + return(KErrNotFound); + + // Found a free slot; move the card info there + __KTRACE_OPT(KPBUS1, Kern::Printf("-mca:freej=%d,rca=0x%04x", j, TUint16(Card(j).iRCA) )); + if ( Card(j).iRCA != 0 ) + iOwningStack->iRCAPool.UnlockRCA(Card(j).iRCA); + + __KTRACE_OPT(KPBUS1, Kern::Printf("merging new card %d to card %d dest index %d", i, j, j+1)); + MoveCardAndLockRCA(NewCard(i),Card(j),(j+1)); + } + } + return(KErrNone); + } + +void TMMCardArray::UpdateAcquisitions(TUint* aMaxClock) +/** + * Called when we have successfully stored a new set of cards in the card array. + * This performs final initialisation of the card entries and determines the + * maximum bus clock that can be employed - by checking the CSD of each card. + */ + { + + __KTRACE_OPT(KPBUS1,Kern::Printf("=mca:uda")); + iCardsPresent=0; + TUint maxClk = iOwningStack->iMultiplexedBus ? 1 : 800000; // ??? + for ( TUint i=0 ; i < (iOwningStack->iMaxCardsInStack) ; i++ ) + { + if ( Card(i).IsPresent() ) + { + // General initialisation + iCardsPresent++; + Card(i).iSetBlockLen=0; + Card(i).iLastCommand=ECmdSendStatus; + + // Check each card present to determine appropriate bus clock + TUint maxTS = iOwningStack->MaxTranSpeedInKilohertz(Card(i)); + if(iOwningStack->iMultiplexedBus) + { + if ( maxTS > maxClk ) + maxClk = maxTS; + } + else + { + if ( maxTS < maxClk ) + maxClk = maxTS; + } + } + } + // ??? Should also calculate here and return the data timeout and busy timeout + // instead of relying on ASSP defaults. + + *aMaxClock=maxClk; + } + +EXPORT_C void TMMCardArray::DeclareCardAsGone(TUint aCardNumber) +/** + * Clears up a card info object in the main card array + */ + { + + __KTRACE_OPT(KPBUS1,Kern::Printf("=mca:dcag")); + // If we thought this one was present then mark it as not present + TMMCard& card = Card(aCardNumber); + if (card.IsPresent()) + { + card.iIndex=0; // Mark card as not present + iCardsPresent--; + } + + // If this card is in use by a session then flag that card has now gone + if( card.iUsingSessionP != NULL ) + card.iUsingSessionP->iState |= KMMCSessStateCardIsGone; + + card.iUsingSessionP=NULL; + card.iSetBlockLen=0; + card.iFlags=0; // Reset 'has password' and 'write protected' bit fields + card.iHighSpeedClock=0; + card.iBusWidth=1; + } + +// return this card's index in the array or KErrNotFound if not found +TInt TMMCardArray::CardIndex(const TMMCard* aCard) + { + TInt i; + for (i = KMaxMMCardsPerStack-1; i>= 0; i--) + { + if (iCards[i] == aCard) + break; + } + return i; + } + +// -------- class TMMCCommandDesc -------- + +EXPORT_C TInt TMMCCommandDesc::Direction() const +/** + * returns -1, 0 or +1 for DT directions read, none or write respectively + */ + { + TUint dir = iSpec.iDirection; + TInt result = dir; + + if( dir == 0 ) + return( 0 ); + + if( dir & KMMCCmdDirWBitArgument ) + result = TUint(iArgument) >> (dir & KMMCCmdDirIndBitPosition); + + if( dir & KMMCCmdDirNegate ) + result = ~result; + + return( ((result&1)-1)|1 ); + } + + +TBool TMMCCommandDesc::AdjustForBlockOrByteAccess(const DMMCSession& aSession) + { +/** + * The MMC session provides both block and byte based IO methods, all of which can + * be used on both block and byte based MMC cards. This method adjusts the command + * arguments so that they match the underlying cards access mode. + * + * @return ETrue if the address is valid or successfully converted, EFalse otherwise + */ + TUint32 blockLength = BlockLength(); + + if(iTotalLength == 0 || + blockLength == 0 || + iTotalLength % KMMCardHighCapBlockSize != 0 || // always aligned on 512 bytes + blockLength % KMMCardHighCapBlockSize != 0) + { + return(EFalse); + } + + if(aSession.CardP()->IsHighCapacity()) + { + // high capacity (block-based) card + if((iFlags & KMMCCmdFlagBlockAddress) == 0) + { + // The command arguments are using byte based addressing + // - adjust to block-based addressing + if(iArgument % KMMCardHighCapBlockSize != 0) + { + // Block based media does not support misaligned access + return(EFalse); + } + + // adjust for block based access + iArgument = iArgument >> KMMCardHighCapBlockSizeLog2; + iFlags |= KMMCCmdFlagBlockAddress; + } + } + else + { + // standard (byte based) card + if((iFlags & KMMCCmdFlagBlockAddress) != 0) + { + // The command arguments are using block based addressing + // - adjust to byte-based addressing + const TUint32 maxBlocks = 4 * 1024 * ((1024 * 1024) >> KMMCardHighCapBlockSizeLog2); + + if(iArgument > maxBlocks) + { + // The address is out of range (>2G) - cannot convert + return(EFalse); + } + + // adjust for byte-based access + iArgument = iArgument << KMMCardHighCapBlockSizeLog2; + iFlags &= ~KMMCCmdFlagBlockAddress; + } + else if(iArgument % KMMCardHighCapBlockSize != 0) + { + // byte addressing, unaligned address + return(EFalse); + } + } + + return(ETrue); + } + +void TMMCCommandDesc::Dump(TUint8* aResponseP, TMMCErr aErr) + { + + Kern::Printf("------------------------------------------------------------------"); + Kern::Printf("CMD %02d(0x%08x) - ",TUint(iCommand),TUint(iArgument)); + + switch(iCommand) + { + case 0 : Kern::Printf(" | GO_IDLE_STATE"); break; + case 1 : Kern::Printf(" | SEND_OP_COND"); break; + case 2 : Kern::Printf(" | ALL_SEND_CID"); break; + case 3 : Kern::Printf(" | SET_RELATIVE_ADDR"); break; + case 4 : Kern::Printf(" | SET_DSR"); break; + case 5 : Kern::Printf(" | SLEEP/AWAKE"); break; + case 6 : Kern::Printf(" | SWITCH"); break; + case 7 : Kern::Printf(" | SELECT/DESELECT_CARD"); break; + case 8 : Kern::Printf(" | SEND_EXT_CSD"); break; + case 9 : Kern::Printf(" | SEND_CSD"); break; + case 10 : Kern::Printf(" | SEND_CID"); break; + case 11 : Kern::Printf(" | READ_DAT_UNTIL_STOP"); break; + case 12 : Kern::Printf(" | STOP_TRANSMISSION"); break; + case 13 : Kern::Printf(" | SEND_STATUS"); break; + case 14 : Kern::Printf(" | BUSTEST_R"); break; + case 15 : Kern::Printf(" | GO_INACTIVE_STATE"); break; + case 16 : Kern::Printf(" | SET_BLOCKLEN"); break; + case 17 : Kern::Printf(" | READ_SINGLE_BLOCK"); break; + case 18 : Kern::Printf(" | READ_MULTIPLE_BLOCK"); break; + case 19 : Kern::Printf(" | BUSTEST_W"); break; + case 20 : Kern::Printf(" | WRITE_DAT_UNTIL_STOP"); break; + case 23 : Kern::Printf(" | SET_BLOCK_COUNT"); break; + case 24 : Kern::Printf(" | WRITE_BLOCK"); break; + case 25 : Kern::Printf(" | WRITE_MULTIPLE_BLOCK"); break; + case 26 : Kern::Printf(" | PROGRAM_CID"); break; + case 27 : Kern::Printf(" | PROGRAM_CSD"); break; + case 28 : Kern::Printf(" | SET_WRITE_PROT"); break; + case 29 : Kern::Printf(" | CLR_WRITE_PROT"); break; + case 30 : Kern::Printf(" | SEND_WRITE_PROT"); break; + case 32 : Kern::Printf(" | ERASE_WR_BLK_START"); break; // SD + case 33 : Kern::Printf(" | ERASE_WR_BLK_END"); break; // SD + case 35 : Kern::Printf(" | ERASE_GROUP_START"); break; + case 36 : Kern::Printf(" | ERASE_GROUP_END"); break; + case 38 : Kern::Printf(" | ERASE"); break; + case 39 : Kern::Printf(" | FAST_IO"); break; + case 40 : Kern::Printf(" | GO_IRQ_STATE"); break; + case 42 : Kern::Printf(" | LOCK_UNLOCK"); break; + case 55 : Kern::Printf(" | APP_CMD"); break; + case 56 : Kern::Printf(" | GEN_CMD"); break; + default : Kern::Printf(" | *** UNKNOWN COMMAND ***"); break; + } + + switch(iSpec.iResponseType) + { + case ERespTypeNone: Kern::Printf(" RSP - NONE"); break; + case ERespTypeUnknown: Kern::Printf(" RSP - UNKNOWN"); break; + case ERespTypeR1: Kern::Printf(" RSP - R1"); break; + case ERespTypeR1B: Kern::Printf(" RSP - R1b"); break; + case ERespTypeR2: Kern::Printf(" RSP - R2"); break; + case ERespTypeR3: Kern::Printf(" RSP - R3"); break; + case ERespTypeR4: Kern::Printf(" RSP - R4"); break; + case ERespTypeR5: Kern::Printf(" RSP - R5"); break; + case ERespTypeR6: Kern::Printf(" RSP - R6"); break; + default : Kern::Printf(" RSP - *** UNKNOWN RESPONSE ***"); break; + } + + switch(iSpec.iResponseLength) + { + case 0 : break; + case 4 : Kern::Printf(" | 0x%08x", TMMC::BigEndian32(aResponseP)); break; + case 16 : Kern::Printf(" | 0x%08x 0x%08x 0x%08x 0x%08x", ((TUint32*)aResponseP)[0], ((TUint32*)aResponseP)[1], ((TUint32*)aResponseP)[2], ((TUint32*)aResponseP)[3]); break; + default : Kern::Printf(" | *** RESPONSE NOT PARSED ***"); break; + } + Kern::Printf(" ERR - 0x%08x", aErr); + if(aErr & KMMCErrResponseTimeOut) Kern::Printf(" | KMMCErrResponseTimeOut"); + if(aErr & KMMCErrDataTimeOut) Kern::Printf(" | KMMCErrDataTimeOut"); + if(aErr & KMMCErrBusyTimeOut) Kern::Printf(" | KMMCErrBusyTimeOut"); + if(aErr & KMMCErrBusTimeOut) Kern::Printf(" | KMMCErrBusTimeOut"); + if(aErr & KMMCErrTooManyCards) Kern::Printf(" | KMMCErrTooManyCards"); + if(aErr & KMMCErrResponseCRC) Kern::Printf(" | KMMCErrResponseCRC"); + if(aErr & KMMCErrDataCRC) Kern::Printf(" | KMMCErrDataCRC"); + if(aErr & KMMCErrCommandCRC) Kern::Printf(" | KMMCErrCommandCRC"); + if(aErr & KMMCErrStatus) Kern::Printf(" | KMMCErrStatus"); + if(aErr & KMMCErrNoCard) Kern::Printf(" | KMMCErrNoCard"); + if(aErr & KMMCErrBrokenLock) Kern::Printf(" | KMMCErrBrokenLock"); + if(aErr & KMMCErrPowerDown) Kern::Printf(" | KMMCErrPowerDown"); + if(aErr & KMMCErrAbort) Kern::Printf(" | KMMCErrAbort"); + if(aErr & KMMCErrStackNotReady) Kern::Printf(" | KMMCErrStackNotReady"); + if(aErr & KMMCErrNotSupported) Kern::Printf(" | KMMCErrNotSupported"); + if(aErr & KMMCErrHardware) Kern::Printf(" | KMMCErrHardware"); + if(aErr & KMMCErrBusInconsistent) Kern::Printf(" | KMMCErrBusInconsistent"); + if(aErr & KMMCErrBypass) Kern::Printf(" | KMMCErrBypass"); + if(aErr & KMMCErrInitContext) Kern::Printf(" | KMMCErrInitContext"); + if(aErr & KMMCErrArgument) Kern::Printf(" | KMMCErrArgument"); + if(aErr & KMMCErrSingleBlock) Kern::Printf(" | KMMCErrSingleBlock"); + if(aErr & KMMCErrUpdPswd) Kern::Printf(" | KMMCErrUpdPswd"); + if(aErr & KMMCErrLocked) Kern::Printf(" | KMMCErrLocked"); + if(aErr & KMMCErrNotFound) Kern::Printf(" | KMMCErrNotFound"); + if(aErr & KMMCErrAlreadyExists) Kern::Printf(" | KMMCErrAlreadyExists"); + if(aErr & KMMCErrGeneral) Kern::Printf(" | KMMCErrGeneral"); + + + if(iSpec.iResponseType == ERespTypeR1 || iSpec.iResponseType == ERespTypeR1B) + { + const TUint32 stat = TMMC::BigEndian32(aResponseP); + + Kern::Printf(" STAT - 0x%08x", stat); + if(stat & KMMCStatAppCmd) Kern::Printf(" | KMMCStatAppCmd"); + if(stat & KMMCStatSwitchError) Kern::Printf(" | KMMCStatSwitchError"); + if(stat & KMMCStatReadyForData) Kern::Printf(" | KMMCStatReadyForData"); + if(stat & KMMCStatCurrentStateMask){ Kern::Printf(" | KMMCStatCurrentStateMask"); + const TMMCardStateEnum cardState = (TMMCardStateEnum)(stat & KMMCStatCurrentStateMask); + switch (cardState){ + case ECardStateIdle : Kern::Printf(" | ECardStateIdle"); break; + case ECardStateReady : Kern::Printf(" | ECardStateReady"); break; + case ECardStateIdent : Kern::Printf(" | ECardStateIdent"); break; + case ECardStateStby : Kern::Printf(" | ECardStateStby"); break; + case ECardStateTran : Kern::Printf(" | ECardStateTran"); break; + case ECardStateData : Kern::Printf(" | ECardStateData"); break; + case ECardStateRcv : Kern::Printf(" | ECardStateRcv"); break; + case ECardStatePrg : Kern::Printf(" | ECardStatePrg"); break; + case ECardStateDis : Kern::Printf(" | ECardStateDis"); break; + case ECardStateBtst : Kern::Printf(" | ECardStateBtst"); break; + case ECardStateSlp : Kern::Printf(" | ECardStateSlp"); break; + default : Kern::Printf(" | ECardStateUnknown"); break; + } + } + if(stat & KMMCStatEraseReset) Kern::Printf(" | KMMCStatEraseReset"); + if(stat & KMMCStatCardECCDisabled) Kern::Printf(" | KMMCStatCardECCDisabled"); + if(stat & KMMCStatWPEraseSkip) Kern::Printf(" | KMMCStatWPEraseSkip"); + if(stat & KMMCStatErrCSDOverwrite) Kern::Printf(" | KMMCStatErrCSDOverwrite"); + if(stat & KMMCStatErrOverrun) Kern::Printf(" | KMMCStatErrOverrun"); + if(stat & KMMCStatErrUnderrun) Kern::Printf(" | KMMCStatErrUnderrun"); + if(stat & KMMCStatErrUnknown) Kern::Printf(" | KMMCStatErrUnknown"); + if(stat & KMMCStatErrCCError) Kern::Printf(" | KMMCStatErrCCError"); + if(stat & KMMCStatErrCardECCFailed) Kern::Printf(" | KMMCStatErrCardECCFailed"); + if(stat & KMMCStatErrIllegalCommand) Kern::Printf(" | KMMCStatErrIllegalCommand"); + if(stat & KMMCStatErrComCRCError) Kern::Printf(" | KMMCStatErrComCRCError"); + if(stat & KMMCStatErrLockUnlock) Kern::Printf(" | KMMCStatErrLockUnlock"); + if(stat & KMMCStatCardIsLocked) Kern::Printf(" | KMMCStatCardIsLocked"); + if(stat & KMMCStatErrWPViolation) Kern::Printf(" | KMMCStatErrWPViolation"); + if(stat & KMMCStatErrEraseParam) Kern::Printf(" | KMMCStatErrEraseParam"); + if(stat & KMMCStatErrEraseSeqError) Kern::Printf(" | KMMCStatErrEraseSeqError"); + if(stat & KMMCStatErrBlockLenError) Kern::Printf(" | KMMCStatErrBlockLenError"); + if(stat & KMMCStatErrAddressError) Kern::Printf(" | KMMCStatErrAddressError"); + if(stat & KMMCStatErrOutOfRange) Kern::Printf(" | KMMCStatErrOutOfRange"); + } + + Kern::Printf(" -----------------------------------------------"); + } + +// -------- class TMMCRCAPool -------- + +TRCA TMMCRCAPool::GetFreeRCA() +/** + * Returns a free RCA number from the pool or zero if none is available + */ + { + TUint32 seekm = (iPool | iLocked) + 1; + iPool |= (seekm & ~iLocked); + + if( (seekm & 0xFFFFFFFF) == 0 ) + return( 0 ); + + TUint16 pos = 1; + + if ((seekm & 0xFFFF) == 0) { seekm >>= 16; pos = 17; } + if ((seekm & 0xFF) == 0) { seekm >>= 8; pos += 8; } + if ((seekm & 0xF) == 0) { seekm >>= 4; pos += 4; } + if ((seekm & 0x3) == 0) { seekm >>= 2; pos += 2; } + if ((seekm & 0x1) == 0) pos++; + + // Multiply return value by 257 so that 1 is never returned. (0x0001 is the default RCA value.) + // The RCA integer value is divided by 257 in LockRCA() and UnlockRCA() to compensate + // for this adjustment. These functions are only ever called in this file with the iRCA + // field of a TMMCard object, and not with arbitrary values. + // The iRCA field itself is only assigned values from iNewCards[] or zero. iNewCards + // in turn is fed values from this function, in DMMCStack::CIMUpdateAcqSM() / EStSendCIDIssued. + + return TUint16(pos << 8 | pos); + } + + + +// -------- class TMMCSessRing -------- + +TMMCSessRing::TMMCSessRing() +/** + * Constructor + */ + : iPMark(NULL),iPoint(NULL),iPrevP(NULL),iSize(0) + {} + + +void TMMCSessRing::Erase() +/** + * Erases all the ring content + */ + {iPMark = iPoint = iPrevP = NULL; iSize = 0;} + + +DMMCSession* TMMCSessRing::operator++(TInt) +/** + * Post increment of Point + */ + { + if( iPoint == NULL ) + return( NULL ); + + if( (iPrevP=iPoint) == iPMark ) + iPoint = NULL; + else + iPoint = iPoint->iLinkP; + + return( iPrevP ); + } + + +TBool TMMCSessRing::Point(DMMCSession* aSessP) +/** + * Finds aSessP and sets Point to that position + */ + { + Point(); + + while( iPoint != NULL ) + if( iPoint == aSessP ) + return( ETrue ); + else + this->operator++(0); + + return( EFalse ); + } + +void TMMCSessRing::Add(DMMCSession* aSessP) +/** + * Inserts aSessP before Marker. Point is moved into the Marker position. + */ + { + if( iSize == 0 ) + { + iPMark = iPrevP = iPoint = aSessP; + aSessP->iLinkP = aSessP; + iSize = 1; + return; + } + + iPoint = iPMark->iLinkP; + iPMark->iLinkP = aSessP; + aSessP->iLinkP = iPoint; + iPMark = iPrevP = aSessP; + iSize++; + } + + +void TMMCSessRing::Add(TMMCSessRing& aRing) +/** + * Inserts aRing before Marker. Point is moved into the Marker position. + * aRing Marker becomes the fisrt inserted element. + * Erases aRing. + */ + { + Point(); + + if( aRing.iSize == 0 ) + return; + + if( iSize == 0 ) + { + iPrevP = iPMark = aRing.iPMark; + iPoint = iPrevP->iLinkP; + iSize = aRing.iSize; + } + else + { + iPrevP->iLinkP = aRing.iPMark->iLinkP; + iPMark = iPrevP = aRing.iPMark; + iPrevP->iLinkP = iPoint; + iSize += aRing.iSize; + } + + aRing.Erase(); + } + +DMMCSession* TMMCSessRing::Remove() +/** + * Removes an element pointed to by Point. + * Point (and possibly Marker) move forward as in operator++ + */ + { + DMMCSession* remS = iPrevP; + + if( iSize < 2 ) + Erase(); + else + { + remS = remS->iLinkP; + iPrevP->iLinkP = remS->iLinkP; + iSize--; + + if( iPoint != NULL ) + iPoint = iPrevP->iLinkP; + + if( iPMark == remS ) + { + iPMark = iPrevP; + iPoint = NULL; + } + } + + return( remS ); + } + + +void TMMCSessRing::Remove(DMMCSession* aSessP) +/** + * Removes a specified session from the ring + */ + { + if( Point(aSessP) ) + Remove(); + else + DMMCSocket::Panic(DMMCSocket::EMMCSessRingNoSession); + } + + + +// -------- class TMMCStateMachine -------- + + +/** +Removes all state from the state machine. + +It also resets the stack and the exit code. +*/ +EXPORT_C void TMMCStateMachine::Reset() + { + iAbort = EFalse; + iSP = 0; iExitCode = 0; + iStack[0].iState = 0; iStack[0].iTrapMask = 0; + } + + + + +/** +The state machine dispatcher. + +@return The MultiMediaCard error code. +*/ +EXPORT_C TMMCErr TMMCStateMachine::Dispatch() + { + + // If a state machine returns non-zero, i.e. a non-empty error set, then the second + // inner while loop is broken. The errors are thrown like an exception where the + // stack is unravelled until it reaches a state machine which can handle at least + // one of the error codes, else this function returns with the exit code or'd with + // KMMCErrBypass. If the state machine returns zero, then this function returns + // zero if iSuspend is set, i.e., if the stack is waiting on an asynchronous event. + // If suspend is not set, then the next state machine is called. This may be the + // same as the current state machine, or its caller if the current state machine + // ended called Pop() before exiting, e.g., via SMF_END. + + while( iSP >= 0 && !iAbort ) + { + // If there is an un-trapped error, wind back down the stack, either + // to the end of the stack or until the error becomes trapped. + while( iSP >= 0 && (iExitCode & ~iStack[iSP].iTrapMask) != 0 ) + iSP--; + + iExitCode &= ~KMMCErrBypass; + + if ( iExitCode ) + { + __KTRACE_OPT(KPBUS1,Kern::Printf(">MMC:Err %x",iExitCode)); + } + + while( iSP >= 0 && !iAbort ) + { + __KTRACE_OPT(KPBUS1,Kern::Printf("-msm:dsp:%02x:%08x.%02x",iSP, TUint32(iStack[iSP].iFunction), State())); + + iSuspend = ETrue; + const TMMCErr signal = iStack[iSP].iFunction(iContextP); + + if (signal) + { + iExitCode = signal; + break; + } + + if( iSuspend ) + { + __KTRACE_OPT(KPBUS1,Kern::Printf("SetStack(this); + + iStackDFC.SetDfcQ(&iSocket->iDfcQ); + + // Get the maximal number of cards from ASSP layer + iMaxCardsInStack = iSocket->TotalSupportedCards(); + if ( iMaxCardsInStack > KMaxMMCardsPerStack ) + iMaxCardsInStack=KMaxMMCardsPerStack; + + TInt r = iCardArray->AllocCards(); + + return(r); + } + +EXPORT_C void DMMCStack::PowerUpStack() +/** + * Enforce stack power-up and initialisation. + * This is an asynchronous operation, which calls DMMCSocket::PowerUpSequenceComplete upon completion. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:pus")); + + if (iPSLBuf == NULL) + { + GetBufferInfo(&iPSLBuf, &iPSLBufLen); + iMinorBufLen = KMinMinorBufSize; + } + + ReportPowerDown(); // ensure power will be switch on regardless + + Scheduler( iInitialise ); + } + +void DMMCStack::QSleepStack() +/** + * Schedules a session to place media in Sleep State + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:qsleep")); + + Scheduler( iSleep ); + } + +EXPORT_C void DMMCStack::PowerDownStack() +/** + * Enforce stack power down. + * Clients generally shouldn't need to concern themselves with powering down a stack + * unless they specifically need to perform a power reset of a card. If a driver fails to + * open then normal practise is for that driver to leave the card powered so that any subsequent + * driver which may attempt to open immediately after this failed attempt won't have to re-power the card. + * If no driver successfully opens on the card then the Controllers inactivity/not in use + * timeout system can be left to power it down. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:pds")); + + ReportPowerDown(); + iInitState = EISPending; + DoPowerDown(); + + TBool cardRemoved = (iStackState & KMMCStackStateCardRemoved); + for (TUint i=0;iCard(i); + card.SetBusWidth(1); + card.SetHighSpeedClock(0); + if (cardRemoved) + { + iCardArray->DeclareCardAsGone(i); + } + else + { + // set the locked bit if the card has a password - need to do this + // now that RLocalDrive::Caps() no longer powers up the stack + if (card.HasPassword()) + { + TMapping* pmp = iSocket->iPasswordStore->FindMappingInStore(card.CID()); + if (!pmp || pmp->iState != TMapping::EStValid) + { + *((TUint32*) &card.iStatus) |= KMMCStatCardIsLocked; + } + } + + // Remove card state flags, after a power cycle all cards are in idle state + *((TUint32*) &card.iStatus) &= ~KMMCStatCurrentStateMask; + } + } + if (cardRemoved) + iStackState &= ~KMMCStackStateCardRemoved; + + + iSocket->iVcc->SetState(EPsuOff); + if (iSocket->iVccCore) + iSocket->iVccCore->SetState(EPsuOff); + + // Cancel timers, reset ASSP, cancel stack DFC & remove session from workset + // to ensure stack doesn't wake up again & attempt to dereference iSessionP + if (iSessionP) + Abort(iSessionP); + + iStackDFC.Cancel(); + + // The stack may have powered down while attempting to power up (e.g. because a card has not responded), + // so ensure stack doesn't attempt to initialize itself again until next PowerUpStack() + iInitialise = EFalse; + iStackState &= ~(KMMCStackStateInitInProgress | KMMCStackStateInitPending | KMMCStackStateBusInconsistent | KMMCStackStateWaitingDFC); + iSessionP = NULL; + } + +// +// DMMCStack:: --- Stack Scheduler and its supplementary functions --- +// +DMMCStack::TMMCStackSchedStateEnum DMMCStack::SchedGetOnDFC() +/** + * Initiates stack DFC. Returns either Continue or Loop. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sgd")); + + if( iDFCRunning ) + return( ESchedContinue ); + + if( (iStackState & KMMCStackStateWaitingDFC) == 0 ) + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sgd:q")); + iStackState |= KMMCStackStateWaitingDFC; + if (NKern::CurrentContext()==NKern::EInterrupt) + iStackDFC.Add(); + else + iStackDFC.Enque(); + } + + return( ESchedLoop ); + } + +void DMMCStack::SchedSetContext(DMMCSession* aSessP) +/** + * Sets up the specified session as the current session. + * Invoked by JobChooser and Initialiser. + * @param aSessP A pointer to the session. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:ssc")); + + if( (iStackState & (KMMCStackStateInitPending|KMMCStackStateBusInconsistent)) != 0 && + aSessP->iSessionID != ECIMInitStack ) + { + iInitialise = ETrue; + return; + } + + if( iSessionP != aSessP ) + { + iStackState |= KMMCStackStateReScheduled; + MergeConfig( aSessP ); + + if( aSessP->iSessionID == ECIMInitStack ) + iInitialise = ETrue; + else + if( InitStackInProgress() ) + MarkComplete( aSessP, KMMCErrStackNotReady ); + else + if( aSessP->iBrokenLock ) + MarkComplete( aSessP, KMMCErrBrokenLock ); + + iSessionP = aSessP; + } + + iSessionP->iState &= ~KMMCSessStateDoReSchedule; + } + +void DMMCStack::SchedDoAbort(DMMCSession* aSessP) +/** + * Aborts asynchronous activities of a session aSessP + * @param aSessP A pointer to the session to be aborted. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sda")); + +#ifdef __EPOC32__ + if( aSessP->iBlockOn & KMMCBlockOnPollTimer ) + aSessP->iPollTimer.Cancel(); + + if( aSessP->iBlockOn & KMMCBlockOnRetryTimer ) + aSessP->iRetryTimer.Cancel(); + + if( aSessP->iBlockOn & KMMCBlockOnPgmTimer ) + aSessP->iProgramTimer.Cancel(); +#endif // #ifdef __EPOC32__ + + if( aSessP->iBlockOn & KMMCBlockOnWaitToLock ) + iStackState &= ~KMMCStackStateWaitingToLock; + + if( aSessP->iBlockOn & (KMMCBlockOnASSPFunction | KMMCBlockOnInterrupt | KMMCBlockOnDataTransfer) ) + ASSPReset(); + + if( (aSessP->iState & (KMMCSessStateInProgress|KMMCSessStateCritical)) == + (KMMCSessStateInProgress|KMMCSessStateCritical) ) + iStackState |= KMMCStackStateInitPending; + + + (void)__e32_atomic_and_ord32(&aSessP->iBlockOn, ~(KMMCBlockOnPollTimer | KMMCBlockOnRetryTimer | + KMMCBlockOnWaitToLock | KMMCBlockOnASSPFunction | + KMMCBlockOnInterrupt | KMMCBlockOnDataTransfer) ); + } + +DMMCStack::TMMCStackSchedStateEnum DMMCStack::SchedResolveStatBlocks(DMMCSession* aSessP) +/** + * Checks static blocking conditions and removes them as necessary + * @param aSessP A pointer to the session. + * @return EschedContinue or ESchedLoop (if scheduler is to be restarted) + */ + { + + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:srsb")); + + if( (aSessP->iBlockOn & KMMCBlockOnCardInUse) && aSessP->iCardP->iUsingSessionP == NULL ) + aSessP->SynchUnBlock( KMMCBlockOnCardInUse ); + + if( (aSessP->iBlockOn & KMMCBlockOnWaitToLock) && iWorkSet.Size() == 1 ) + { + // ECIMLockStack processed here + iLockingSessionP = aSessP; // in this order + iStackState &= ~KMMCStackStateWaitingToLock; + aSessP->SynchUnBlock( KMMCBlockOnWaitToLock ); + MarkComplete( aSessP, KMMCErrNone ); + return( ESchedLoop ); + } + + return( ESchedContinue ); + } + +DMMCStack::TMMCStackSchedStateEnum DMMCStack::SchedGroundDown(DMMCSession* aSessP, TMMCErr aReason) +/** + * Aborts all asynchronous activities of session aSessP with + * iExitCode = aReason. This function conserns itself with asynchronous + * activities only; session static state (eg Critical) is not taken into + * account. Session dynamic state and action flags (i.e. SafeInGaps, + * DoReSchedule and DoDFC) are cleared. + * @param aSessP A pointer to the session. + * @param aReason The reason for aborting. + * @return EschedContinue if everything's done OK. + * @return ESchedLoop if the session can not be safely grounded (eg + * iStackSession) and should therefore be aborted and/or completed by a + * separate scheduler pass. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sgdn")); + + if( (aSessP == iStackSession) || InitStackInProgress() ) + return( ESchedLoop ); + + if( aSessP->iState & KMMCSessStateInProgress ) + { + SchedDoAbort( aSessP ); + //coverity[check_return] + //return value is not saved or checked because there is no further uses. + aSessP->iMachine.SetExitCode( aReason ); + aSessP->iState &= ~(KMMCSessStateSafeInGaps | KMMCSessStateDoReSchedule | + KMMCSessStateDoDFC); + } + + return( ESchedContinue ); + } + +DMMCStack::TMMCStackSchedStateEnum DMMCStack::SchedEnqueStackSession(TMMCSessionTypeEnum aSessID) +/** + * Prepare internal session for InitStack and enque it into WorkSet. + * @return EschedContinue or ESchedLoop + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sess")); + + if( iStackSession->IsEngaged() ) + { + MarkComplete( iStackSession, KMMCErrAbort ); + return( ESchedLoop ); + } + + iStackSession->SetupCIMControl( aSessID ); + iWorkSet.Add( iStackSession ); + iStackSession->iState |= KMMCSessStateEngaged; + return( ESchedContinue ); + } + +void DMMCStack::SchedGrabEntries() +/** + * Merges Entry queue into Ready queue. Invoked at the scheduler entry and + * after the completion pass + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sge")); + + iAttention = EFalse; // Strictly in this order + if( !iEntryQueue.IsEmpty() ) + { + DISABLEPREEMPTION + iReadyQueue.Add( iEntryQueue ); + RESTOREPREEMPTION + } + } + +void DMMCStack::SchedDisengage() +/** + * This function is called by AbortPass() and CompletionPass() to remove the session + * at WorkSet Point, to abort its asynchronous activities (if any) and + * clear up the dependent resources + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sd")); + + DMMCSession* sessP = iWorkSet.Remove(); + + SchedDoAbort( sessP ); + + if( sessP == iSessionP ) + iSessionP = NULL; + + if( sessP->iCardP != NULL && sessP->iCardP->iUsingSessionP == sessP ) + sessP->iCardP->iUsingSessionP = NULL; + + // iAutoUnlockSession may attach to more than once card, so need to iterate + // through all cards and clear their session pointers if they match sessP + if (sessP == &iAutoUnlockSession) + { + for (TUint i = 0; i < iMaxCardsInStack; i++) + { + TMMCard& cd = *(iCardArray->CardP(i)); + if (cd.iUsingSessionP == sessP) + cd.iUsingSessionP = NULL; + } + } + + if( sessP->iState & KMMCSessStateASSPEngaged ) + ASSPDisengage(); + + sessP->iState = 0; + } + +inline DMMCStack::TMMCStackSchedStateEnum DMMCStack::SchedAbortPass() +/** + * DMMCStack Scheduler private inline functions. These functions were separated as inline functions + * only for the sake of Scheduler() clarity. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sap")); + + iAbortReq = EFalse; + SchedGrabEntries(); + DMMCSession* sessP; + + iWorkSet.Point(); + + while( (sessP = iWorkSet) != NULL ) + if( iAbortAll || sessP->iDoAbort ) + SchedDisengage(); + else + iWorkSet++; + + iReadyQueue.Point(); + + while( (sessP = iReadyQueue) != NULL ) + if( iAbortAll || sessP->iDoAbort ) + { + iReadyQueue.Remove(); + sessP->iState = 0; + } + else + iReadyQueue++; + + if( iAbortReq ) + return( ESchedLoop ); + + // Clearing iAbortAll here is a bit dodgy. It wouldn't work if somebody interrupted us + // at this point, enqued a session and then immediately called Reset() - that session + // would not be discarded. However, the correct solution (enque Reset() requests + // and process them in the Scheduler main loop) seems to be too expensive just to avoid + // this particular effect. + iAbortAll = EFalse; + return( ESchedContinue ); + } + +inline DMMCStack::TMMCStackSchedStateEnum DMMCStack::SchedCompletionPass() +/** + * This function calls back all the sessions waiting to be completed + * Returns either Continue or Loop. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:scp")); + + iCompReq = EFalse; + DMMCSession* sessP; + + if( iCompleteAllExitCode ) + { + SchedGrabEntries(); + iWorkSet.Add( iReadyQueue ); + } + + iWorkSet.Point(); + + while( (sessP = iWorkSet) != NULL ) + if( iCompleteAllExitCode || sessP->iDoComplete ) + { + if( (EffectiveModes(sessP->iConfig) & KMMCModeCompleteInStackDFC) != 0 && + SchedGetOnDFC() ) + { + // DFC has been queued so return back to main loop. Next time + // SchedGetOnDfc() will return EFalse, and the callback will be called. + iCompReq = ETrue; + return( ESchedLoop ); + } + + SchedDisengage(); // calls iWorkSet.Remove + sessP->iMMCExitCode |= iCompleteAllExitCode; + // Update the controller store if a password operation was in progress. + TBool doCallback = ETrue; + if (sessP->iSessionID == ECIMLockUnlock) + { + iSocket->PasswordControlEnd(sessP, sessP->EpocErrorCode()); + + if(sessP->EpocErrorCode() == KErrNone) + { + sessP->SetupCIMInitStackAfterUnlock(); + if(sessP->Engage() == KErrNone) + { + doCallback = EFalse; + } + } + } + + if(sessP->iSessionID == ECIMInitStackAfterUnlock) + { + // After unlocking the stack, cards may have switched into HS mode + // (HS switch commands are only valid when the card is unlocked). + // + // Therefore, we need to re-negotiate the maximum bus clock again. + // + // The PSL will use this to set the master config (limiting the clock if + // appropriate). + // + // Note that the clock may change when a specific card is selected. + // + TUint maxClk; + iCardArray->UpdateAcquisitions(&maxClk); + SetBusConfigDefaults( iMasterConfig.iBusConfig, maxClk ); + DoSetClock(maxClk); + } + + if(doCallback) + { + // call media driver completion routine or StackSessionCBST(). + sessP->iCallBack.CallBack(); + } + } + else + iWorkSet++; + + if( iCompReq ) + return( ESchedLoop ); + + iCompleteAllExitCode = 0; + + return( ESchedContinue ); + } + +inline DMMCStack::TMMCStackSchedStateEnum DMMCStack::SchedInitStack() +/** + * "Immediate" InitStack initiator. Returns either Continue or Loop. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sis")); + + if( SchedGetOnDFC() ) + return( ESchedLoop ); + + if( iSessionP != NULL && (iStackState & KMMCStackStateJobChooser) == 0 ) + { + if( (iSessionP->iState & KMMCSessStateInProgress) ) + { + if( SchedGroundDown(iSessionP, KMMCErrPowerDown) ) + { + MarkComplete( iSessionP, KMMCErrPowerDown ); + return( ESchedLoop ); + } + } + else + iSessionP->iMachine.Reset(); + } + + // NB if the current session was InitStack InProgress, JobChooser can not be active; + // so we are not going to continue another InitStack as if nothing happened. + + iStackState &= ~(KMMCStackStateInitInProgress|KMMCStackStateInitPending); + + // If there is no current session (e.g. called from PowerUpStack()) or the current + // session isn't specifically ECIMInitStack (which it rarely will be) then we have to use + // the stack session to perform the stack init. + if( iSessionP == NULL || iSessionP->iSessionID != ECIMInitStack ) + { + if( SchedEnqueStackSession(ECIMInitStack) ) + return( ESchedLoop ); + + SchedSetContext( iStackSession ); // make the internal session to be current job + } + + // Neither client nor internal session could be blocked here, not even on "BrokenLock" + __ASSERT_ALWAYS( (iSessionP->iBlockOn)==0, + DMMCSocket::Panic(DMMCSocket::EMMCInitStackBlocked) ); + + iStackState |= KMMCStackStateInitInProgress; + // nothing can stop this session now; it's safe to clear iInitialise here. + iInitialise = EFalse; + return( ESchedContinue ); + } + +inline DMMCStack::TMMCStackSchedStateEnum DMMCStack::SchedSleepStack() +/** + * "Immediate" Stack sleep mode. Returns either Continue or Loop. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:SchdSlp!")); + + // Make sure Stack DFC is Running! + if( SchedGetOnDFC() ) + { + __KTRACE_OPT(KPBUS1, Kern::Printf("mst:SchdSlp - DFC not running")); + return( ESchedLoop ); + } + + if( iSessionP != NULL && (iStackState & KMMCStackStateJobChooser) == 0 ) + { + if( (iSessionP->iState & KMMCSessStateInProgress) ) + { + // A session has been queued before sleep, + // cancel sleep and loop for next session + iSleep = EFalse; + return( ESchedLoop ); + } + } + + // Use the stack session to perform the stack sleep. + if( SchedEnqueStackSession(ECIMSleep) ) + { + __KTRACE_OPT(KPBUS1,Kern::Printf("SchdSlp: already Enqued")); + // Stack already busy cancel sleep + iSleep = EFalse; + return( ESchedLoop ); + } + + SchedSetContext( iStackSession ); // make the internal session to be current job + + // Sleep has now been queued + iSleep = EFalse; + iStackState |= KMMCStackStateSleepinProgress; + __KTRACE_OPT(KPBUS1, Kern::Printf("iState & KMMCSessStateDoReSchedule) ) + return( ETrue ); + + if( (iSessionP->iBlockOn & KMMCBlockOnASSPFunction) ) + return( EFalse ); + + TBool preemptDC = EFalse; + + if (iSessionP->iBlockOn & KMMCBlockOnYielding) + { + // Added to support yielding the stack for a specific command. + preemptDC = ETrue; + } + else if( (iSessionP->iBlockOn & KMMCBlockOnDataTransfer) ) + { + // Added for SDIO Read/Wait and SDC support. This condition + // is set at the variant, and determines whether commands may be + // issued during the data transfer period. + if(!(iSessionP->iState & KMMCSessStateAllowDirectCommands)) + return( EFalse ); + + // We must consider the remaining blocking conditions + // before being sure that we can enable pre-emtion of this session + preemptDC = ETrue; + } + + if( (iSessionP->iBlockOn & (KMMCBlockOnCardInUse | KMMCBlockOnNoRun)) ) + return( ETrue ); + + if( (iConfig.iModes & KMMCModeEnablePreemption) == 0 ) + return( EFalse ); + + if( (iSessionP->iBlockOn & KMMCBlockOnGapTimersMask) && + (iConfig.iModes & KMMCModePreemptInGaps) && + (iSessionP->iState & KMMCSessStateSafeInGaps) ) + return( ETrue ); + + if( iSessionP->iBlockOn & KMMCBlockOnInterrupt ) + return( ETrue ); + + if(preemptDC) + return( ETrue ); + + return( EFalse ); + } + +inline DMMCStack::TMMCStackSchedStateEnum DMMCStack::SchedSession() +/** + * Current context analyser. Returns Exit, Loop or ChooseJob. + */ + { + + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:ss")); + + // If no current session selected then we need to choose one + if (iSessionP == NULL) + return(ESchedChooseJob); + + // Check any static blocking conditions on the current session and remove if possible + if (SchedResolveStatBlocks(iSessionP)==ESchedLoop) + return(ESchedLoop); + + // If current session is still blocked, see if we could pre-empt the session + if (iSessionP->iBlockOn) + { + if( SchedPreemptable() ) + return(ESchedChooseJob); + + return( ESchedExit ); // No preemption possible + } + + // If the current session has been marked to be 'un-scheduled' then we + // need to choose another session if ones available + if ( (iSessionP->iState & KMMCSessStateDoReSchedule) ) + return( ESchedChooseJob ); + + // Check if this session requires to be run in DFC context - loop if necessary + if ( (iSessionP->iState & KMMCSessStateDoDFC) ) + { + iSessionP->iState &= ~KMMCSessStateDoDFC; + if( SchedGetOnDFC()==ESchedLoop ) + return( ESchedLoop ); + } + + // Now we actually execute the current session + if( iLockingSessionP != NULL ) + { + if( (iStackState & KMMCStackStateLocked) ) + { + if( iSessionP != iLockingSessionP ) + { + iLockingSessionP->iBrokenLock = ETrue; + iLockingSessionP = NULL; + DeselectsToIssue(KMMCIdleCommandsAtRestart); // use it for the number of deselects as well + } + } + else + if( iSessionP == iLockingSessionP ) + iStackState |= KMMCStackStateLocked; + } + + if( iSessionP->iInitContext != iInitContext ) + { + // If the current session's init_stack pass number is set but isn't the same as the current + // pass number, it indicates this session is being resumed having tried to recover from + // a bus inconsitency by re-initialising the stack. Set the exit code to a special + // value so this session can un-wind from where the initial error occured, back to the start. + if( iSessionP->iInitContext != 0 ) + //coverity[check_return] + //return value is not saved or checked because there is no further uses. + iSessionP->iMachine.SetExitCode(KMMCErrInitContext | iSessionP->iMachine.ExitCode()); + + iSessionP->iInitContext = iInitContext; + } + + iStackState &= ~KMMCStackStateJobChooser; + iSessionP->iState &= ~KMMCSessStateSafeInGaps; + + // Execute the session state machine until it completes, is blocked or is aborted. + TMMCErr exitCode = iSessionP->iMachine.Dispatch(); + + iStackState &= ~KMMCStackStateReScheduled; + + if( exitCode ) + MarkComplete( iSessionP, (exitCode & ~KMMCErrBypass) ); + + return(ESchedLoop); + } + +TBool DMMCStack::SchedYielding(DMMCSession* aSessP) +/** + * Check whether the scheduler should yield to another command + */ + { + // Test whether a full loop through the sessions has occurred during a yield + if ((aSessP->iBlockOn & KMMCBlockOnYielding) && (iStackState & KMMCStackStateYielding)) + { + // We've looped, now stop yielding + aSessP->iBlockOn &= ~KMMCBlockOnYielding; + iStackState &= ~KMMCStackStateYielding; + } + return(iStackState & KMMCStackStateYielding) != 0; + } + +TBool DMMCStack::SchedAllowDirectCommands(DMMCSession* aSessP) +/** + * Check whether direct only commands can be run. + */ + { + TBool allowDirectCommands = EFalse; + + // Test the remaining sessions to see if they have a DMA data transfer blockage which allow direct commands only + DMMCSession* testSessP = aSessP; + do + { + if ((testSessP->iBlockOn & KMMCBlockOnDataTransfer) && (testSessP->iState & KMMCSessStateAllowDirectCommands)) + allowDirectCommands = ETrue; + testSessP = testSessP->iLinkP; + } + while((aSessP != testSessP) && (testSessP != NULL)); + + return(allowDirectCommands); + } + +inline DMMCStack::TMMCStackSchedStateEnum DMMCStack::SchedChooseJob() +/** + * Find an unblocked job to run. Returns Exit or Loop. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:scj")); + + iStackState |= KMMCStackStateJobChooser; + SchedGrabEntries(); + DMMCSession* sessP = NULL; + + if( iLockingSessionP != NULL ) // if stack is already locked we accept only locking session + { + if( iWorkSet.IsEmpty() && iReadyQueue.Point(iLockingSessionP) ) + sessP = iReadyQueue.Remove(); + } + else // otherwise we might add a fresh session from reserve + { + iStackState &= ~KMMCStackStateLocked; + if( iWorkSet.Size() < KMMCMaxJobsInStackWorkSet && // if work set is not too big + !iReadyQueue.IsEmpty() && // and there are ready sessions + (iStackState & KMMCStackStateWaitingToLock) == 0 ) // and nobody waits to lock us + { + iReadyQueue.Point(); // at marker to preserve FIFO + sessP = iReadyQueue.Remove(); + } + } + + if( sessP != NULL ) + { + iWorkSet.Add( sessP ); + + if( sessP->iSessionID == ECIMLockStack ) + { + sessP->SynchBlock( KMMCBlockOnWaitToLock | KMMCBlockOnNoRun ); + sessP->iBrokenLock = EFalse; + iStackState |= KMMCStackStateWaitingToLock; + } + } + + if( iSessionP != NULL ) + iWorkSet.AdvanceMarker(); // move current session to the end of the queue + + iWorkSet.Point(); + + while( (sessP = iWorkSet) != NULL ) + { + // first, remove all static blocking conditions + if( SchedResolveStatBlocks(sessP) ) + return( ESchedLoop ); + + TBool scheduleSession = ETrue; + // Test whether we are yielding + if (SchedYielding(sessP) && (sessP->Command().iSpec.iCommandType != iYieldCommandType)) + scheduleSession = EFalse; + // Test whether this session is blocked + else if (sessP->iBlockOn) + scheduleSession = EFalse; + // Test whether we can only handle direct commands + else if (SchedAllowDirectCommands(sessP) && (sessP->Command().iSpec.iCommandType != ECmdTypeADC)) + scheduleSession = EFalse; + + if (scheduleSession) + { + iWorkSet.SetMarker(); + SchedSetContext( sessP ); + return( ESchedLoop ); + } + + iWorkSet++; + } + + return( ESchedExit ); + } + +void DMMCStack::StackDFC(TAny* aStackP) +/** + * This DFC is used to startup Stack Scheduler from the background. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sdf")); + + DMMCStack* const stackP = static_cast(aStackP); + stackP->Scheduler( stackP->iDFCRunning ); + } + +void DMMCStack::Scheduler(volatile TBool& aFlag) +/** + * This is the main function which controls, monitors and synchronises session execution. + * It's divided into the entry function Scheduler() and the scheduling mechanism itself, + * DoSchedule() + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sch")); + + DISABLEPREEMPTION + aFlag = ETrue; + + if( iStackState & KMMCStackStateRunning ) + { + RESTOREPREEMPTION + return; + } + + iStackState |= KMMCStackStateRunning; + RESTOREPREEMPTION + DoSchedule(); + } + +void DMMCStack::DoSchedule() + { + __KTRACE_OPT(KPBUS1,Kern::Printf(">mst:dos")); + + for(;;) + { + for(;;) + { + if( iAbortReq && SchedAbortPass() ) + continue; + + if( iDFCRunning ) + iStackState &= ~KMMCStackStateWaitingDFC; + else + if( iStackState & KMMCStackStateWaitingDFC ) + break; + + if( iCompReq && SchedCompletionPass() ) + continue; + + if( iInitialise && SchedInitStack() ) + continue; + + if( iSleep && SchedSleepStack() ) + continue; + + iAttention = EFalse; + + DMMCStack::TMMCStackSchedStateEnum toDo = SchedSession(); + + if( toDo == ESchedLoop ) + continue; + + if( toDo == ESchedExit ) + break; + + if( SchedChooseJob() == ESchedExit ) + break; + } + + DISABLEPREEMPTION + + if( !iAbortReq && + ((iStackState & KMMCStackStateWaitingDFC) || + (iCompReq | iInitialise | iAttention)==0) || + ((iSessionP) && (iSessionP->iState & KMMCSessStateAllowDirectCommands))) + { + // Clear DFC flag here in case somebody was running scheduler in the background + // when DFC turned up. This should never really happen, but with EPOC who knows + iStackState &= ~KMMCStackStateRunning; + iDFCRunning = EFalse; + + RESTOREPREEMPTION + __KTRACE_OPT(KPBUS1,Kern::Printf("MMC:Add %d",TUint(aSessP->iSessionID))); + + DISABLEPREEMPTION + iEntryQueue.Add( aSessP ); + aSessP->iState |= KMMCSessStateEngaged; + RESTOREPREEMPTION + Scheduler( iAttention ); + } + +void DMMCStack::Abort(DMMCSession* aSessP) +/** + * Aborts a session + */ + { + ASSERT_NOT_ISR_CONTEXT + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:abt")); + + if( !aSessP->IsEngaged() ) + return; + + aSessP->iDoAbort = ETrue; + aSessP->iMachine.Abort(); + + Scheduler( iAbortReq ); + } + +void DMMCStack::Stop(DMMCSession* aSessP) +/** + * Signals session to stop + */ + { + ASSERT_NOT_ISR_CONTEXT + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:stp")); + + if( !aSessP->IsEngaged() ) + return; + + aSessP->iDoStop = ETrue; + } + +EXPORT_C void DMMCStack::Block(DMMCSession* aSessP, TUint32 aFlag) + { + ASSERT_NOT_ISR_CONTEXT + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:blk")); + + if( !aSessP->IsEngaged() ) + return; + + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:blk:[aFlag=%08x, iBlockOn=%08x]", aFlag, aSessP->iBlockOn)); + + (void)__e32_atomic_ior_ord32(&aSessP->iBlockOn, aFlag); + } + +EXPORT_C void DMMCStack::UnBlock(DMMCSession* aSessP, TUint32 aFlag, TMMCErr anExitCode) +/** + * aFlag is a bitset of KMMCBlockOnXXX events that have occured. If the stack's + * session is waiting on all of these events, then it is scheduled. + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:ubl")); + + if (aSessP != NULL) + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:ubl:[aFlag=%08x, iBlockOn=%08x", aFlag, aSessP->iBlockOn)); + + if( (aSessP->iBlockOn & aFlag) == 0 ) + return; + + // Must be either in a DFC or have the KMMCSessStateDoDFC flag set + __ASSERT_DEBUG( + (aSessP->iState & KMMCSessStateDoDFC) != 0 || + NKern::CurrentContext() != NKern::EInterrupt, + DMMCSocket::Panic(DMMCSocket::EMMCUnblockingInWrongContext)); + + (void)__e32_atomic_and_ord32(&aSessP->iBlockOn, ~aFlag); + aSessP->iMachine.SetExitCode( anExitCode ); + + if( aSessP->iBlockOn == 0 ) + Scheduler( iAttention ); + } + } + +void DMMCStack::UnlockStack(DMMCSession* aSessP) +/** + * Removes stack lock. Asynchronous function. + */ + { + ASSERT_NOT_ISR_CONTEXT + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:ust")); + + aSessP->iBrokenLock = EFalse; + + if( aSessP == iLockingSessionP ) + { + iLockingSessionP = NULL; + Scheduler( iAttention ); + } + } + +EXPORT_C TInt DMMCStack::Stop(TMMCard* aCardP) +/** + * Completes all sessions operating with a specified card with KMMCErrAbort. + * Returns either KErrNone or KErrServerBusy. + */ + { + ASSERT_NOT_ISR_CONTEXT + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:stp")); + + DISABLEPREEMPTION + + if( iStackState & KMMCStackStateRunning ) + { + RESTOREPREEMPTION + return( KErrServerBusy ); // can not operate in foreground + } + + iStackState |= KMMCStackStateRunning; + RESTOREPREEMPTION + + DMMCSession* sessP; + SchedGrabEntries(); + + iWorkSet.Point(); + + while( (sessP = iWorkSet++) != NULL ) + if( sessP->iCardP == aCardP ) + MarkComplete( sessP, KMMCErrAbort ); + + iReadyQueue.Point(); + + while( (sessP = iReadyQueue) != NULL ) + if( sessP->iCardP == aCardP ) + { + MarkComplete( sessP, KMMCErrAbort ); + iReadyQueue.Remove(); + iWorkSet.Add( sessP ); + } + else + iReadyQueue++; + + SchedGetOnDFC(); + DoSchedule(); + return( KErrNone ); + } + +void DMMCStack::MarkComplete(DMMCSession* aSessP, TMMCErr anExitCode) +/** + * Marks session to be completed on the next scheduler pass. + */ + { + ASSERT_NOT_ISR_CONTEXT + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:mcp")); + + aSessP->SynchBlock( KMMCBlockOnNoRun ); + aSessP->iMMCExitCode = anExitCode; + aSessP->iDoComplete = ETrue; + iCompReq = ETrue; + } + +// +// DMMCStack:: --- Miscellaneous --- +// +EXPORT_C TUint32 DMMCStack::EffectiveModes(const TMMCStackConfig& aClientConfig) +/** + * Calculates effective client modes as real client modes merged with iMasterConfig modes + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:em")); + + const TUint32 masterMode = (iMasterConfig.iModes & iMasterConfig.iUpdateMask) | + (KMMCModeDefault & ~iMasterConfig.iUpdateMask); + + const TUint32 c = aClientConfig.iClientMask; + const TUint32 u = aClientConfig.iUpdateMask; + const TUint32 m = aClientConfig.iModes; + const TUint32 userMode = (c & ((m & u) | ~u)) | (m & KMMCModeMask); + const TUint32 userMask = (u | KMMCModeClientMask) & + ((masterMode & KMMCModeMasterOverrides) | ~KMMCModeMasterOverrides); + + const TUint32 effectiveMode = (userMode & userMask) | (masterMode & ~userMask); + + if( effectiveMode & KMMCModeEnableClientConfig ) + return( effectiveMode ); + else + return( (effectiveMode & KMMCModeClientOverrides) | + (masterMode & ~(KMMCModeClientOverrides | KMMCModeClientMask)) ); + } + +void DMMCStack::MergeConfig(DMMCSession* aSessP) +/** + * Merges client and master configuration into iConfig + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:mc")); + + TMMCStackConfig& cC = aSessP->iConfig; + TMMCStackConfig& mC = iMasterConfig; + const TUint32 modes = EffectiveModes( cC ); + const TUint32 mastM = mC.iClientMask; + + iConfig.iModes = modes; + + iConfig.iPollAttempts = + (modes & KMMCModeClientPollAttempts) + ? cC.iPollAttempts + : ((mastM & KMMCModeClientPollAttempts) ? mC.iPollAttempts : KMMCMaxPollAttempts); + + iConfig.iTimeOutRetries = + (modes & KMMCModeClientTimeOutRetries) + ? cC.iTimeOutRetries + : ((mastM & KMMCModeClientTimeOutRetries) ? mC.iTimeOutRetries : KMMCMaxTimeOutRetries); + + iConfig.iCRCRetries = + (modes & KMMCModeClientCRCRetries) + ? cC.iCRCRetries + : ((mastM & KMMCModeClientCRCRetries) ? mC.iCRCRetries : KMMCMaxCRCRetries); + + iConfig.iUnlockRetries = + (modes & KMMCModeClientUnlockRetries) + ? cC.iUnlockRetries + : ((mastM & KMMCModeClientUnlockRetries) ? mC.iUnlockRetries : KMMCMaxUnlockRetries); + + iConfig.iOpCondBusyTimeout = + (modes & KMMCModeClientiOpCondBusyTimeout) + ? cC.iOpCondBusyTimeout + : ((mastM & KMMCModeClientiOpCondBusyTimeout) ? mC.iOpCondBusyTimeout : KMMCMaxOpCondBusyTimeout); + + // There are no default constants defining BusConfig parameters. + // iMasterConfig.iBusConfig must be initialised by ASSP layer + + // _?_? The code below can be modified later for a card controlled session + // to include CSD analisys and calculate time-out and clock parameters on that basis. + // As it written now, the defaults for all cards will be the same. + + if( modes & KMMCModeClientBusClock ) + { + TUint clock = cC.iBusConfig.iBusClock; + if( clock > mC.iBusConfig.iBusClock ) + clock = mC.iBusConfig.iBusClock; + if( clock < KMMCBusClockFOD ) + clock = KMMCBusClockFOD; + DoSetClock(clock); + } + else if( modes & KMMCModeCardControlled && aSessP->CardP() ) + { + TUint clock = MaxTranSpeedInKilohertz(*aSessP->CardP()); + if( clock > mC.iBusConfig.iBusClock ) + clock = mC.iBusConfig.iBusClock; + if( clock < KMMCBusClockFOD ) + clock = KMMCBusClockFOD; + DoSetClock(clock); + } + else + DoSetClock(mC.iBusConfig.iBusClock); + + iConfig.iBusConfig.iClockIn = (modes & KMMCModeClientClockIn) + ? cC.iBusConfig.iClockIn + : mC.iBusConfig.iClockIn; + + iConfig.iBusConfig.iClockOut = (modes & KMMCModeClientClockOut) + ? cC.iBusConfig.iClockOut + : mC.iBusConfig.iClockOut; + + iConfig.iBusConfig.iResponseTimeOut = (modes & KMMCModeClientResponseTimeOut) + ? cC.iBusConfig.iResponseTimeOut + : mC.iBusConfig.iResponseTimeOut; + + iConfig.iBusConfig.iDataTimeOut = (modes & KMMCModeClientDataTimeOut) + ? cC.iBusConfig.iDataTimeOut + : mC.iBusConfig.iDataTimeOut; + + iConfig.iBusConfig.iBusyTimeOut = (modes & KMMCModeClientBusyTimeOut) + ? cC.iBusConfig.iBusyTimeOut + : mC.iBusConfig.iBusyTimeOut; + } + +TBool DMMCStack::StaticBlocks() +/** + * This function realises the potential blocking conditions of the current session. + * Returns ETrue if the session has to be stopped right now + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:stb")); + + if( iSessionP->iDoStop ) + { + MarkComplete( iSessionP, KMMCErrAbort ); + return( ETrue ); + } + + if( !iDFCRunning && (iSessionP->iState & KMMCSessStateDoDFC) ) + return( ETrue ); + + return( (iSessionP->iState & KMMCSessStateDoReSchedule) != 0 ); + } + + +EXPORT_C TBool DMMCStack::CardDetect(TUint /*aCardNumber*/) +/** + * Returns ETrue when a card is present in the card socket 'aCardNumber'. + * Default implementation when not provided by ASSP layer. + */ + { + return(ETrue); + } + +EXPORT_C TBool DMMCStack::WriteProtected(TUint /*aCardNumber*/) +/** + * Returns ETrue when the card in socket 'aCardNumber' is mechanically write + * protected. + * Default implementation when not provided by ASSP layer. + */ + { + return(EFalse); + } + +// -------- DMMCStack State Machine functions -------- +// + +// Auxiliary SM function service + +void DMMCStack::StackSessionCBST(TAny* aStackP) +/** + * Stack Session completion routine. + */ + { + + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sscbs")); + static_cast(aStackP)->StackSessionCB(); + } + + +TInt DMMCStack::StackSessionCB() + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:sscb ")); + + if (iStackState & KMMCStackStateSleepinProgress) + { + // Sleep completed update stack state + iStackState &= ~KMMCStackStateSleepinProgress; + return( 0 ); + } + + TMMCErr mmcError = iStackSession->MMCExitCode(); + iStackState &= ~KMMCStackStateInitInProgress; + + TInt errCode = KErrNone; + TBool anyLocked = EFalse; + + if (mmcError != KMMCErrNone) + { + // + // StackSessionCB is the completion callback for the internal initialisation/power-up + // session, so we never expect a callback while initialisation is still in progress. + // - unless a card has failed to respond and the controller has not detected the error (!) + // + errCode = KErrTimedOut; // this error code is not sticky, so should allow stack to be powered up again + iInitialise = EFalse; + iStackState &= ~(KMMCStackStateInitInProgress | KMMCStackStateInitPending | KMMCStackStateBusInconsistent); + } + else + { + if (! iCardArray->CardsPresent()) + { + errCode = KErrNotReady; + } + else + { + // Stack initialized ok, so complete request or start auto-unlock + + iInitState = EISDone; + + // remove bindings from password store for cards that do not have passwords + TUint i; + for (i = 0; i < iMaxCardsInStack; ++i) + { + TMMCard& cd = *(iCardArray->CardP(i)); + if (cd.IsPresent()) + { + if (cd.HasPassword()) + anyLocked = ETrue; + else + { + TMapping* pmp = iSocket->iPasswordStore->FindMappingInStore(cd.CID()); + if (pmp) + pmp->iState = TMapping::EStInvalid; + } + } // if (cd.IsPresent()) + } // for (i = 0; i < iMaxCardsInStack; ++i) + + // if any cards are locked then launch auto-unlock mechanism + if (anyLocked) + { + // + // During power up (stack session context), we use the iAutoUnlockSession + // to perform auto-unlock of the cards. Upon completion of the AutoUnlock + // state machine, the local media subsystem is notified via ::PowerUpSequenceComplete + // + iAutoUnlockSession.SetStack(this); + iAutoUnlockSession.SetupCIMAutoUnlock(); + + errCode = iAutoUnlockSession.Engage(); + if(errCode == KErrNone) + { + // don't complete power up request yet + // - This will be done in DMMCStack::AutoUnlockCB() + return 0; + } + } + } // else ( !iCardArray->CardsPresent() ) + } + + if(errCode == KErrNone) + { + // + // No cards are locked (otherwise we will have engaged iAutoUnlockSession) and we + // have encountered no error, so can now continue with the second-stage initialisation + // phase (InitStackAfterUnlock). This performs initialisation that can only be + // performed when a card is unlocked (such as setting bus width, speed class etc..) + // + // iAutoUnlockSession::AutoUnlockCB will complete power-up by calling ::PowerUpSequenceComplete + // + iAutoUnlockSession.SetStack(this); + iAutoUnlockSession.iCardP = NULL; + iAutoUnlockSession.SetupCIMInitStackAfterUnlock(); + errCode = iAutoUnlockSession.Engage(); + } + + if(errCode != KErrNone) + { + // + // We have encountered an error during power up initialisation + // - Complete the request and notify the local media subsystem. + // + + // Calling PowerUpSequenceComplete() with an error may result in the media driver being closed which will delete + // the media driver's session, so the stack must be made re-entrant here to allow all references to any engaged + // sessions to be removed from the stack immediately to prevent the stack from referencing a deleted object + __ASSERT_ALWAYS(iStackState & KMMCStackStateRunning, DMMCSocket::Panic(DMMCSocket::EMMCNotInDfcContext)); + iStackState &= ~KMMCStackStateRunning; + iSocket->PowerUpSequenceComplete(errCode); + iStackState |= KMMCStackStateRunning; + + } + + return( 0 ); + } + +void DMMCStack::AutoUnlockCBST(TAny *aStackP) + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:aucbs")); + + static_cast(aStackP)->AutoUnlockCB(); + } + + +TInt DMMCStack::AutoUnlockCB() + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:aucb")); + + // This is the session end callback for iAutoUnlockSession, + // called at the end of the power up and initialisation process. + + TInt epocErr = iAutoUnlockSession.EpocErrorCode(); + + // Calling PowerUpSequenceComplete() with an error may result in the media driver being closed which will delete + // the media driver's session, so the stack must be made re-entrant here to allow all references to any engaged + // sessions to be removed from the stack immediately to prevent the stack from referencing a deleted object + __ASSERT_ALWAYS(iStackState & KMMCStackStateRunning, DMMCSocket::Panic(DMMCSocket::EMMCNotInDfcContext)); + if (epocErr != KErrNone) + iStackState &= ~KMMCStackStateRunning; + iSocket->PowerUpSequenceComplete(epocErr); + iStackState |= KMMCStackStateRunning; + + return 0; + } + + +inline TMMCErr DMMCStack::AttachCardSM() +/** + * This SM function must be invoked by every session which is CardControlled. + * + * Some commands require that the data held by the stack for a given card is up to date. + * + * These are card controlled commands. Before such commands are issued, this function should + * first be invoked which performs the SEND_STATUS (CMD13) command. + * + * @return MMC error code + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf("=mst:ac")); + + enum states + { + EStBegin=0, + EStAttStatus, + EStEnd + }; + + DMMCSession& s=Session(); + + SMF_BEGIN + + if( s.iCardP == NULL ) + return( KMMCErrNoCard ); + + if( s.iCardP->iUsingSessionP != NULL && s.iCardP->iUsingSessionP != &s ) + { + s.SynchBlock( KMMCBlockOnCardInUse ); + SMF_WAIT + } + + if( s.iCardP->IsPresent() && s.iCardP->iCID == s.iCID ) + s.iCardP->iUsingSessionP = &s; + else + return( KMMCErrNoCard ); + + s.iConfig.SetMode( KMMCModeCardControlled ); // for future context switching + iConfig.SetMode( KMMCModeCardControlled ); // for this context + + // read card status if there are sticky bits in it + if( (TUint32(s.iCardP->iStatus) & KMMCStatClearByReadMask) == 0 || + s.iCardP->iLastCommand == ECmdSendStatus || + s.iSessionID == ECIMNakedSession ) + SMF_EXIT + + s.PushCommandStack(); + s.FillCommandDesc( ECmdSendStatus, 0 ); + m.SetTraps( KMMCErrBasic ); // to restore command stack position to its original level + SMF_INVOKES( ExecCommandSMST, EStAttStatus ) + + SMF_STATE(EStAttStatus) + + s.PopCommandStack(); + SMF_RETURN( err ) + + SMF_END + } + +inline TMMCErr DMMCStack::CIMInitStackSM() +/** + * Performs the Perform the CIM_INIT_STACK macro. + * @return MMC error code + */ + { + enum states + { + EStBegin=0, + EStInitDone, + EStEnd + }; + + __KTRACE_OPT(KPBUS1,Kern::Printf(">MMC:InitStackSM")); + + DMMCSession& s=Session(); + + SMF_BEGIN + + m.SetTraps( KMMCErrAll ); // to prevent this macro from infinite restarts via iInitialise + + SMF_INVOKES( CIMUpdateAcqSMST, EStInitDone ) + + SMF_STATE(EStInitDone) + + s.iState &= ~KMMCSessStateInProgress; // now we won't be restarted + SchedGetOnDFC(); // StackSessionCB must be on DFC + SMF_RETURN( err ) // _?_ power cycles can be performed here if error + + SMF_END + } + +TMMCErr DMMCStack::CIMUpdateAcqSM() +/** + * Performs an identification of a card stack. New cards are always + * initialised but if KMMCStackStateInitInProgress is FALSE then existing + * cards keep their configuration. + * After successful execution of this function, all cards will be in standby + * state. + * If iPoweredUp is FALSE then the stack is powered up and a full INIT_STACK + * is performed (i.e all cards set to idle and then initialized). + * @return MMC error code + */ + { + enum states + { + EStBegin=0, + EStPoweredUp, + EStClockOn, + EStStartInterrogation, + EStCheckStack, + EStCardCap, + EStIssueDSR, + EStFinishUp, + EStEnd + }; + + __KTRACE_OPT(KPBUS1,Kern::Printf(">MMC:UpdAcqSM")); + + DMMCSession& s=Session(); + DMMCPsu* psu=(DMMCPsu*)iSocket->iVcc; + + SMF_BEGIN + + // This macro works naked and must not be preempted + iConfig.RemoveMode( KMMCModeEnablePreemption | KMMCModeCardControlled ); + // Ensure DFC is running before and after powering up + if( SchedGetOnDFC() ) // Such a direct synchronisation with Scheduler() can only + SMF_WAIT // be used in this macro + + s.iState |= (KMMCSessStateInProgress | KMMCSessStateCritical); + + if (iPoweredUp) + SMF_GOTOS( EStPoweredUp ) + + // The bus is not powered so all cards need initialising - enforce INIT_STACK. + iStackState |= KMMCStackStateInitInProgress; + + // Need to turn on the PSU at it's default voltage. Let the ASSP layer choose + // this voltage by calling SetVoltage() with the full range the ASSP supports. + iCurrentOpRange=(psu->VoltageSupported() & ~KMMCAdjustableOpVoltage); + psu->SetVoltage(iCurrentOpRange); + SMF_INVOKES( DoPowerUpSMST, EStPoweredUp ) + + SMF_STATE(EStPoweredUp) + + // Switch on the bus clock in identification mode + SetBusConfigDefaults(iMasterConfig.iBusConfig, KMMCBusClockFOD); + DoSetClock(KMMCBusClockFOD); + + // Make sure controller is in 1-bit bus width mode + DoSetBusWidth(EBusWidth1); + + MergeConfig(&s); // This might take some time, but we are running in DFC here + // Reinstate config bits after the merge + iConfig.RemoveMode( KMMCModeEnablePreemption | KMMCModeCardControlled ); + SMF_INVOKES( InitClockOnSMST, EStClockOn ) // Feed init clock to the bus + + SMF_STATE(EStClockOn) + + // Check if there are any cards present in the stack + if (!HasCardsPresent()) + SMF_GOTOS( EStCheckStack ) + + if( !InitStackInProgress() ) + SMF_GOTOS( EStStartInterrogation ) + + // Increment the stack's initialiser pass number. Set the current session's pass + // number to the new value. Pass number may be used later on to detect sessions + // which have been re-initialized due to problems on the bus. + if ((++iInitContext) == 0) + iInitContext++; // Pass number must never be zero + s.iInitContext = iInitContext; // this session is always in a proper context + + SMF_STATE(EStStartInterrogation) + + // NB: RCAs are not unlocked here. They will be unlocked one by one during the update of card info array. + SMF_INVOKES( AcquireStackSMST, EStCheckStack ) + + SMF_STATE(EStCheckStack) + + // Check that all known cards are still present by issuing select/deselect + SMF_INVOKES( CheckStackSMST, EStCardCap ) + + SMF_STATE(EStCardCap) + + // Call a licencee-specific state machine to allow card capabilities to be modified. + SMF_INVOKES( ModifyCardCapabilitySMST, EStIssueDSR ) + + SMF_STATE(EStIssueDSR) + + // Now that we have updated the card entries, do any final initialisation + // of the card entries and determine the maximum bus clock that can be employed. + // + // If the bus is not multiplexed (ie - MMC stack), then the max clock is set to + // the lowest common denominator of all cards in the stack. Otherwise (in the case + // of a multiplexed bus such as SD), the highest clock is returned and the clock + // rate is changed when a new card is selected. + // + TUint maxClk; + iCardArray->UpdateAcquisitions(&maxClk); + SetBusConfigDefaults( iMasterConfig.iBusConfig, maxClk ); + DoSetClock(maxClk); + + // merge clock from iMasterConfig.iBusConfig.iBusClock to + // iConfig.iBusConfig.iBusClock - which the PSL should use to configure it's clock + MergeConfig(&s); + + // switch to normal iConfig clock mode + InitClockOff(); + + SMF_STATE(EStFinishUp) + + s.iState &= ~(KMMCSessStateInProgress | KMMCSessStateCritical); + + // Update/Init stack has been completed. + + SMF_END + } + + +#define MHZ_TO_KHZ(valInMhz) ((valInMhz) * 1000) + +EXPORT_C TMMCErr DMMCStack::InitStackAfterUnlockSM() +/** + * Perform last-stage initialisation of the MMC card. + * This implements initialiation that must occur only when the card + * is unlocked (ie - immediately after unlocking, or during initialisation + * if the card is already unlocked) + */ + { + enum states + { + EStBegin=0, + EStTestNextCard, + EStGetExtendedCSD, + EStGotExtendedCSD, + EStGotModifiedExtendedCSD, + EStEraseGroupDefSet, + EStDetermineBusWidthAndClock, + EStGotBusWidthAndClock, + EStNoMoreCards, + EStExit, + EStEnd + }; + + DMMCSession& s = Session(); + TBool initSingleCard = (s.CardP() == NULL) ? (TBool)EFalse : (TBool)ETrue; + + SMF_BEGIN + + if(initSingleCard) + { + iSelectedCardIndex = iCxCardCount; + TMMCard* cardP = iCardArray->CardP(iSelectedCardIndex); + + // if card not present or is locked, exit + if ((!cardP->IsPresent()) || (cardP->IsLocked())) + SMF_GOTOS(EStExit); + + s.SetCard(cardP); + + // If a card is currently indexed for initialisation, then only configure this one. + // We assume that this has been called from the SD stack, so only one MMC card will be present on the bus + + SMF_GOTOS(EStGetExtendedCSD); + } + + // Initialising the entire MMC stack - start with the first card + iSelectedCardIndex = -1; + + // ...fall through... + + SMF_STATE(EStTestNextCard) + + // any more cards ? + if (++iSelectedCardIndex >= iCxCardCount) + SMF_GOTOS(EStNoMoreCards); + + // if no card in this slot, try next one + if (!iCardArray->CardP(iSelectedCardIndex)->IsPresent()) + SMF_GOTOS(EStTestNextCard); + + TMMCard* cardP = iCardArray->CardP(iSelectedCardIndex); + s.SetCard(cardP); + + // if card is locked, try the next one + if(cardP->IsLocked()) + SMF_GOTOS(EStTestNextCard); + + SMF_STATE(EStGetExtendedCSD) + + // Get the Extended CSD if this is an MMC version 4 card + + __KTRACE_OPT(KPBUS1, Kern::Printf(">ConfigureHighSpeed(), SpecVers() %u", s.CardP()->CSD().SpecVers())); + + // clear the Extended CSD contents in case this is a pre-version 4 card or the read fails. + memset(s.CardP()->iExtendedCSD.Ptr(), 0, KMMCExtendedCSDLength); + + if (s.CardP()->CSD().SpecVers() < 4) + SMF_GOTOS(EStTestNextCard); + + m.SetTraps(KMMCErrResponseTimeOut | KMMCErrStatus | KMMCErrDataCRC | KMMCErrBypass); // KMMCErrDataCRC will pick up if the card is not in 1-bit mode + + s.FillCommandDesc(ECmdSendExtendedCSD); + s.FillCommandArgs(0, KMMCExtendedCSDLength, iPSLBuf, KMMCExtendedCSDLength); + + __KTRACE_OPT(KPBUS1, Kern::Printf(">ConfigureHighSpeed(), Sending ECmdSendExtendedCSD")); + SMF_INVOKES(CIMReadWriteBlocksSMST, EStGotExtendedCSD) + + SMF_STATE(EStGotExtendedCSD) + + if (err != KMMCErrNone) + { + SMF_GOTOS(EStExit); + } + + memcpy(s.CardP()->iExtendedCSD.Ptr(), iPSLBuf, KMMCExtendedCSDLength); + + // Call a licencee-specific state machine to allow the Extended CSD register to be modified. + SMF_INVOKES( ModifyCardCapabilitySMST, EStGotModifiedExtendedCSD ) + + SMF_STATE(EStGotModifiedExtendedCSD) + + __KTRACE_OPT(KPBUS1, Kern::Printf("Extended CSD")); + __KTRACE_OPT(KPBUS1, Kern::Printf("CSDStructureVer: %u", s.CardP()->ExtendedCSD().CSDStructureVer())); + __KTRACE_OPT(KPBUS1, Kern::Printf("ExtendedCSDRev: %u", s.CardP()->ExtendedCSD().ExtendedCSDRev())); + __KTRACE_OPT(KPBUS1, Kern::Printf("-------------------------------")); + __KTRACE_OPT(KPBUS1, Kern::Printf("SupportedCmdSet: %u", s.CardP()->ExtendedCSD().SupportedCmdSet())); + __KTRACE_OPT(KPBUS1, Kern::Printf("PowerClass26Mhz360V: 0x%02X", s.CardP()->ExtendedCSD().PowerClass26Mhz360V())); + __KTRACE_OPT(KPBUS1, Kern::Printf("PowerClass52Mhz360V: 0x%02X", s.CardP()->ExtendedCSD().PowerClass52Mhz360V())); + __KTRACE_OPT(KPBUS1, Kern::Printf("PowerClass26Mhz195V: 0x%02X", s.CardP()->ExtendedCSD().PowerClass26Mhz195V())); + __KTRACE_OPT(KPBUS1, Kern::Printf("PowerClass52Mhz195V: 0x%02X", s.CardP()->ExtendedCSD().PowerClass52Mhz195V())); + __KTRACE_OPT(KPBUS1, Kern::Printf("CardType: %u", s.CardP()->ExtendedCSD().CardType())); + __KTRACE_OPT(KPBUS1, Kern::Printf("CmdSet: %u", s.CardP()->ExtendedCSD().CmdSet())); + __KTRACE_OPT(KPBUS1, Kern::Printf("CmdSetRev: %u", s.CardP()->ExtendedCSD().CmdSetRev())); + __KTRACE_OPT(KPBUS1, Kern::Printf("PowerClass: %u", s.CardP()->ExtendedCSD().PowerClass())); + __KTRACE_OPT(KPBUS1, Kern::Printf("HighSpeedTiming: %u", s.CardP()->ExtendedCSD().HighSpeedTiming())); + __KTRACE_OPT(KPBUS1, Kern::Printf("HighCapacityEraseGroupSize: %u", s.CardP()->ExtendedCSD().HighCapacityEraseGroupSize())); + __KTRACE_OPT(KPBUS1, Kern::Printf("AccessSize: %u", s.CardP()->ExtendedCSD().AccessSize())); + __KTRACE_OPT(KPBUS1, Kern::Printf("BootInfo: %u", s.CardP()->ExtendedCSD().BootInfo() )); + __KTRACE_OPT(KPBUS1, Kern::Printf("BootSizeMultiple: %u", s.CardP()->ExtendedCSD().BootSizeMultiple() )); + __KTRACE_OPT(KPBUS1, Kern::Printf("EraseTimeoutMultiple: %u", s.CardP()->ExtendedCSD().EraseTimeoutMultiple() )); + __KTRACE_OPT(KPBUS1, Kern::Printf("ReliableWriteSector: %u", s.CardP()->ExtendedCSD().ReliableWriteSector() )); + __KTRACE_OPT(KPBUS1, Kern::Printf("HighCapWriteProtGroupSize: %u", s.CardP()->ExtendedCSD().HighCapacityWriteProtectGroupSize() )); + __KTRACE_OPT(KPBUS1, Kern::Printf("SleepCurrentVcc: %u", s.CardP()->ExtendedCSD().SleepCurrentVcc() )); + __KTRACE_OPT(KPBUS1, Kern::Printf("SleepCurrentVccQ: %u", s.CardP()->ExtendedCSD().SleepCurrentVccQ())); + __KTRACE_OPT(KPBUS1, Kern::Printf("SleepAwakeTimeout: %u", s.CardP()->ExtendedCSD().SleepAwakeTimeout())); + __KTRACE_OPT(KPBUS1, Kern::Printf("BootConfig: %u", s.CardP()->ExtendedCSD().BootConfig())); + __KTRACE_OPT(KPBUS1, Kern::Printf("BootBusWidth: %u", s.CardP()->ExtendedCSD().BootBusWidth())); + __KTRACE_OPT(KPBUS1, Kern::Printf("EraseGroupDef: %u", s.CardP()->ExtendedCSD().EraseGroupDef())); + + if (s.CardP()->ExtendedCSD().ExtendedCSDRev() >= 3) + { + if (!(s.CardP()->ExtendedCSD().EraseGroupDef()) && s.CardP()->ExtendedCSD().HighCapacityEraseGroupSize()) + { + // Need to ensure that media is using correct erase group sizes. + TMMCArgument arg = TExtendedCSD::GetWriteArg( + TExtendedCSD::EWriteByte, + TExtendedCSD::EEraseGroupDefIndex, + TExtendedCSD::EEraseGrpDefEnableHighCapSizes, + 0); + + __KTRACE_OPT(KPBUS1, Kern::Printf(">Writing to EXT_CSD (EEraseGroupDefIndex), arg %08X", (TUint32) arg)); + s.FillCommandDesc(ECmdSwitch, arg); + + SMF_INVOKES(ExecSwitchCommandST, EStEraseGroupDefSet) + } + } + + SMF_GOTOS(EStDetermineBusWidthAndClock) + + SMF_STATE(EStEraseGroupDefSet) + + if (err == KMMCErrNone) + { + // EEraseGroupDef has been updated succussfully, + // update the Extended CSD to reflect this + memset( s.CardP()->iExtendedCSD.Ptr()+TExtendedCSD::EEraseGroupDefIndex, TExtendedCSD::EEraseGrpDefEnableHighCapSizes, 1); + } + + SMF_STATE(EStDetermineBusWidthAndClock) + + SMF_INVOKES( DetermineBusWidthAndClockSMST, EStGotBusWidthAndClock ) + + SMF_STATE(EStGotBusWidthAndClock) + + SMF_NEXTS(initSingleCard ? EStExit : EStTestNextCard) + + if(iMultiplexedBus || iCardArray->CardsPresent() == 1) + { + SMF_CALL( ConfigureHighSpeedSMST ) + } + + SMF_GOTONEXTS + + SMF_STATE(EStNoMoreCards) + + + SMF_STATE(EStExit) + m.ResetTraps(); + + SMF_END + } + + + +/** +DetermineBusWidthAndClockSM() + +Reads the extended CSD register for all MMCV4 cards in the stack. +If there is only one MMCV4 card, then an attempt is made to switch +both the card and the controller to a higher clock rate (either 26MHz of 52MHz) +and to a wider bus width (4 or 8 bits). +The clock speed & bus width chosen depend on : + +- whether the card supports it +- whether the controller supports it +- whether the controller has the ability to supply enough current (the current used + by the card can be read from the Extended CSD register) + +*/ +TMMCErr DMMCStack::DetermineBusWidthAndClockSM() + { + enum states + { + EStBegin=0, + EStWritePowerClass, + EStStartBusTest, + EStExit, + EStEnd + }; + + DMMCSession& s = Session(); + TMMCard* cardP = iCardArray->CardP(iSelectedCardIndex); + + SMF_BEGIN + // Trap Switch errors & no-response errors + m.SetTraps(KMMCErrResponseTimeOut | KMMCErrStatus); + + __KTRACE_OPT(KPBUS1, Kern::Printf(">ConfigureHighSpeed(), iCxCardCount %u", iCxCardCount)); + + + SMF_STATE(EStWritePowerClass) + + // Check the card type is valid + // The only currently valid values for this field are 0x01 or 0x03 + TUint cardType = cardP->iExtendedCSD.CardType(); + if (cardType != (TExtendedCSD::EHighSpeedCard26Mhz) && + cardType != (TExtendedCSD::EHighSpeedCard26Mhz | TExtendedCSD::EHighSpeedCard52Mhz)) + { + __KTRACE_OPT(KPBUS1, Kern::Printf("Unsupported card type %u", cardType)); + SMF_GOTOS(EStExit); + } + + // determine the optimum bus width & clock speed which match the power constraints + TUint powerClass; + DetermineBusWidthAndClock( + *cardP, + (iCurrentOpRange == KMMCOCRLowVoltage), + powerClass, + iBusWidthAndClock); + + // If the power class for the chosen width is different from the default, + // send SWITCH cmd and write the POWER_CLASS byte of the EXT_CSD register + if (powerClass > 0) + { + TMMCArgument arg = TExtendedCSD::GetWriteArg( + TExtendedCSD::EWriteByte, + TExtendedCSD::EPowerClassIndex, + powerClass, + 0); + + __KTRACE_OPT(KPBUS1, Kern::Printf(">ConfigureHighSpeed(), Writing to EXT_CSD (EPowerClass), arg %08X", (TUint32) arg)); + s.FillCommandDesc(ECmdSwitch, arg); + SMF_INVOKES(ExecSwitchCommandST, EStStartBusTest) + } + + SMF_STATE(EStStartBusTest) + + if (err != KMMCErrNone) + { + SMF_GOTOS(EStExit); + } + + // We have determined the capabilities of the host and card. + // - Before switching to the required bus width, perform the BUSTEST sequence + SMF_INVOKES(ExecBusTestSMST, EStExit); + + SMF_STATE(EStExit) + m.ResetTraps(); + + SMF_END + } + + +/** +ConfigureHighSpeedSM() + +Reads the extended CSD register for all MMCV4 cards in the stack. +If there is only one MMCV4 card, then an attempt is made to switch +both the card and the controller to a higher clock rate (either 26MHz of 52MHz) +and to a wider bus width (4 or 8 bits). +The clock speed & bus width chosen depend on : + +- whether the card supports it +- whether the controller supports it +- whether the controller has the ability to supply enough current (the current used + by the card can be read from the Extended CSD register) + +*/ +TMMCErr DMMCStack::ConfigureHighSpeedSM() + { + enum states + { + EStBegin=0, + EStConfigureBusWidth, + EStWriteHsTiming, + EStConfigureClock, + EStExit, + EStEnd + }; + + DMMCSession& s = Session(); + TMMCard* cardP = iCardArray->CardP(iSelectedCardIndex); + + SMF_BEGIN + + // Trap Switch errors & no-response errors + m.SetTraps(KMMCErrResponseTimeOut | KMMCErrStatus); + + __KTRACE_OPT(KPBUS1, Kern::Printf(">ConfigureHighSpeed(), iCxCardCount %u", iCxCardCount)); + + cardP->SetHighSpeedClock(0); + + // Check the card type is valid + // The only currently valid values for this field are 0x01 or 0x03 + TUint cardType = cardP->iExtendedCSD.CardType(); + if (cardType != (TExtendedCSD::EHighSpeedCard26Mhz) && + cardType != (TExtendedCSD::EHighSpeedCard26Mhz | TExtendedCSD::EHighSpeedCard52Mhz)) + { + __KTRACE_OPT(KPBUS1, Kern::Printf("Unsupported card type %u", cardType)); + SMF_GOTOS(EStExit); + } + + // If the bus width is 4 or 8, send SWITCH cmd and write the BUS_WIDTH byte of the EXT_CSD register + + if (iBusWidthAndClock != E1Bit20Mhz) + { + TMMCArgument arg = TExtendedCSD::GetWriteArg( + TExtendedCSD::EWriteByte, + TExtendedCSD::EBusWidthModeIndex, + (iBusWidthAndClock & E4BitMask) ? TExtendedCSD::EExtCsdBusWidth4 : TExtendedCSD::EExtCsdBusWidth8, + 0); + + __KTRACE_OPT(KPBUS1, Kern::Printf(">ConfigureHighSpeed(), Writing to EXT_CSD (EBusWidthMode), arg %08X", (TUint32) arg)); + s.FillCommandDesc(ECmdSwitch, arg); + SMF_INVOKES(ExecSwitchCommandST, EStConfigureBusWidth) + } + + SMF_STATE(EStConfigureBusWidth) + + if (err != KMMCErrNone) + { + SMF_GOTOS(EStExit); + } + + // Ensure that the controller is configured for an 4 or 8 bit bus + // - BUSTEST should have already done this + if (iBusWidthAndClock & E4BitMask) + { + DoSetBusWidth(EBusWidth4); + } + else if (iBusWidthAndClock & E8BitMask) + { + DoSetBusWidth(EBusWidth8); + } + // fall through to next state + + SMF_STATE(EStWriteHsTiming) + if (iBusWidthAndClock == E1Bit20Mhz) + SMF_GOTOS(EStExit); + + TMMCArgument arg = TExtendedCSD::GetWriteArg( + TExtendedCSD::EWriteByte, + TExtendedCSD::EHighSpeedInterfaceTimingIndex, + 1, // turn on high speed (26 or 52 Mhz, depending on the card type) + 0); + + __KTRACE_OPT(KPBUS1, Kern::Printf(">ConfigureHighSpeed(), Writing to EXT_CSD (EHighSpeedInterfaceTiming), arg %08X", (TUint32) arg)); + s.FillCommandDesc(ECmdSwitch, arg); + SMF_INVOKES(ExecSwitchCommandST, EStConfigureClock) + + + SMF_STATE(EStConfigureClock) + + if (err != KMMCErrNone) + { + DoSetBusWidth(EBusWidth1); + SMF_GOTOS(EStExit); + } + + cardP->SetHighSpeedClock( + MHZ_TO_KHZ(((iBusWidthAndClock & E52MhzMask) ? + TMMCMachineInfoV4::EClockSpeed52Mhz: + TMMCMachineInfoV4::EClockSpeed26Mhz))); + + SMF_STATE(EStExit) + m.ResetTraps(); + + SMF_END + } + +// Issue a switch command and then wait while he card is in prg state +// +TMMCErr DMMCStack::ExecSwitchCommand() + { + enum states + { + EStBegin=0, + EStSendStatus, + EStGetStatus, + EStEnd + }; + + DMMCSession& s=Session(); + + SMF_BEGIN + SMF_INVOKES(ExecCommandSMST, EStSendStatus) + + SMF_STATE(EStSendStatus) + s.FillCommandDesc(ECmdSendStatus, 0); + SMF_INVOKES(ExecCommandSMST, EStGetStatus) + + SMF_STATE(EStGetStatus) + const TMMCStatus st(s.ResponseP()); + + const TMMCardStateEnum st1 = st.State(); + if (st1 == ECardStatePrg) + { + SMF_INVOKES(ProgramTimerSMST, EStSendStatus); + } + + // Fall through if CURRENT_STATE is not PGM + + SMF_END + } + +// Issue CMD5 to change device status to Sleep mode +// +TMMCErr DMMCStack::ExecSleepCommandSM() + { + enum states + { + EStBegin=0, + EStIndexNxtCard, + EStIssueSleepAwake, + EStSleepAwakeIssued, + EStUpdateStackState, + EStDone, + EStEnd + }; + + DMMCSession& s=Session(); + + SMF_BEGIN + + __KTRACE_OPT(KPBUS1, Kern::Printf(">ExecSleepCommandSM()")); + + iAutoUnlockIndex = -1; + // drop through.... + + SMF_STATE(EStIndexNxtCard) + + __KTRACE_OPT(KPBUS1, Kern::Printf(">EStIndexNxtCard")); + // the cycle is finished when iAutoUnlockIndex == KMaxMultiMediaCardsPerStack + if(iAutoUnlockIndex >= TInt(KMaxMMCardsPerStack)) + { + SMF_GOTOS(EStUpdateStackState); + } + + // Potentionaly more than one eMMC device attached to Controller + // need to select each device and determine if Sleep can be issued + TBool useIndex = EFalse; + for (++iAutoUnlockIndex; iAutoUnlockIndex < TInt(KMaxMMCardsPerStack); ++iAutoUnlockIndex) + { + // card must be present and a valid 4.3 device + TMMCard* cardP = iCardArray->CardP(iAutoUnlockIndex); + useIndex = ( (cardP->IsPresent()) && + (cardP->ExtendedCSD().ExtendedCSDRev() >= 3) && + (cardP->iStatus != ECardStateSlp) ); + + // don't increment iAutoUnlockIndex in continuation loop + if (useIndex) + { + __KTRACE_OPT(KPBUS1, Kern::Printf(">Card[%d]: is v4.3 device",iAutoUnlockIndex)); + break; + } + } + + if (!useIndex) + { + SMF_GOTOS(EStUpdateStackState); + } + + TMMCard &cd = *(iCardArray->CardP(iAutoUnlockIndex)); + s.SetCard(&cd); + + s.PushCommandStack(); + s.FillCommandDesc(ECmdSleepAwake, KBit15); + + // CMD5 is an AC command, ExecCommandSMST will automatically issue a deselect + SMF_INVOKES(ExecCommandSMST, EStSleepAwakeIssued) + + SMF_STATE(EStSleepAwakeIssued) + + __KTRACE_OPT(KPBUS1, Kern::Printf(">EStSleepAwakeIssued!")); + + const TMMCStatus status(s.ResponseP()); + + s.PopCommandStack(); + + if(status.State() == ECardStateStby || status.State() == ECardStateSlp) + { + // R1b is issued before Sleep state is achieved and + // will therefore return the previous state which was Standby + __KTRACE_OPT(KPBUS1, Kern::Printf(">Card[%d]: SLEEP",iAutoUnlockIndex)); + + // Ensure card status is ECardStateSlp + s.CardP()->iStatus.UpdateState(ECardStateSlp); + + // Update Stack state to indicate media is sleep mode + iStackState |= KMMCStackStateSleep; + } + else + { + __KTRACE_OPT(KPBUS1, Kern::Printf(">Card[%d]: UNKNOWN",iAutoUnlockIndex)); + return (KMMCErrStatus); + } + + SMF_GOTOS(EStIndexNxtCard) + + SMF_STATE(EStUpdateStackState) + + if (iStackState & KMMCStackStateSleep) + { + // Media has been transitioned to sleep state + iStackState &= ~KMMCStackStateSleep; + + // VccCore may now be switched off + iSocket->iVccCore->SetState(EPsuOff); + } +// else + // No media transitioned to sleep state or media was already in sleep state, + // nothing to do... + + SMF_STATE(EStDone) + + __KTRACE_OPT(KPBUS1, Kern::Printf("ExecAwakeCommandSM()")); + + // Call PSL to ensure VccQ is powered up before continuing + SMF_INVOKES( DoWakeUpSMST, EStPoweredUp ) + + SMF_STATE(EStPoweredUp) + + __KTRACE_OPT(KPBUS1, Kern::Printf("VccQ Powered Up")); + + //Issue CMD5 to awaken media + s.PushCommandStack(); + s.FillCommandDesc(ECmdSleepAwake); + s.Command().iArgument.SetRCA(s.CardP()->RCA()); + + SMF_INVOKES(IssueCommandCheckResponseSMST, EStAwakeIssued) + + SMF_STATE(EStAwakeIssued) + + __KTRACE_OPT(KPBUS1, Kern::Printf(">>EStAwakeIssued!")); + + TMMCStatus status(s.ResponseP()); + + if(status.State() == ECardStateStby || status.State() == ECardStateSlp) + { + // R1b is issued before Standby state is achieved and + // will therefore return the previous state which was Sleep + __KTRACE_OPT(KPBUS1, Kern::Printf(">Card[%d]: STANDBY",iAutoUnlockIndex)); + + s.CardP()->iStatus.UpdateState(ECardStateStby); + } + else + { + __KTRACE_OPT(KPBUS1, Kern::Printf(">Card[%d]: UNKNOWN",iAutoUnlockIndex)); + return (KMMCErrStatus); + } + + s.PopCommandStack(); + + SMF_STATE(EStDone) + + __KTRACE_OPT(KPBUS1, Kern::Printf("= TMMCMachineInfoV4::EClockSpeed26Mhz && maxBusWidth >= EBusWidth4) + controllerWidthAndClock|= E4Bits26Mhz; + if (maxClockSpeedInMhz >= TMMCMachineInfoV4::EClockSpeed52Mhz && maxBusWidth >= EBusWidth4) + controllerWidthAndClock|= E4Bits52Mhz; + + if (maxClockSpeedInMhz >= TMMCMachineInfoV4::EClockSpeed26Mhz && maxBusWidth >= EBusWidth8) + controllerWidthAndClock|= E8Bits26Mhz; + if (maxClockSpeedInMhz >= TMMCMachineInfoV4::EClockSpeed52Mhz && maxBusWidth >= EBusWidth8) + controllerWidthAndClock|= E8Bits52Mhz; + + // Get the bus widths & clocks supported by the card + TUint32 cardWidthAndClock = E1Bit20Mhz; + + if (aCard.ExtendedCSD().CardType() & TExtendedCSD::EHighSpeedCard26Mhz) + cardWidthAndClock|= E4Bits26Mhz | E8Bits26Mhz; + if (aCard.ExtendedCSD().CardType() & TExtendedCSD::EHighSpeedCard52Mhz) + cardWidthAndClock|= E4Bits52Mhz | E8Bits52Mhz; + + + // Get the bus widths & clocks supported by both the controller & card + // by AND-ing them together, + TUint32 supportedWidthAndClock = controllerWidthAndClock & cardWidthAndClock; + + // Iterate through all the modes (starting at the fastest) until we find one + // that is supported by both card & controller and fits the power constraints + TUint powerClass = 0; + for (TUint targetWidthAndClock = E8Bits52Mhz; targetWidthAndClock != 0; targetWidthAndClock>>= 1) + { + if ((supportedWidthAndClock & targetWidthAndClock) == 0) + continue; + + powerClass = GetPowerClass(aCard, TBusWidthAndClock(targetWidthAndClock), aLowVoltage); + + // can the controller support this power class ? + if (powerClass > (aLowVoltage ? machineInfo.iLowVoltagePowerClass : machineInfo.iHighVoltagePowerClass )) + continue; + + aPowerClass = powerClass; + aBusWidthAndClock = TBusWidthAndClock(targetWidthAndClock); + break; + } + + __KTRACE_OPT(KPBUS1, Kern::Printf("aPowerClass %u, targetWidthAndClock = %08X", aPowerClass, aBusWidthAndClock)); + } + +TUint DMMCStack::GetPowerClass(const TMMCard& aCard, TBusWidthAndClock aWidthAndClock, TBool aLowVoltage) + { + // The power class for 4 bit bus configurations is in the low nibble, + // The power class for 8 bit bus configurations is in the high nibble, + #define LO_NIBBLE(val) (val & 0x0F) + #define HI_NIBBLE(val) ((val >> 4) & 0x0F) + + const TExtendedCSD& extendedCSD = aCard.ExtendedCSD(); + + TUint powerClass = 0; + + if (aLowVoltage) + { + switch( aWidthAndClock) + { + case E4Bits26Mhz: + powerClass = LO_NIBBLE(extendedCSD.PowerClass26Mhz195V()); + break; + case E4Bits52Mhz: + powerClass = LO_NIBBLE(extendedCSD.PowerClass52Mhz195V()); + break; + case E8Bits26Mhz: + powerClass = HI_NIBBLE(extendedCSD.PowerClass26Mhz195V()); + break; + case E8Bits52Mhz: + powerClass = HI_NIBBLE(extendedCSD.PowerClass52Mhz195V()); + break; + case E1Bit20Mhz: + powerClass = 0; + break; + } + } + else + { + switch( aWidthAndClock) + { + case E4Bits26Mhz: + powerClass = LO_NIBBLE(extendedCSD.PowerClass26Mhz360V()); + break; + case E4Bits52Mhz: + powerClass = LO_NIBBLE(extendedCSD.PowerClass52Mhz360V()); + break; + case E8Bits26Mhz: + powerClass = HI_NIBBLE(extendedCSD.PowerClass26Mhz360V()); + break; + case E8Bits52Mhz: + powerClass = HI_NIBBLE(extendedCSD.PowerClass52Mhz360V()); + break; + case E1Bit20Mhz: + powerClass = 0; + break; + } + } + + return powerClass; + } + +// +// Execute the BUSTEST procedure for a given bus width +// +TMMCErr DMMCStack::ExecBusTestSM() + { + enum states + { + EStBegin=0, + EstSendBusTest_W, + EstSendBusTest_R, + EstGotBusTest_R, + EStExit, + EStEnd + }; + + DMMCSession& s = Session(); + + SMF_BEGIN + // + // Start the BUSTEST sequence at the maximum supported by the PSL + // - iSpare[0] keeps track of the current bus width + // + if (iBusWidthAndClock & E8BitMask) + { + iSpare[0] = EBusWidth8; + __KTRACE_OPT(KPBUS1, Kern::Printf("...Hardware supports 8-bit bus")); + } + else if(iBusWidthAndClock & E4BitMask) + { + iSpare[0] = EBusWidth4; + __KTRACE_OPT(KPBUS1, Kern::Printf("...Hardware supports 4-bit bus")); + } + else + { + __KTRACE_OPT(KPBUS1, Kern::Printf("...Hardware supports 1-bit bus")); + iSpare[0] = EBusWidth1; + } + + // remove KMMCModeCardControlled so that IssueCommandCheckResponseSMST doesn't try to + // override the bus width & clock rate using the card settings + iConfig.RemoveMode( KMMCModeCardControlled ); + + SMF_STATE(EstSendBusTest_W) + // + // Issue the BUSTEST_W command + // + TInt length = 2; + switch(iSpare[0]) + { + case EBusWidth8: + // Set the host to 8-bit mode + __KTRACE_OPT(KPBUS1, Kern::Printf("BUSTEST : EBusWidth8")); + DoSetBusWidth(EBusWidth8); + iPSLBuf[0] = 0x55; + iPSLBuf[1] = 0xaa; + break; + + case EBusWidth4: + // Set the host to 4-bit mode + __KTRACE_OPT(KPBUS1, Kern::Printf("BUSTEST : EBusWidth4")); + DoSetBusWidth(EBusWidth4); + iPSLBuf[0] = 0x5a; + iPSLBuf[1] = 0x00; + break; + + case EBusWidth1: + default: + // Set the host to 1-bit mode + __KTRACE_OPT(KPBUS1, Kern::Printf("BUSTEST : EBusWidth1")); + DoSetBusWidth(EBusWidth1); + iPSLBuf[0] = 0x40; + iPSLBuf[1] = 0x00; + break; + } + + // Issue BUSTEST_W + + __KTRACE_OPT(KPBUS1, Kern::Printf("...Issue BUSTEST_W [%02x:%02x]", iPSLBuf[1], iPSLBuf[0])); + + m.SetTraps(KMMCErrDataCRC); // CRC check is optional for BUSTEST + + s.FillCommandDesc(ECmdBustest_W); + s.FillCommandArgs(0, length, &iPSLBuf[0], length); + SMF_INVOKES(IssueCommandCheckResponseSMST, EstSendBusTest_R) + + SMF_STATE(EstSendBusTest_R) + // + // Issue the BUSTEST_R command + // + __KTRACE_OPT(KPBUS1, Kern::Printf("...got BUSTEST_W response : %02x", err)); + + if(err == KMMCErrNone || err == KMMCErrDataCRC) + { + __KTRACE_OPT(KPBUS1, Kern::Printf("...sending BUSTEST_R")); + + iPSLBuf[0] = 0; + iPSLBuf[1] = 0; + + s.FillCommandDesc(ECmdBustest_R); + s.FillCommandArgs(0, 2, &iPSLBuf[0], 2); + SMF_INVOKES(IssueCommandCheckResponseSMST, EstGotBusTest_R) + } + else + { + SMF_RETURN(KMMCErrNotSupported); + } + + SMF_STATE(EstGotBusTest_R) + // + // Validate the BUSTEST_R data with that issued by BUSTEST_W + // + __KTRACE_OPT(KPBUS1, Kern::Printf("...got BUSTEST_R response [%02x:%02x] : err(%02x)", iPSLBuf[1], iPSLBuf[0], err)); + + TBool retry = EFalse; + TBool is52MHzSupported = (iBusWidthAndClock & E52MhzMask) ? (TBool)ETrue : (TBool)EFalse; + + switch(iSpare[0]) + { + case EBusWidth8: + { + if(iPSLBuf[0] == 0xAA && iPSLBuf[1] == 0x55) + { + // 8-Bit bus supported + iBusWidthAndClock = is52MHzSupported ? E8Bits52Mhz : E8Bits26Mhz; + } + else + { + // 8-Bit bus not supported - retry with 4-Bit + retry = ETrue; + iSpare[0] = EBusWidth4; + } + break; + } + + case EBusWidth4: + { + if(iPSLBuf[0] == 0xA5) + { + // 4-Bit Bus Supported + iBusWidthAndClock = is52MHzSupported ? E4Bits52Mhz : E4Bits26Mhz; + } + else + { + // 4-Bit bus not supported - retry with 1-Bit + retry = ETrue; + iSpare[0] = EBusWidth1; + } + break; + } + + case EBusWidth1: + { + if((iPSLBuf[0] & 0xC0) == 0x80) + { + // 1-Bit Bus Supported + iBusWidthAndClock = E1Bit20Mhz; + } + + // Failed to perform BUSTEST with 1-Bit bus. + // - We can't recover from this, but let's continue using low-speed 1-Bit mode + iBusWidthAndClock = E1Bit20Mhz; + break; + } + + default: + DMMCSocket::Panic(DMMCSocket::EMMCBadBusWidth); + break; + } + + if(retry) + { + __KTRACE_OPT(KPBUS1, Kern::Printf("...BUSTEST Failed : Retry")); + SMF_GOTOS(EstSendBusTest_W); + } + + switch(iBusWidthAndClock) + { + case E1Bit20Mhz: + iCardArray->CardP(iSelectedCardIndex)->SetBusWidth(1); + break; + + case E4Bits26Mhz: + case E4Bits52Mhz: + iCardArray->CardP(iSelectedCardIndex)->SetBusWidth(4); + break; + + case E8Bits26Mhz: + case E8Bits52Mhz: + iCardArray->CardP(iSelectedCardIndex)->SetBusWidth(8); + break; + } + + __KTRACE_OPT(KPBUS1, Kern::Printf("...BUSTEST OK")); + + DoSetBusWidth(EBusWidth1); + + SMF_STATE(EStExit) + iConfig.SetMode( KMMCModeCardControlled ); + + SMF_END + } + + +/** + * PSL-supplied method to retrieve an interface. + * The caller should set aInterfacePtr to NULL before calling + * the PSL should only modify aInterfacePtr if it supports the interface + * The default implementation here does nothing + * Replaces Dummy2() +*/ +EXPORT_C void DMMCStack::GetInterface(TInterfaceId aInterfaceId, MInterface*& aInterfacePtr) + { + if (aInterfaceId == KInterfaceCancelSession) + { + DMMCSession* session = (DMMCSession*&) aInterfacePtr; + Abort(session); + UnlockStack(session); + } + + } + + +TMMCErr DMMCStack::GoIdleSM() +/** + * Issues GO_IDLE_STATE twice with a RetryGap between them. + * After that the bus context ought to be considered as "known". + * @return MMC error code + */ + { + enum states + { + EStBegin=0, + EStIdleLoop, + EStIdleEndCheck, + EStEnd + }; + + DMMCSession& s=Session(); + + SMF_BEGIN + + s.FillCommandDesc( ECmdGoIdleState, 0 ); + iCxPollRetryCount = KMMCIdleCommandsAtRestart; + + SMF_STATE(EStIdleLoop) + SMF_INVOKES( ExecCommandSMST, EStIdleEndCheck ) + + SMF_STATE(EStIdleEndCheck) + + if( --iCxPollRetryCount > 0 ) + SMF_INVOKES( RetryGapTimerSMST, EStIdleLoop ) + + iStackState &= ~(KMMCStackStateDoDeselect|KMMCStackStateBusInconsistent); + iSelectedCard = 0; + + // According to the spec, the default bus width after power up or GO_IDLE is 1 bit bus width + DoSetBusWidth(EBusWidth1); + + SMF_END + } + +EXPORT_C TMMCErr DMMCStack::AcquireStackSM() +/** + * This macro acquires new cards in an MMC - bus topology stack. + * It starts with the Controller reading the operating conditions of the + * cards in the stack (SEND_OP_COND - CMD1). Then, any new cards in the stack + * are identified (ALL_SEND_CID - CMD2) and each one is assigned a relative + * card address (SET_RCA - CMD3). This is done by systematically broadcasting + * CMD2 to all cards on the bus until all uninitialized cards have responded. + * Finally the card specific data (SEND_CSD - CMD9) is read from each card. + * @return MMC error code + */ + { + enum states + { + EStBegin=0, + EStIdle, + EStFullRangeDone, + EStSetRangeLoop, + EStSetRangeBusyCheck, + EStCIDLoop, + EStSendCIDIssued, + EStCIDsDone, + EStCSDLoop, + EStSendCSDDone, + EStMergeCards, + EStReMergeCards, + EStEnd + }; + + DMMCSession& s=Session(); + DMMCPsu* psu=(DMMCPsu*)iSocket->iVcc; + + SMF_BEGIN + + iRCAPool.ReleaseUnlocked(); + iCxPollRetryCount = 0; // Reset max number of poll attempts on card busy + + SMF_INVOKES( GoIdleSMST, EStIdle ) + + SMF_STATE(EStIdle) + + // If this platform doesn't support an adjustable voltage PSU then there is + // no point in interogating the card(s) present for their supported range + if ( !(psu->VoltageSupported()&KMMCAdjustableOpVoltage) ) + { + // if the PSU isn't adjustable then it can't support low voltage mode + iCurrentOpRange&= ~KMMCOCRLowVoltage; + s.FillCommandDesc(ECmdSendOpCond, (iCurrentOpRange | KMMCOCRAccessModeHCS | KMMCOCRBusy)); // Range supported + Busy bit (iArgument==KBit31) + SMF_GOTOS( EStSetRangeLoop ) + } + + // Interrogate card(s) present - issue CMD1 with omitted voltage range + s.FillCommandDesc( ECmdSendOpCond, KMMCOCRAccessModeHCS | KMMCOCRBusy); // Full range + Sector Access + Busy bit (iArgument==KBit31) + m.SetTraps( KMMCErrResponseTimeOut ); + + SMF_INVOKES( ExecCommandSMST, EStFullRangeDone ) + + SMF_STATE(EStFullRangeDone) + + if( err ) // If timeout + { + iConfig.RemoveMode( KMMCModeEnableTimeOutRetry ); // There is no point to do it second time + } + else + { + // Cards responded with Op range - evaluate the common subset with the current setting + // Dont worry aboout the busy bit for now, we'll check that when we repeat the command + TUint32 newrange = (TMMC::BigEndian32(s.ResponseP()) & ~KMMCOCRBusy); + newrange &= iCurrentOpRange; + + if (newrange==0) + { + // One or more card is incompatible with our h/w + if (iMaxCardsInStack<=1) + return( KMMCErrNotSupported ); // There can only be one card - we don't support it. + else + // Force the default range + iCurrentOpRange=(psu->VoltageSupported() & ~KMMCAdjustableOpVoltage); + } + else + iCurrentOpRange=newrange; // OK, new cards are compatible + } + + // If platform and the card both support low voltage mode (1.65 - 1.95v), switch + if (iCurrentOpRange & KMMCOCRLowVoltage) + { + iCurrentOpRange = KMMCOCRLowVoltage; + SMF_INVOKES( SwitchToLowVoltageSMST, EStSetRangeLoop ) + } + + SMF_STATE(EStSetRangeLoop) + + // Repeat CMD1 this time setting Current Op Range + s.Command().iArgument = iCurrentOpRange | KMMCOCRAccessModeHCS | KMMCOCRBusy; + + m.SetTraps( KMMCErrResponseTimeOut ); + + SMF_INVOKES( ExecCommandSMST, EStSetRangeBusyCheck ) + + SMF_STATE(EStSetRangeBusyCheck) + + if( !err ) + { + // Bit31 of the OCR response is low if the cards are still powering up. + const TUint32 ocrResponse = TMMC::BigEndian32(s.ResponseP()); + + const TBool isBusy = ((ocrResponse & KMMCOCRBusy) == 0); + __KTRACE_OPT(KPBUS1,Kern::Printf("-mmc:upd:bsy%d", isBusy)); + + if (isBusy) + { + // Some cards are still busy powering up. Check if we should timeout + if ( ++iCxPollRetryCount > iConfig.OpCondBusyTimeout() ) + return( KMMCErrBusTimeOut ); + m.ResetTraps(); + SMF_INVOKES( RetryGapTimerSMST, EStSetRangeLoop ) + } + + iSpare[0] = 0; + + if((ocrResponse & KMMCOCRAccessModeMask) == KMMCOCRAccessModeHCS) + { + __KTRACE_OPT(KPBUS1, Kern::Printf("Found large MMC card.")); + iSpare[0] = KMMCardIsHighCapacity; + } + } + + iConfig.SetMode( EffectiveModes(s.iConfig) & KMMCModeEnableTimeOutRetry ); // Restore original setting + + // All cards are now ready and notified of the voltage range - ask ASSP to set it up + psu->SetVoltage(iCurrentOpRange); + if (psu->SetState(EPsuOnFull) != KErrNone) + return(KMMCErrHardware); + + iCardArray->InitNewCardScan(); // Collect new cards, one by one + + SMF_STATE(EStCIDLoop) + + if ( iCardArray->NewCardCount() >= iMaxCardsInStack ) + SMF_GOTOS( EStCIDsDone ) + + s.FillCommandDesc( ECmdAllSendCID, 0 ); + m.SetTraps( KMMCErrResponseTimeOut ); + + SMF_INVOKES( ExecCommandSMST, EStSendCIDIssued ) + + SMF_STATE(EStSendCIDIssued) + + if( !err ) + { + // A card responded with a CID. Create a new card entry in the card array + // and initialise this entry with the CID. The card array allocates it an + // RCA, either the old RCA if we have seen this card before, or a new one. + TRCA rca; + iCardArray->AddNewCard(s.ResponseP(),&rca); // Response is CID + + // Now assign the new RCA to the card + s.FillCommandDesc( ECmdSetRelativeAddr, TMMCArgument(rca) ); + m.ResetTraps(); + SMF_INVOKES( ExecCommandSMST, EStCIDLoop ) + } + + SMF_STATE(EStCIDsDone) + + // All cards are initialised; get all their CSDs + m.ResetTraps(); // We are no longer processing any errors + + if( iCardArray->NewCardCount()==0 ) + SMF_EXIT // No new cards acquired + + iCxCardCount=0; // New cards index + s.FillCommandDesc( ECmdSendCSD ); + + SMF_STATE(EStCSDLoop) + + s.Command().iArgument = TMMCArgument(iCardArray->NewCardP(iCxCardCount)->iRCA); + SMF_INVOKES( ExecCommandSMST, EStSendCSDDone ) + + SMF_STATE(EStSendCSDDone) + + // Store the CSD in the new card entry + TMMCard* cardP = iCardArray->NewCardP(iCxCardCount); + cardP->iCSD = s.ResponseP(); + + // Perform MMC Specific parsing of the CSD structure + TUint specVers = cardP->CSD().SpecVers(); // 1 => 1.4, 2 => 2.0 - 2.2, 3 => 3.1 + + if ((specVers >= 2) && (cardP->CSD().CCC() & KMMCCmdClassLockCard)) + { + cardP->iFlags |= KMMCardIsLockable; + } + + if(iSpare[0] == KMMCardIsHighCapacity) + { + cardP->iFlags |= KMMCardIsHighCapacity; + } + + if( ++iCxCardCount < (TInt)iCardArray->NewCardCount() ) + SMF_GOTOS( EStCSDLoop ) + + SMF_NEXTS(EStMergeCards) + + SMF_STATE(EStMergeCards) + + // Merging the old card info with newly acquired cards (we will ask each card for status + // to determine whether it's really present later). + if( SchedGetOnDFC() ) + SMF_WAIT + if ( iCardArray->MergeCards(ETrue)==KErrNone ) + SMF_EXIT // Completed successfully + + SMF_INVOKES( CheckStackSMST, EStReMergeCards ) // No space so check if any cards have gone + + SMF_STATE(EStReMergeCards) + + if( SchedGetOnDFC() ) + SMF_WAIT + if ( iCardArray->MergeCards(EFalse)!=KErrNone ) // There are more cards in the stack than we can handle + return(KMMCErrTooManyCards); + + SMF_END + } + + +/** + * Power down the bus and power it up again in low-voltage mode + * + * @return MMC error code. + */ +TMMCErr DMMCStack::SwitchToLowVoltageSM() + { + enum states + { + EStBegin=0, + EStPoweredUp, + EStClockOn, + EStEnd + }; + + __KTRACE_OPT(KPBUS1,Kern::Printf(">MMC:SwLowVolt")); + + DMMCPsu* psu=(DMMCPsu*)iSocket->iVcc; + + SMF_BEGIN + // turn power off + DoPowerDown(); + psu->SetState(EPsuOff); + + // turn power back on in low voltage mode + psu->SetVoltage(iCurrentOpRange); + if (psu->SetState(EPsuOnFull) != KErrNone) + return(KMMCErrHardware); + + SMF_INVOKES( DoPowerUpSMST, EStPoweredUp ) + + SMF_STATE(EStPoweredUp) + // turn the clock back on + SMF_INVOKES( InitClockOnSMST, EStClockOn ) // Feed init clock to the bus + + SMF_STATE(EStClockOn) + // wait for 1ms and then 74 clock cycles + // 74 clock cylces @ 400 Khz = 74 / 400,000 = 0.000185 secs = 0.185 ms + // so total wait = 1.185 ms + SMF_INVOKES(LowVoltagePowerupTimerSMST, EStEnd); + + SMF_END + + } + +inline TMMCErr DMMCStack::CIMCheckStackSM() +/** + * Performs the CIM_CHECK_STACK macro (with pre-emption disabled). + * @return MMC error code + */ + { + enum states + { + EStBegin=0, + EStFinish, + EStEnd + }; + + DMMCSession& s=Session(); + + SMF_BEGIN + + // This macro works naked and must not be preempted + iConfig.RemoveMode( KMMCModeEnablePreemption | KMMCModeCardControlled ); + s.iState |= KMMCSessStateInProgress; + + SMF_INVOKES( CheckStackSMST, EStFinish ) + + SMF_STATE(EStFinish) + + s.iState &= ~KMMCSessStateInProgress; + + SMF_END + } + +inline TMMCErr DMMCStack::CheckStackSM() +/** + * For each card in iCards[], sends CMD13 to see if still there. + * If not, calls DeclareCardAsGone(). Frees up space for new cards. + * @return MMC error code + */ + { + enum states + { + EStBegin=0, + EStLoop, + EStCardSelectedGotStatus, + EStCardDeselected, + EStEnd + }; + + DMMCSession& s=Session(); + + SMF_BEGIN + + iCxCardCount=-1; + m.SetTraps( KMMCErrResponseTimeOut ); + + SMF_STATE(EStLoop) + + if ( ++iCxCardCount == (TInt)iMaxCardsInStack ) + SMF_EXIT + + if ( !iCardArray->CardP(iCxCardCount)->IsPresent() ) + SMF_GOTOS( EStLoop ) // card's not present + + TUint32 arg = TUint32(iCardArray->CardP(iCxCardCount)->RCA()) << 16; + s.FillCommandDesc(ECmdSelectCard, arg); + SMF_INVOKES(ExecCommandSMST, EStCardSelectedGotStatus) + + SMF_STATE(EStCardSelectedGotStatus) + + __KTRACE_OPT(KPBUS1, Kern::Printf("-mst:cssm:err%08x", err)); + + if(err) + { + // Timeout - the card is no longer present so remove from the card array + iCardArray->DeclareCardAsGone(iCxCardCount); + SMF_GOTOS( EStLoop ) + } + + TMMCard& card=*(iCardArray->CardP(iCxCardCount)); + card.iStatus=s.ResponseP(); + + // This function is only called as part of the power up sequence, so + // take the opportunity to record if it has a password + if((card.iStatus & KMMCStatCardIsLocked) != 0) + { + card.iFlags|=KMMCardHasPassword; + } + + s.FillCommandDesc(ECmdSelectCard, 0); + SMF_INVOKES(ExecCommandSMST, EStCardDeselected) + + SMF_STATE(EStCardDeselected) + SMF_GOTOS( EStLoop ) + + SMF_END + } + +inline TMMCErr DMMCStack::CheckLockStatusSM() +/* + * Called as part of the power-up sequence, this determined if the card is locked or has a password. + * + * If the card reports itself as being unlocked and there is a mapping in the password store, + * then the stored password is used to attempt to lock the card. The overall aim of this is + * to ensure that if a card always powers up in the locked state if it contains a known password. + * + * This ensures that cards that are still unlocked after a power down/power up sequence do not + * end up having their passwords removed from the store, which can happen in environments where + * the PSU voltage level is not monitored - in such systems, we cannot guarantee that a card will + * be fully reset and power up locked, hence the need to attempt to lock the card. + * + * @return MMC error code + */ + { + enum states + { + EStBegin=0, + EStLoop, + EStCardSelectedGotStatus, + EStCheckLockStatus, + EStCardDeselected, + EStEnd + }; + + DMMCSession& s=Session(); + + SMF_BEGIN + + iCxCardCount=-1; + m.SetTraps( KMMCErrResponseTimeOut ); + iMinorBufLen = KMinMinorBufSize; + + SMF_STATE(EStLoop) + + if ( ++iCxCardCount == (TInt)iMaxCardsInStack ) + SMF_EXIT + + if ( !iCardArray->CardP(iCxCardCount)->IsPresent() ) + SMF_GOTOS( EStLoop ) // card's not present + + TUint32 arg = TUint32(iCardArray->CardP(iCxCardCount)->RCA()) << 16; + s.FillCommandDesc(ECmdSelectCard, arg); + SMF_INVOKES(ExecCommandSMST, EStCardSelectedGotStatus) + + SMF_STATE(EStCardSelectedGotStatus) + + __KTRACE_OPT(KPBUS1, Kern::Printf("-mst:cssm:err%08x", err)); + if ( !err ) + { + TMMCard& card=*(iCardArray->CardP(iCxCardCount)); + card.iStatus=s.ResponseP(); // Got the response + + iMinorBufLen = Max(iMinorBufLen, 1 << card.MaxReadBlLen()); + + // this function is only called as part of the power up sequence, so + // take the opportunity to record if it has a password + if((card.iStatus & KMMCStatCardIsLocked) != 0) + { + card.iFlags |= KMMCardHasPassword; + } + + // If the status suggests that the card is unlocked, we test + // for the presence of a password by attempting to lock the card + // (if we have a password in the store). This handles conditions + // where a card has not been fully powered down before reapplying power. + if(!(card.iFlags & KMMCardHasPassword)) + { + TMapping* pmp = iSocket->iPasswordStore->FindMappingInStore(card.CID()); + if(pmp) + { + if(pmp->iState == TMapping::EStValid) + { + const TInt kPWD_LEN = pmp->iPWD.Length(); + iPSLBuf[0] = KMMCLockUnlockLockUnlock; // LOCK_UNLOCK = 1, SET_PWD = 0, CLR_PWD = 0 + iPSLBuf[1] = static_cast(kPWD_LEN); + TPtr8 pwd(&iPSLBuf[2], kPWD_LEN); + pwd.Copy(pmp->iPWD); + + const TInt kBlockLen = 1 + 1 + kPWD_LEN; + + // Need to use CIMReadWriteBlocksSMST to ensure that the + // card is connected and the block length is set correctly + s.SetCard(iCardArray->CardP(iCxCardCount)); + m.SetTraps(KMMCErrStatus | KMMCErrUpdPswd); + s.FillCommandDesc(ECmdLockUnlock); + s.FillCommandArgs(0, kBlockLen, iPSLBuf, kBlockLen); + + TMMCCommandDesc& cmd = s.Command(); + cmd.iUnlockRetries = 0; + + SMF_INVOKES(CIMReadWriteBlocksSMST,EStCheckLockStatus) + } + } + } + } + + SMF_GOTOS( EStLoop ) + + SMF_STATE(EStCheckLockStatus) + + __KTRACE_OPT(KPBUS1, Kern::Printf("-mst:cssm:err%08x", err)); + + if ((err & KMMCErrUpdPswd) || + ((err & KMMCErrStatus) && (s.LastStatus().Error() == KMMCStatErrLockUnlock))) + { + // ECMDLockUnlock (with LockUnlockLockUnlock param) succeeded. + // (either locked successfully, or we have attempted to lock a locked card) + // Now determine if the card really is locked by checking the lock status. + TMMCard& card=*(iCardArray->CardP(iCxCardCount)); + card.iStatus=s.LastStatus(); + if((card.iStatus & KMMCStatCardIsLocked) != 0) + { + card.iFlags |= KMMCardHasPassword; + } + } + + s.FillCommandDesc(ECmdSelectCard, 0); + SMF_INVOKES(ExecCommandSMST, EStCardDeselected) + + SMF_STATE(EStCardDeselected) + SMF_GOTOS( EStLoop ) + + SMF_END + } + +EXPORT_C TMMCErr DMMCStack::ModifyCardCapabilitySM() +// +// This function provides a chance to modify the capability of paticular cards. +// Licensee may overide this function to modify certain card's capability as needed. +// A state machine is needed in derived function and function of base class should be +// called in order to act more generic behaviour. +// + { + enum states + { + EStBegin=0, + EStEnd + }; + + SMF_BEGIN + + SMF_END + } + +// +// Timers +// + +inline TMMCErr DMMCStack::PollGapTimerSM() +/** + * Starts the poll timer. + * + * This may be used when executing CIM_UPDATE_ACQ when handling cards which are + * slow to power-up/reset and return busy following the issuing of CMD1. + * + * @return MMC error code. + */ + { + enum states + { + EStBegin=0, + EStEnd + }; +#ifdef __EPOC32__ + DMMCSession& s=Session(); +#endif + + SMF_BEGIN + +#ifdef __EPOC32__ + s.SynchBlock( KMMCBlockOnPollTimer ); + s.iPollTimer.OneShot(KMMCPollGapInMilliseconds,EFalse); + + SMF_EXITWAIT +#endif + + SMF_END + } + +inline TMMCErr DMMCStack::RetryGapTimerSM() +/** + * Starts the retry timer. + * + * This may be used when executing CIM_UPDATE_ACQ. When initialising the stack, + * CMD0 is issued twice to get the bus in a known state and this timer is used + * to time the gap between issuing the two CMD0 commands. + * + * @return MMC error code. + */ + { + enum states + { + EStBegin=0, + EStEnd + }; +#ifdef __EPOC32__ + DMMCSession& s=Session(); +#endif + + SMF_BEGIN + +#ifdef __EPOC32__ + s.SynchBlock( KMMCBlockOnRetryTimer ); + s.iRetryTimer.OneShot(KMMCRetryGapInMilliseconds,EFalse); + + SMF_EXITWAIT +#endif + + SMF_END + } + +inline TMMCErr DMMCStack::ProgramTimerSM() +/** + * Starts the program timer. + * + * This is used during write operartions to a card to sleep for an PSL-dependent period + * between issuing send status commands (CMD13). This is required in order to check when + * the card has finished writing its data to payload memory. + * + * @return MMC error code. + */ + { + enum states + { + EStBegin = 0, + EStEnd + }; + +#ifdef __EPOC32__ + DMMCSession &s = Session(); +#endif + + SMF_BEGIN +#ifdef __EPOC32__ + s.SynchBlock(KMMCBlockOnPgmTimer); + s.iProgramTimer.Cancel(); + s.iProgramTimer.OneShot(ProgramPeriodInMilliSeconds(),EFalse); + + SMF_EXITWAIT +#endif + SMF_END + } + +TMMCErr DMMCStack::LowVoltagePowerupTimerSM() +/** + * Starts the low voltage power-up timer + * NB Re-uses the retry gap timer. + * + * This is used after powering the bus off and then on after discovering that + * both the controller and card support low voltage operation. + * + * @return MMC error code. + */ + { + enum states + { + EStBegin = 0, + EStEnd + }; + +#ifdef __EPOC32__ + DMMCSession &s = Session(); +#endif + + SMF_BEGIN +#ifdef __EPOC32__ + s.SynchBlock(KMMCBlockOnRetryTimer); + s.iRetryTimer.OneShot(KMMCLowVoltagePowerUpTimeoutInMilliseconds,EFalse); + + SMF_EXITWAIT +#endif + SMF_END + } + + +inline TMMCErr DMMCStack::ExecCommandSM() +/** + * The main command executor. + * Depending on the main command being issued, this macro may result in the issuing of whole sequence + * of commands as it prepares the bus for the command in question. + * + * In certain circumstances, this first issues one or more de-select commands (CMD7 + reserved RCA) + * to get the bus in a known state. It then analyses the main command and if necessary, selects the + * card in question (CMD7 + target RCA). + * + * For block transfer commands, it will set the block length on the card concerned (CMD16) if this has + * not been done already. Likewise, for SD Cards, if the bus width has not yet been set-up, it will issue + * the appropriate bus width command (ACMD6). + * + * Finally it issues the main command requested before performing any error recovery that may be necessary + * following this. + * + * In all cases it calls the generic layer child function IssueCommandCheckResponseSM() to execute each command. + * + * @return MMC error code + */ + { + enum states + { + EStBegin=0, + EStExecCmd, + EStRetry, + EStDeselectLoop, + EStDeselectEndCheck, + EStAnalyseCommand, + EStSelectDone, + EStBlockCountCmdIssued, + EStTestAppCommand, + EStIssueAppCommandDone, + EStIssueCommand, + EStCommandIssued, + EStErrRecover, + EStEnd + }; + + DMMCSession& s=Session(); + + SMF_BEGIN + + if ( ( s.CardRCA() != 0 ) && ( (s.CardP()->iStatus.State()) == ECardStateSlp) ) + { + // Currently selected media is asleep, so it must be awoken + SMF_INVOKES(ExecAwakeCommandSMST,EStExecCmd) + } + + SMF_STATE(EStExecCmd) + + TMMCCommandDesc& cmd = s.Command(); + // clearup some internally used flags + cmd.iFlags &= ~(KMMCCmdFlagExecTopBusy|KMMCCmdFlagExecSelBusy); + cmd.iPollAttempts = cmd.iTimeOutRetries = cmd.iCRCRetries = 0; + + SMF_STATE(EStRetry) + + TMMCCommandDesc& cmd = s.Command(); + m.SetTraps( KMMCErrBasic & ~Command().iExecNotHandle); // Processing all trappable errors + + if (iMultiplexedBus) + { + if(cmd.iCommand == ECmdSelectCard) + { + DeselectsToIssue(1); + } + + if (iConfig.iModes & KMMCModeCardControlled) + { + DoSetBusWidth(BusWidthEncoding(s.CardP()->BusWidth())); + DoSetClock(MaxTranSpeedInKilohertz(*s.CardP())); + + // Check if this card is already in the appropriate selected/deselected + // state for the forthcoming command. + if (s.CardRCA() != iSelectedCard) + { + DeselectsToIssue(1); + } + } + } + + // If bus context is unknown, issue DESELECT a few times with a RetryGap between them. + if ( (iStackState & KMMCStackStateDoDeselect) == 0 ) + SMF_GOTOS( EStAnalyseCommand ) + + // Save the top-level command while we issue de-selects + s.PushCommandStack(); + s.FillCommandDesc( ECmdSelectCard, 0 ); // Deselect - RCA=0 + iCxDeselectCount=iDeselectsToIssue; + + SMF_STATE(EStDeselectLoop) + + SMF_INVOKES(IssueCommandCheckResponseSMST,EStDeselectEndCheck) + + SMF_STATE(EStDeselectEndCheck) + + // If we got an error and this is the last de-select then give up + if (err && iCxDeselectCount == 1) + { + s.PopCommandStack(); + SMF_RETURN(err) + } + + if (--iCxDeselectCount > 0) + SMF_INVOKES(RetryGapTimerSMST,EStDeselectLoop) + + s.PopCommandStack(); + iStackState &= ~KMMCStackStateDoDeselect; + + SMF_STATE(EStAnalyseCommand) + + TMMCCommandDesc& cmd = s.Command(); + // Check if its un-important whether the card is in transfer state (i.e + // selected) or not for the command we are about to execute. Such + // commands are CMD0, CMD7 and CMD13. + + // This state machine should never send CMD55 + if (cmd.iCommand == ECmdAppCmd) + SMF_RETURN (KMMCErrNotSupported) + + SMF_NEXTS( EStTestAppCommand ) + if (cmd.iCommand == ECmdGoIdleState || cmd.iCommand == ECmdSelectCard || cmd.iCommand == ECmdSendStatus) + { + SMF_GOTONEXTS + } + + // See if we need to select (or deselect) this card + TRCA targetRCA=0; + switch( cmd.iSpec.iCommandType ) + { + case ECmdTypeBC: case ECmdTypeBCR: case ECmdTypeAC: + // Command which don't require the card to be selected + break; + case ECmdTypeACS: case ECmdTypeADTCS: case ECmdTypeADC: + // Commands which do require the card to be selected + { + if ( (iConfig.iModes & KMMCModeCardControlled) == 0 ) + SMF_GOTONEXTS + // Get the RCA of the card + if ( (targetRCA = s.CardRCA()) == 0 ) + SMF_RETURN( KMMCErrNoCard ) + break; + } + default: + SMF_GOTONEXTS + } + + // Check if this card is already in the appropriate selected/deselected + // state for the forthcoming command. + if (targetRCA==iSelectedCard) + SMF_GOTONEXTS + + // Need to select (or deselect by using RCA(0)) the card so push the + // top-level command onto the command stack while we issue the select command. + s.PushCommandStack(); + s.FillCommandDesc(ECmdSelectCard,targetRCA); + SMF_INVOKES(IssueCommandCheckResponseSMST,EStSelectDone) + + SMF_STATE(EStSelectDone) + + TMMCCommandDesc& cmd = s.Command(); + + if ( err ) + { + cmd.iFlags &= ~(KMMCCmdFlagASSPFlags|KMMCCmdFlagExecSelBusy); + + if (err == KMMCErrBusyTimeOut) + cmd.iFlags |= KMMCCmdFlagExecSelBusy; + + s.PopCommandStack(); + SMF_NEXTS(EStErrRecover) + return(err); // re-enter the next state with that error + } + + // Are we trying to recover from a top-level command returning busy (by de-selecting and re-selecting) + if ( cmd.iFlags & KMMCCmdFlagExecTopBusy ) + { + cmd.iFlags &= ~KMMCCmdFlagExecTopBusy; + + TUint32 blockLength = cmd.BlockLength(); + + if ( !(cmd.iSpec.iMultipleBlocks) || cmd.iTotalLength <= blockLength ) + SMF_EXIT // operation is completed + + cmd.iTotalLength -= blockLength; + cmd.iArgument = TUint(cmd.iArgument) + blockLength; + cmd.iDataMemoryP += blockLength; + s.iBytesTransferred += blockLength; + cmd.iPollAttempts = 0; + } + + s.PopCommandStack(); + + cmd = s.Command(); + if (!cmd.iSpec.iUseStopTransmission && cmd.iSpec.iMultipleBlocks) + { + // Multi-block command using SET_BLOCK_COUNT + // This is a re-try of the data transfer, normally select (CMD7) is performed along with the issuing of CMD23, + // therefore need to re-issue SET_BLOCK_COUNT..... + const TUint blocks = cmd.NumBlocks(); + + s.PushCommandStack(); + s.FillCommandDesc( ECmdSetBlockCount, blocks ); + SMF_INVOKES( IssueCommandCheckResponseSMST, EStBlockCountCmdIssued ) + } + else + { + SMF_GOTOS( EStTestAppCommand ) + } + + SMF_STATE(EStBlockCountCmdIssued) + + const TMMCStatus status(s.ResponseP()); + s.PopCommandStack(); + if (status.Error()) + SMF_RETURN(KMMCErrStatus) + + if(err & KMMCErrNotSupported) + { + // Not supported by the PSL, so use standard Stop Transmission mode + s.Command().iSpec.iUseStopTransmission = ETrue; + } + // Fall through... + + SMF_STATE(EStTestAppCommand) + TMMCCommandDesc& cmd = s.Command(); + if (cmd.iSpec.iCommandClass != KMMCCmdClassApplication) + SMF_GOTOS( EStIssueCommand ) + + s.PushCommandStack(); + s.FillCommandDesc(ECmdAppCmd, s.CardRCA()); // Send APP_CMD (CMD55) + SMF_INVOKES(IssueCommandCheckResponseSMST,EStIssueAppCommandDone) + + SMF_STATE(EStIssueAppCommandDone) + s.PopCommandStack(); + if ( err ) + { + SMF_NEXTS(EStErrRecover) + return(err); // re-enter the next state with that error + } + + + SMF_STATE(EStIssueCommand) + + TMMCCommandDesc& cmd = s.Command(); + // If its an addressed command (rather than a selected command), then + // setup the argument with the RCA. (Commands requiring card to be + // selected - ACS don't matter since selected cards don't need an RCA now). + if ((iConfig.iModes & KMMCModeCardControlled) && cmd.iSpec.iCommandType==ECmdTypeAC ) + { + const TRCA targetRCA = s.CardRCA(); + if ( targetRCA == 0 ) + SMF_RETURN( KMMCErrNoCard ) + cmd.iArgument.SetRCA(targetRCA); + } + + // Issue the top-level command + SMF_INVOKES(IssueCommandCheckResponseSMST,EStCommandIssued) + + SMF_STATE(EStCommandIssued) + + // If command was succesful then we've finished. + if (!err) + SMF_EXIT + + SMF_STATE(EStErrRecover) + + TMMCCommandDesc& cmd = s.Command(); + const TUint32 modes=iConfig.iModes; + SMF_NEXTS(EStRetry) + + m.ResetTraps(); // no more re-entries via return() + + if (cmd.iCommand == ECmdSelectCard) + DeselectsToIssue( 1 ); // in case stby/tran synch is lost + + if (cmd.iSpec.iMultipleBlocks && (cmd.iFlags & KMMCCmdFlagBytesValid)) + { + cmd.iTotalLength -= cmd.iBytesDone; + cmd.iArgument = TUint(cmd.iArgument) + cmd.iBytesDone; + cmd.iDataMemoryP += cmd.iBytesDone; + s.iBytesTransferred += cmd.iBytesDone; + + if (cmd.iTotalLength < cmd.BlockLength()) + { + DeselectsToIssue(1); + return(err); + } + } + + if ((modes & KMMCModeEnableRetries) == 0) + return( err ); + + const TUint32 toMask = (KMMCErrResponseTimeOut|KMMCErrDataTimeOut); + const TUint32 crcMask = (KMMCErrResponseCRC|KMMCErrDataCRC|KMMCErrCommandCRC); + + if( (err & ~(toMask|crcMask)) == KMMCErrNone ) // time-outs/CRC errors + { + if( cmd.iSpec.iCommandType == ECmdTypeADTCS ) // for data transfer commands + { + DeselectsToIssue( 1 ); // enforce deselect before any retries + + if( (modes & KMMCModeCardControlled) == 0 ) + return( err ); // we wouldn't know what to select - no retries + } + + TUint32 gapEnabled = 0; + + if( err & toMask ) + { + cmd.iTimeOutRetries++; + gapEnabled |= KMMCModeTimeOutRetryGap; + + if( (modes & KMMCModeEnableTimeOutRetry) == 0 || + cmd.iTimeOutRetries > iConfig.iTimeOutRetries ) + return( err ); + } + + if( err & crcMask ) + { + cmd.iCRCRetries++; + gapEnabled |= KMMCModeCRCRetryGap; + + if( (modes & KMMCModeEnableCRCRetry) == 0 || + cmd.iCRCRetries > iConfig.iCRCRetries || + ((err & KMMCErrDataCRC) != 0 && (modes & KMMCModeDataCRCRetry) == 0) ) + return( err ); + } + + if( (modes & gapEnabled) == gapEnabled ) + { + if( modes & KMMCModeCardControlled ) + s.iState |= KMMCSessStateSafeInGaps; // preemption allowed + + SMF_CALL( RetryGapTimerSMST ) + } + + if( (modes & (KMMCModeEnablePreemption|KMMCModePreemptOnRetry|KMMCModeCardControlled)) == + (KMMCModeEnablePreemption|KMMCModePreemptOnRetry|KMMCModeCardControlled) ) + { + s.SwapMe(); + SMF_WAIT // let the others take over the bus before retry + } + + // No preemption, just repeat the command + SMF_GOTONEXTS + } + + if( err & KMMCErrBusInconsistent ) + { + // ASSP layer reported that we must re-initialise the stack to recover. + // Here we'll allow stack initialiser to take over. The control will then be transferred + // to whoever processes KMMCErrInitContext (must be a top-level SM function) + +// ReportInconsistentBusState(); // ASSP layer should have it done + s.iGlobalRetries++; + + if( s.iGlobalRetries > KMMCMaxGlobalRetries ) + return( err ); + + s.SwapMe(); // To prevent the initialiser from aborting this session + SMF_WAIT // Initialiser will take over here + } + + if( err == KMMCErrBusyTimeOut ) + { + if ((cmd.iFlags & KMMCCmdFlagExecSelBusy) == 0) // check if that was a response + cmd.iFlags |= KMMCCmdFlagExecTopBusy; // to a top level command + + DeselectsToIssue( 1 ); // force deselect as the next bus operation + cmd.iPollAttempts++; + + if( (modes & KMMCModeEnableBusyPoll) == 0 || + ((modes & KMMCModeCardControlled) == 0 && cmd.iCommand != ECmdSelectCard) || + cmd.iPollAttempts > iConfig.iPollAttempts ) + return( err ); + + if( modes & KMMCModeBusyPollGap ) + { + s.iState |= KMMCSessStateSafeInGaps; // preemption allowed + SMF_CALL( PollGapTimerSMST ) + } + + if( (modes & (KMMCModeEnablePreemption|KMMCModePreemptOnBusy)) == + (KMMCModeEnablePreemption|KMMCModePreemptOnBusy) ) + { + s.SwapMe(); + SMF_WAIT // let the others take over the bus before retry + } + + // No preemption, just repeat the Deselect/Select sequence + SMF_GOTONEXTS + } + + return( err ); + + SMF_END + } + +TMMCErr DMMCStack::IssueCommandCheckResponseSM() +/** + * Issue a single command to the card and check the response + * + * This generic layer child function in turn calls IssueMMCCommandSM(). + * + * @return MMC error code. + */ + { + enum states + { + EStBegin=0, + EStIssueCommand, + EStCommandIssued, + EStEnd + }; + + DMMCSession& s=Session(); + TMMCCommandDesc& cmd = Command(); + + SMF_BEGIN + + __KTRACE_OPT(KPBUS1,Kern::Printf(">MMC:Issue %d %x",TUint(cmd.iCommand),TUint(cmd.iArgument))); + + // Stop the Controller from powering down the card due to bus inactivity + iSocket->ResetInactivity(0); + + SMF_STATE(EStIssueCommand) + + // If command is directed at a specific card then save this command in card object + if (iConfig.iModes & KMMCModeCardControlled) + { + s.iCardP->iLastCommand = cmd.iCommand; + + if(iMultiplexedBus) + { + DoSetBusWidth(BusWidthEncoding(s.CardP()->BusWidth())); + DoSetClock(MaxTranSpeedInKilohertz(*s.CardP())); + } + } + + if (cmd.iCommand==ECmdSelectCard) + iSelectedCard = TUint16(~0); + + // Pass the command to ASSP layer + cmd.iFlags &= ~(KMMCCmdFlagASSPFlags|KMMCCmdFlagExecSelBusy); + cmd.iBytesDone=0; + m.SetTraps(KMMCErrAll); + SMF_INVOKES(IssueMMCCommandSMST,EStCommandIssued) + + SMF_STATE(EStCommandIssued) + +#ifdef ENABLE_DETAILED_SD_COMMAND_TRACE + cmd.Dump(s.ResponseP(), err); +#endif + + TMMCErr exitCode=err; + // If we have just de-selected all cards in the stack, RCA(0) then ignore response timeout. + if ( cmd.iCommand==ECmdSelectCard && TRCA(cmd.iArgument)==0 ) + exitCode &= ~KMMCErrResponseTimeOut; + else + { + // If commands returns card status and there we no command errors + // (or the status contains errors) then save the status info. + if ( (cmd.iFlags & KMMCCmdFlagStatusReceived) || + ((exitCode==KMMCErrNone || (exitCode & KMMCErrStatus)) && + (cmd.iSpec.iResponseType==ERespTypeR1 || cmd.iSpec.iResponseType==ERespTypeR1B)) ) + { + TMMCStatus status=s.ResponseP(); + s.iLastStatus=status; + __KTRACE_OPT(KPBUS1, Kern::Printf("mmc:ec:st=%08x", TUint32(status))); + + if (iConfig.iModes & KMMCModeCardControlled) + s.iCardP->iStatus=status; + + // Update exit code if card status is reporting an error (in case not done already) + if (status.Error() != 0) + exitCode |= KMMCErrStatus; + } + } + + // If we've just selected a card and the command was succesful then + // remember which one so we don't need to do it twice. + if (cmd.iCommand==ECmdSelectCard && exitCode==KMMCErrNone) + iSelectedCard=TRCA(cmd.iArgument); + + SMF_RETURN(exitCode) + + SMF_END + } + +// +// General Client Service CIMs/Sessions; top level functions +// +inline TMMCErr DMMCStack::NakedSessionSM() +/** + * Executes an individual MMC command (as opposed to a macro). + * + * If the command is 'card controlled' it first invokes AttachCardSM() + * before calling ExecCommandSM() to excecute the requested command. + * + * @return MMC error code. + */ + { + enum states + { + EStBegin=0, + EStAttached, + EStFinish, + EStEnd + }; + + DMMCSession& s=Session(); + + SMF_BEGIN + s.iState |= KMMCSessStateInProgress; + + if( (iConfig.iModes & KMMCModeCardControlled) != 0 ) + SMF_INVOKES( AttachCardSMST, EStAttached ) + + SMF_BPOINT(EStAttached) + + SMF_INVOKES( ExecCommandSMST, EStFinish ) + + SMF_STATE(EStFinish) + + s.iState &= ~KMMCSessStateInProgress; + SMF_END + } + +inline TMMCErr DMMCStack::CIMSetupCardSM() +/** + * Executes the CIM_SETUP_CARD macro. + * + * @return MMC error code. + */ + { + enum states + { + EStBegin=0, + EStAttached, + EStSelected, + EStGotCSD, + EStEnd + }; + + DMMCSession& s=Session(); + + __KTRACE_OPT(KPBUS1,Kern::Printf(">MMC:SetupCardSM %x",TUint(s.iLastStatus))); + + SMF_BEGIN + s.iState |= KMMCSessStateInProgress; + + SMF_INVOKES( AttachCardSMST, EStAttached ) // attachment is mandatory here + + SMF_BPOINT(EStAttached) + + s.FillCommandDesc( ECmdSelectCard, 0 ); + SMF_INVOKES( ExecCommandSMST, EStSelected ) + + SMF_STATE(EStSelected) + + if( !s.iCardP->IsReady() ) + return( KMMCErrNoCard ); + + s.FillCommandDesc( ECmdSendCSD, Command().iArgument ); // NB: the card will be deselected to execute this command + SMF_INVOKES( ExecCommandSMST, EStGotCSD ) + + SMF_STATE(EStGotCSD) + + s.iCardP->iCSD = s.ResponseP(); + + s.iState &= ~KMMCSessStateInProgress; + SMF_END + } + +EXPORT_C TMMCErr DMMCStack::CIMReadWriteBlocksSM() +/** + * This macro performs single/multiple block reads and writes. + * + * For normal read/write block operations, this function determines the appropriate + * MMC command to send and fills the command descriptor accordingly based on + * the value of the session ID set. However, it is necessary to have set the + * command arguments (with DMMCSession::FillCommandArgs()) before this function + * is called. + * + * For special block read/write operations, e.g. lock/unlock, it is required to + * have already filled the command descriptor (with DMMCSession::FillCommandDesc()) + * for the special command required - in addition to have setup the command arguments. + * + * @return MMC error code. + */ + { + enum states + { + EStBegin=0, + EStRestart, + EStAttached, + EStLength1, + EStLengthSet, + EStIssueBlockCount, + EStAppCmdIssued, + EStBlockCountCmdIssued, + EStBpoint1, + EStIssued, + EStWaitFinish, + EStWaitFinish1, + EStRWFinish, + EStEnd + }; + + DMMCSession& s=Session(); + + __KTRACE_OPT(KPBUS1,Kern::Printf(">MMC:RWBlocksSM %x",TUint(s.iLastStatus))); + + SMF_BEGIN + + if(s.iSessionID == ECIMWriteBlock || s.iSessionID == ECIMWriteMBlock) + { + // Check that the card supports class 4 (Write) commands + const TUint ccc = s.iCardP->CSD().CCC(); + if(!(ccc & KMMCCmdClassBlockWrite)) + return( KMMCErrNotSupported ); + } + + s.iState |= KMMCSessStateInProgress; + m.SetTraps(KMMCErrInitContext); + + SMF_STATE(EStRestart) // NB: ErrBypass is not processed here + + SMF_CALLMEWR(EStRestart) // Create a recursive call entry to recover from the errors trapped + m.SetTraps(KMMCErrStatus); + if (s.Command().iSpec.iCommandClass!=KMMCCmdClassApplication || s.Command().iCommand==ECmdAppCmd) + { + s.ResetCommandStack(); + SMF_INVOKES( AttachCardSMST, EStAttached ) // attachment is mandatory here + } + + SMF_BPOINT(EStAttached) + + TMMCCommandDesc& cmd = s.Command(); + + const TUint32 blockLength = cmd.BlockLength(); + if(blockLength == 0) + return KMMCErrArgument; + + if(s.iSessionID == ECIMReadBlock || + s.iSessionID == ECIMWriteBlock || + s.iSessionID == ECIMReadMBlock || + s.iSessionID == ECIMWriteMBlock) + { + // read/write operation + if(!cmd.AdjustForBlockOrByteAccess(s)) + { + // unable to convert command arguments to suit the underlying block/byte access mode + return KMMCErrArgument; + } + } + + // Set the block length if it has changed. Always set for ECIMLockUnlock. + if ((blockLength == s.iCardP->iSetBlockLen) && (s.iSessionID != ECIMLockUnlock)) + { + SMF_GOTOS( EStLengthSet ) + } + + s.iCardP->iSetBlockLen = 0; + s.PushCommandStack(); + s.FillCommandDesc( ECmdSetBlockLen, blockLength ); + SMF_INVOKES( ExecCommandSMST, EStLength1 ) + + SMF_STATE(EStLength1) + + const TMMCStatus status(s.ResponseP()); + s.PopCommandStack(); + if (status.Error()) + SMF_RETURN(KMMCErrStatus) + s.iCardP->iSetBlockLen = s.Command().BlockLength(); + + SMF_STATE(EStLengthSet) + + TMMCCommandDesc& cmd = s.Command(); + TUint opType = 0; + const TUint kTypeWrite = KBit0; + const TUint kTypeMultiple = KBit1; + const TUint kTypeSpecial = KBit2; + static const TMMCCommandEnum cmdCodes[4] = + {ECmdReadSingleBlock, ECmdWriteBlock, ECmdReadMultipleBlock, ECmdWriteMultipleBlock}; + + switch( s.iSessionID ) + { + case ECIMReadBlock: + break; + case ECIMWriteBlock: + opType=kTypeWrite; + break; + case ECIMReadMBlock: + opType=kTypeMultiple; + break; + case ECIMWriteMBlock: + opType=kTypeWrite|kTypeMultiple; + break; + case ECIMLockUnlock: + default: + opType=kTypeSpecial; + break; + } + + const TUint blocks = cmd.NumBlocks(); + + SMF_NEXTS(EStBpoint1) + if ( !(opType & kTypeSpecial) ) // A special session has already set its command descriptor + { + + if (cmd.iFlags & KMMCCmdFlagReliableWrite) + //ensure multiple block commands are used for reliable writing + opType |= kTypeMultiple; + + if ( (blocks==1) && !(cmd.iFlags & KMMCCmdFlagReliableWrite) ) + { + // Reliable Write requires that Multi-Block command is used. + opType &= ~kTypeMultiple; + } + + TUint32 oldFlags = cmd.iFlags; // Maintain old flags which would be overwritten by FillCommandDesc + cmd.iCommand = cmdCodes[opType]; + s.FillCommandDesc(); + cmd.iFlags = oldFlags; // ...and restore + + if((opType & kTypeMultiple) == kTypeMultiple) + { + // + // This is a Multiple-Block DT command. Check the version of the card, as + // MMC Version 3.1 onwards supports the SET_BLOCK_COUNT mode for DT. + // + // Note that if the PSL does not support pre-determined block count of + // data transmission, then it may return KMMCErrNotSupported to force + // the 'Stop Transmission' mode to be used instead. + // + if(s.iCardP->CSD().SpecVers() >= 3) + { + cmd.iSpec.iUseStopTransmission = EFalse; + SMF_NEXTS(EStIssueBlockCount) + } + else + { + cmd.iSpec.iUseStopTransmission = ETrue; + } + } + } + + SMF_GOTONEXTS + + SMF_STATE(EStIssueBlockCount) + // + // Issues SET_BLOCK_COUNT (CMD23) for MB R/W data transfers. + // This is only issued if MMC version >= 3.1 and the PSL + // supports this mode of operation. + // + TMMCCommandDesc& cmd = s.Command(); + TUint32 args = (TUint16)cmd.NumBlocks(); + + m.SetTraps(KMMCErrStatus | KMMCErrNotSupported); + + if (cmd.iFlags & KMMCCmdFlagReliableWrite) + { + // Command marked as Reliable Write + // set Bit31 in CMD23 argument + args |= KMMCCmdReliableWrite; + } + + s.PushCommandStack(); + s.FillCommandDesc( ECmdSetBlockCount, args ); + SMF_INVOKES( ExecCommandSMST, EStBlockCountCmdIssued ) + + SMF_STATE(EStBlockCountCmdIssued) + + const TMMCStatus status(s.ResponseP()); + s.PopCommandStack(); + if (status.Error()) + SMF_RETURN(KMMCErrStatus) + + if(err & KMMCErrNotSupported) + { + // Not supported by the PSL, so use standard Stop Transmission mode + s.Command().iSpec.iUseStopTransmission = ETrue; + } + + SMF_GOTOS(EStBpoint1) + + SMF_STATE(EStAppCmdIssued) + + const TMMCStatus status(s.ResponseP()); + s.PopCommandStack(); + if (status.Error()) + SMF_RETURN(KMMCErrStatus) + + SMF_BPOINT(EStBpoint1) + + // NB We need to trap KMMCErrStatus errors, because if one occurs, + // we still need to wait to exit PRG/RCV/DATA state + m.SetTraps(KMMCErrStatus); + + SMF_INVOKES( ExecCommandSMST, EStIssued ) + + SMF_STATE(EStIssued) + + // check state of card after data transfer with CMD13. + + if (s.Command().Direction() != 0) + { + SMF_GOTOS(EStWaitFinish) + } + + SMF_GOTOS(EStRWFinish); + + SMF_STATE(EStWaitFinish) + // Save the status and examine it after issuing CMD13... + // NB We don't know where in the command stack the last response is stored (e.g. there may + // have bee a Deselect/Select issued), but we do know last response is stored in iLastStatus + TMMC::BigEndian4Bytes(s.ResponseP(), s.iLastStatus); + + s.PushCommandStack(); + s.FillCommandDesc(ECmdSendStatus, 0); + SMF_INVOKES(ExecCommandSMST, EStWaitFinish1) + + SMF_STATE(EStWaitFinish1) + const TMMCStatus status(s.ResponseP()); + s.PopCommandStack(); + +#ifdef __WINS__ + SMF_GOTOS(EStRWFinish); +#else + const TMMCardStateEnum st1 = status.State(); + if (st1 == ECardStatePrg || st1 == ECardStateRcv || st1 == ECardStateData) + { + SMF_INVOKES(ProgramTimerSMST, EStWaitFinish); + } + if (status.Error()) + SMF_RETURN(KMMCErrStatus) +#endif + + // Fall through if CURRENT_STATE is not PGM or DATA + SMF_STATE(EStRWFinish) + + if (TMMCStatus(s.ResponseP()).Error() != 0) + SMF_RETURN(KMMCErrStatus); + + s.iState &= ~KMMCSessStateInProgress; + + // skip over recursive entry or throw error and catch in CIMLockUnlockSM() + return (s.Command().iCommand == ECmdLockUnlock) ? KMMCErrUpdPswd : KMMCErrBypass; + + SMF_END + } + +inline TMMCErr DMMCStack::CIMEraseSM() +/** + * This macro performs sector/group erase of a continuous area + * + * @return MMC error code. + */ + { + enum states + { + EStBegin=0, + EStRestart, + EStAttached, + EStStartTagged, + EStEndTagged, + EStErased, + EStWaitFinish, + EStWaitFinish1, + EStEraseFinish, + EStEnd + }; + + DMMCSession& s=Session(); + + __KTRACE_OPT(KPBUS1,Kern::Printf(">MMC:EraseSM %x",TUint(s.iLastStatus))); + + SMF_BEGIN + + // Check that the card supports class 4 (Write) commands + const TUint ccc = s.iCardP->CSD().CCC(); + if(!(ccc & KMMCCmdClassErase)) + return( KMMCErrNotSupported ); + + s.iState |= KMMCSessStateInProgress; + m.SetTraps( KMMCErrInitContext ); + + SMF_STATE(EStRestart) + + SMF_CALLMEWR(EStRestart) // Create a recursive call entry to recover from Init + + s.ResetCommandStack(); + SMF_INVOKES( AttachCardSMST, EStAttached ) // attachment is mandatory + + SMF_BPOINT(EStAttached) + + TMMCCommandDesc& cmd = s.Command(); + + if(cmd.iTotalLength == 0) + return( KMMCErrArgument ); + + switch( s.iSessionID ) + { + case ECIMEraseSector: + TMMCEraseInfo eraseInfo; + s.iCardP->GetEraseInfo(eraseInfo); + cmd.iBlockLength = eraseInfo.iMinEraseSectorSize; + cmd.iCommand = ECmdTagSectorStart; + break; + case ECIMEraseGroup: + cmd.iBlockLength = s.iCardP->iCSD.EraseGroupSize(); + if(cmd.iBlockLength == 0 || cmd.iTotalLength % cmd.iBlockLength != 0) + return KMMCErrArgument; + cmd.iCommand = ECmdTagEraseGroupStart; + break; + default: + DMMCSocket::Panic(DMMCSocket::EMMCEraseSessionID); + } + + if(!cmd.AdjustForBlockOrByteAccess(s)) + return KMMCErrArgument; + + iConfig.RemoveMode( KMMCModeEnablePreemption ); // erase sequence must not be pre-empted + + const TUint flags = cmd.iFlags; + s.FillCommandDesc(); + cmd.iFlags = flags; + SMF_INVOKES( ExecCommandSMST, EStStartTagged ) + + SMF_STATE(EStStartTagged) + + const TMMCCommandDesc& cmd = s.Command(); + + TMMCCommandEnum command; + TUint endAddr = cmd.iArgument; + if(s.iSessionID == ECIMEraseSector) + { + endAddr += cmd.IsBlockCmd() ? (cmd.iTotalLength / cmd.iBlockLength - 1) : (cmd.iTotalLength - cmd.iBlockLength); + command = ECmdTagSectorEnd; + } + else + { + if(cmd.IsBlockCmd()) + { + endAddr += (cmd.iTotalLength - cmd.iBlockLength) >> KMMCardHighCapBlockSizeLog2; + } + else + { + endAddr += cmd.iTotalLength - cmd.iBlockLength; + } + + command = ECmdTagEraseGroupEnd; + } + + s.PushCommandStack(); + s.FillCommandDesc( command, endAddr ); + SMF_INVOKES( ExecCommandSMST, EStEndTagged ) + + SMF_STATE(EStEndTagged) + + // Increase the inactivity timeout as an erase operation can potentially take a long time + // At the moment this is a somewhat arbitrary 30 seconds. This could be calculated more accurately + // using TAAC,NSAC, R2W_FACTOR etc. but that seems to yield very large values (?) + const TInt KMaxEraseTimeoutInSeconds = 30; + iBody->SetInactivityTimeout(KMaxEraseTimeoutInSeconds); + m.SetTraps(KMMCErrAll); + s.FillCommandDesc( ECmdErase, 0 ); + SMF_INVOKES( ExecCommandSMST, EStErased ) + + SMF_STATE(EStErased) + m.SetTraps( KMMCErrInitContext ); + iBody->RestoreInactivityTimeout(); + if (err != KMMCErrNone) + SMF_RETURN(err); + + + SMF_STATE(EStWaitFinish) + + s.PushCommandStack(); + s.FillCommandDesc(ECmdSendStatus, 0); + SMF_INVOKES(ExecCommandSMST, EStWaitFinish1) + + SMF_STATE(EStWaitFinish1) + + const TMMCStatus st(s.ResponseP()); + s.PopCommandStack(); + +#ifdef __WINS__ + SMF_GOTOS(EStEraseFinish); +#else + const TMMCardStateEnum st1 = st.State(); + if (st1 == ECardStatePrg || st1 == ECardStateRcv || st1 == ECardStateData) + { + SMF_INVOKES(ProgramTimerSMST, EStWaitFinish); + } +#endif + + // Fall through if CURRENT_STATE is not PGM or DATA + SMF_STATE(EStEraseFinish) + + s.iState &= ~KMMCSessStateInProgress; + return( KMMCErrBypass ); // to skip over the recursive entry + + SMF_END + } + +inline TMMCErr DMMCStack::CIMReadWriteIOSM() +/** + * This macro reads/writes a stream of bytes from/to an I/O register. + * This is a generalised form of FAST_IO (CMD39) MMC command. + * + * @return MMC error code. + */ + { + enum states + { + EStBegin=0, + EStReadIO, + EStIOLoop, + EStEnd + }; + + DMMCSession& s=Session(); + TMMCCommandDesc& cmd = s.Command(); + + __KTRACE_OPT(KPBUS1,Kern::Printf(">MMC:IOSM %x",TUint(s.iLastStatus))); + + SMF_BEGIN + s.iState |= KMMCSessStateInProgress; + TUint argument = (TUint(cmd.iArgument)&0x7F) << 8; // shift reg addr into a proper position + + switch( s.iSessionID ) + { + case ECIMReadIO: + break; + case ECIMWriteIO: + argument |= 0x8000; + break; + default: + DMMCSocket::Panic(DMMCSocket::EMMCIOSessionID); + } + + s.FillCommandDesc( ECmdFastIO, argument ); + s.iBytesTransferred = ~0UL; + + SMF_INVOKES( AttachCardSMST, EStIOLoop ) // attachment's mandatory + + SMF_STATE(EStReadIO) + + *(cmd.iDataMemoryP)++ = s.ResponseP()[3]; + cmd.iTotalLength--; + + SMF_BPOINT(EStIOLoop) + + s.iBytesTransferred++; + + if( cmd.iTotalLength == 0 ) + { + s.iState &= ~KMMCSessStateInProgress; + SMF_EXIT + } + + if( s.iSessionID == ECIMWriteIO ) + { + TUint8 byte = *(cmd.iDataMemoryP)++; + cmd.iTotalLength--; + cmd.iArgument = (TUint(cmd.iArgument)&0xFF00) | byte; + } + else + SMF_NEXTS(EStReadIO) + + iConfig.RemoveMode( KMMCModeEnableRetries ); // no retries on I/O registers! + + SMF_CALL( ExecCommandSMST ) + + SMF_END + } + +inline TMMCErr DMMCStack::CIMLockUnlockSM() +/** + * Locking and unlocking a card involves writing a data block to the card. + * CIMReadWriteBlocksSM() could be used directly, but, in practive, a card must + * sometimes be sent the data block twice. + * + * @return MMC error code. + */ + { + enum states + { + EStBegin, + EStRetry, + EStTestR1, + EStEnd + }; + + DMMCSession& s=Session(); + TMMCCommandDesc& cmd = Command(); + + __KTRACE_OPT(KPBUS1, Kern::Printf("mmc:clusm")); + + SMF_BEGIN + m.SetTraps(KMMCErrStatus | KMMCErrUpdPswd); + cmd.iUnlockRetries = 0; // attempt counter + iCMD42CmdByte = cmd.iDataMemoryP[0]; + + SMF_STATE(EStRetry) + __KTRACE_OPT(KPBUS1, Kern::Printf("mmc:clusm:%x/%x", cmd.iUnlockRetries, (iSessionP == &iAutoUnlockSession) ? KMMCMaxAutoUnlockRetries : iConfig.iUnlockRetries)); + + if (iCMD42CmdByte == KMMCLockUnlockErase) + { + // Section 4.6.2 of version 4.2 of the the MMC specification states that + // the maximum time for a force erase operation should be 3 minutes + const TInt KMaxForceEraseTimeoutInSeconds = 3 * 60; + iBody->SetInactivityTimeout(KMaxForceEraseTimeoutInSeconds); + m.SetTraps(KMMCErrAll); + } + + + SMF_INVOKES(CIMReadWriteBlocksSMST, EStTestR1); + + SMF_STATE(EStTestR1) + if (iCMD42CmdByte == KMMCLockUnlockErase) + { + m.SetTraps(KMMCErrStatus | KMMCErrUpdPswd); + iBody->RestoreInactivityTimeout(); + } + + if (err & KMMCErrStatus) + { + const TMMCStatus st = s.LastStatus(); // set in ExecCommandSM() / EStCommandIssued + TMMCCommandDesc& cmd0 = Command(); + + __KTRACE_OPT(KPBUS1, Kern::Printf("mmc:clusm:EStTestR1 [err: %08x, st:%08x] : RETRY [%d]", + err, (TInt)s.LastStatus(), cmd0.iUnlockRetries)); + + const TInt KMaxRetries = (iSessionP == &iAutoUnlockSession) ? KMMCMaxAutoUnlockRetries : iConfig.iUnlockRetries; + + // retry if LOCK_UNLOCK_FAIL only error bit + if (!( iCMD42CmdByte == 0 // LOCK_UNLOCK = 0; SET_PWD = 0 + && err == KMMCErrStatus && st.Error() == KMMCStatErrLockUnlock + && (iConfig.iModes & KMMCModeEnableUnlockRetry) != 0 + && ++cmd0.iUnlockRetries < KMaxRetries )) + { + __KTRACE_OPT(KPBUS1, Kern::Printf("mmc:clusm:abt")); + + SMF_RETURN(err); + } + + __KTRACE_OPT(KPBUS1, Kern::Printf("mmc:clusm:retry")); + +#ifdef __EPOC32__ + s.SynchBlock(KMMCBlockOnRetryTimer); + s.iRetryTimer.OneShot(KMMCUnlockRetryGapInMilliseconds,EFalse); + SMF_WAITS(EStRetry) +#else + SMF_GOTOS(EStRetry); +#endif + } + else if (err & KMMCErrUpdPswd) + { + // CMD42 executed successfully, so update 'Has Password' flag + if ((iCMD42CmdByte & (KMMCLockUnlockClrPwd | KMMCLockUnlockErase)) != 0) + { + s.CardP()->iFlags&=(~KMMCardHasPassword); + } + else if ((iCMD42CmdByte & KMMCLockUnlockSetPwd) != 0) + { + s.CardP()->iFlags|=KMMCardHasPassword; + } + + SMF_EXIT; + } + else if (err != KMMCErrNone) + { + SMF_RETURN(err); + } + + + SMF_END + } + +inline TMMCErr DMMCStack::CIMAutoUnlockSM() +/** + * Performs auto-unlocking of the card stack + * + * @return MMC error code + */ + { + enum states + { + EStBegin=0, + EStNextIndex, + EStInitStackAfterUnlock, + EStIssuedLockUnlock, + EStDone, + EStEnd + }; + + __KTRACE_OPT(KPBUS1,Kern::Printf(">MMC:CIMAutoUnlockSM")); + + DMMCSession& s=Session(); + + SMF_BEGIN + + iAutoUnlockIndex = -1; + + m.SetTraps(KMMCErrAll); // Trap (and ignore) all errors during auto-unlock + + SMF_STATE(EStNextIndex) + + if(err) + { + iSocket->PasswordControlEnd(&Session(), err); + } + + // the cycle is finished when iAutoUnlockIndex == KMaxMultiMediaCardsPerStack + + if(iAutoUnlockIndex >= TInt(KMaxMMCardsPerStack)) + { + SMF_GOTOS(EStInitStackAfterUnlock); + } + + // find next available card with password in the controller store + + const TMapping *mp = NULL; + + TBool useIndex = EFalse; + for (++iAutoUnlockIndex; iAutoUnlockIndex < TInt(KMaxMMCardsPerStack); ++iAutoUnlockIndex) + { + useIndex = // card must be present with valid mapping + iCardArray->CardP(iAutoUnlockIndex)->IsPresent() + && (mp = iSocket->iPasswordStore->FindMappingInStore(iCardArray->CardP(iAutoUnlockIndex)->CID())) != NULL + && mp->iState == TMapping::EStValid; + + // don't increment iAutoUnlockIndex in continuation loop + if (useIndex) + break; + } + + if (! useIndex) + { + // if no usable index, complete with no error code + SMF_GOTOS(EStInitStackAfterUnlock); + } + + // + // We've found a locked card with a password in the password store, + // so attempt to unlock using the CIMLockUnlockSMST state machine. + // + // Upon completion, test the next card before performing further initialisation. + // + + TMMCard &cd = *(iCardArray->CardP(iAutoUnlockIndex++)); + s.SetCard(&cd); + + const TInt kPWD_LEN = mp->iPWD.Length(); + iPSLBuf[0] = 0; // LOCK_UNLOCK = 0; unlock + iPSLBuf[1] = static_cast(kPWD_LEN); + TPtr8 pwd(&iPSLBuf[2], kPWD_LEN); + pwd.Copy(mp->iPWD); + + const TInt kBlockLen = 1 + 1 + kPWD_LEN; + + s.FillCommandDesc(ECmdLockUnlock); + s.FillCommandArgs(0, kBlockLen, iPSLBuf, kBlockLen); + + SMF_INVOKES( CIMLockUnlockSMST, EStNextIndex ) + + SMF_STATE(EStInitStackAfterUnlock) + + // + // We've attempted to unlock all cards (successfully or not) + // - Now perform second-stage initialisation that can only be performed + // on unlocked cards (such as setting bus width, high speed etc..) + // + + m.ResetTraps(); + + SMF_INVOKES( InitStackAfterUnlockSMST, EStDone ) + + SMF_STATE(EStDone) + + SMF_END + } + +inline TMMCErr DMMCStack::NoSessionSM() +/** + * A null state machine function. Just returns KMMCErrNotSupported. + * + * @return KMMCErrNotSupported + */ + { + enum states + { + EStBegin=0, + EStEnd + }; + + SMF_BEGIN + + return( KMMCErrNotSupported ); + + SMF_END + } + +// +// Static adapter functions to top-level state machines. +// + +TMMCErr DMMCStack::NakedSessionSMST(TAny* aStackP) // ECIMNakedSession + { return( static_cast(aStackP)->NakedSessionSM() ); } + +TMMCErr DMMCStack::CIMUpdateAcqSMST( TAny* aStackP ) // ECIMUpdateAcq + { return( static_cast(aStackP)->CIMUpdateAcqSM() ); } + +TMMCErr DMMCStack::CIMInitStackSMST( TAny* aStackP ) // ECIMInitStack + { return( static_cast(aStackP)->CIMInitStackSM() ); } + +TMMCErr DMMCStack::CIMCheckStackSMST( TAny* aStackP ) // ECIMCheckStack + { return( static_cast(aStackP)->CIMCheckStackSM() ); } + +TMMCErr DMMCStack::CIMSetupCardSMST(TAny* aStackP) // ECIMSetupCard + { return( static_cast(aStackP)->CIMSetupCardSM() ); } + +EXPORT_C TMMCErr DMMCStack::CIMReadWriteBlocksSMST(TAny* aStackP) // ECIMReadBlock, ECIMWriteBlock, ECIMReadMBlock, ECIMWriteMBlock + { return( static_cast(aStackP)->CIMReadWriteBlocksSM() ); } + +TMMCErr DMMCStack::CIMEraseSMST(TAny* aStackP) // ECIMEraseSector, ECIMEraseGroup + { return( static_cast(aStackP)->CIMEraseSM() ); } + +TMMCErr DMMCStack::CIMReadWriteIOSMST(TAny* aStackP) // ECIMReadIO, ECIMWriteIO + { return( static_cast(aStackP)->CIMReadWriteIOSM() ); } + +TMMCErr DMMCStack::CIMLockUnlockSMST(TAny* aStackP) // ECIMLockUnlock + { return( static_cast(aStackP)->CIMLockUnlockSM() ); } + +TMMCErr DMMCStack::NoSessionSMST(TAny* aStackP) // ECIMLockStack + { return( static_cast(aStackP)->NoSessionSM() ); } + +TMMCErr DMMCStack::InitStackAfterUnlockSMST( TAny* aStackP ) + { return( static_cast(aStackP)->InitStackAfterUnlockSM() ); } + +TMMCErr DMMCStack::AcquireStackSMST( TAny* aStackP ) + { return( static_cast(aStackP)->AcquireStackSM() ); } + +TMMCErr DMMCStack::CheckStackSMST( TAny* aStackP ) + { return( static_cast(aStackP)->CheckStackSM() ); } + +TMMCErr DMMCStack::ModifyCardCapabilitySMST( TAny* aStackP ) + { return( static_cast(aStackP)->ModifyCardCapabilitySM() ); } + +TMMCErr DMMCStack::AttachCardSMST( TAny* aStackP ) + { return( static_cast(aStackP)->AttachCardSM() ); } + +TMMCErr DMMCStack::ExecCommandSMST( TAny* aStackP ) + { return( static_cast(aStackP)->ExecCommandSM() ); } + +TMMCErr DMMCStack::IssueCommandCheckResponseSMST(TAny* aStackP) + { return( static_cast(aStackP)->IssueCommandCheckResponseSM() ); } + +TMMCErr DMMCStack::PollGapTimerSMST( TAny* aStackP ) + { return( static_cast(aStackP)->PollGapTimerSM() ); } + +TMMCErr DMMCStack::RetryGapTimerSMST( TAny* aStackP ) + { return( static_cast(aStackP)->RetryGapTimerSM() ); } + +TMMCErr DMMCStack::ProgramTimerSMST(TAny *aStackP) + { return static_cast(aStackP)->ProgramTimerSM(); } + +TMMCErr DMMCStack::GoIdleSMST( TAny* aStackP ) + { return( static_cast(aStackP)->GoIdleSM() ); } + +TMMCErr DMMCStack::CheckLockStatusSMST( TAny* aStackP ) + { return( static_cast(aStackP)->CheckLockStatusSM() ); } + +TMMCErr DMMCStack::CIMAutoUnlockSMST( TAny* aStackP ) + { return( static_cast(aStackP)->CIMAutoUnlockSM() ); } + +TMMCErr DMMCStack::ExecSleepCommandSMST( TAny* aStackP ) + { return( static_cast(aStackP)->ExecSleepCommandSM() ); } + +TMMCErr DMMCStack::ExecAwakeCommandSMST( TAny* aStackP ) + { return( static_cast(aStackP)->ExecAwakeCommandSM() ); } +// +// Static interfaces to ASSP layer SM functions +// +TMMCErr DMMCStack::DoPowerUpSMST( TAny* aStackP ) + { return( static_cast(aStackP)->DoPowerUpSM() ); } + +TMMCErr DMMCStack::InitClockOnSMST( TAny* aStackP ) + { return( static_cast(aStackP)->InitClockOnSM() ); } + +EXPORT_C TMMCErr DMMCStack::IssueMMCCommandSMST( TAny* aStackP ) + { return( static_cast(aStackP)->IssueMMCCommandSM() ); } + +TMMCErr DMMCStack::DetermineBusWidthAndClockSMST( TAny* aStackP ) + { return( static_cast(aStackP)->DetermineBusWidthAndClockSM() ); } + +TMMCErr DMMCStack::ConfigureHighSpeedSMST( TAny* aStackP ) + { return( static_cast(aStackP)->ConfigureHighSpeedSM() ); } + +TMMCErr DMMCStack::ExecSwitchCommandST( TAny* aStackP ) + { return( static_cast(aStackP)->ExecSwitchCommand() ); } + +TMMCErr DMMCStack::SwitchToLowVoltageSMST( TAny* aStackP ) + { return( static_cast(aStackP)->SwitchToLowVoltageSM() ); } + +TMMCErr DMMCStack::LowVoltagePowerupTimerSMST(TAny *aStackP) + { return static_cast(aStackP)->LowVoltagePowerupTimerSM(); } + +TMMCErr DMMCStack::ExecBusTestSMST( TAny* aStackP ) + { return( static_cast(aStackP)->ExecBusTestSM() ); } + +TMMCErr DMMCStack::DoWakeUpSMST( TAny* aStackP ) + { + MDoWakeUp* dowakeup = NULL; + static_cast(aStackP)->GetInterface(KInterfaceDoWakeUpSM, (MInterface*&) dowakeup); + if (dowakeup) + { + return dowakeup->DoWakeUpSM(); + } + else + { + // Interface not supported at PSL Level + return KMMCErrNotSupported; + } + } + + +EXPORT_C DMMCSession* DMMCStack::AllocSession(const TMMCCallBack& aCallBack) const +/** +* Factory function to create DMMCSession derived object. Non-generic MMC +* controllers can override this to generate more specific objects. +* @param aCallBack Callback function to notify the client that a session has completed +* @return A pointer to the new session +*/ + { + return new DMMCSession(aCallBack); + } + +EXPORT_C void DMMCStack::Dummy1() {} + +/** + * Calls the PSL-implemented function SetBusWidth() if the bus width has changed + * + */ +void DMMCStack::DoSetBusWidth(TUint32 aBusWidth) + { + if (iBody->iCurrentSelectedBusWidth != aBusWidth) + { + iBody->iCurrentSelectedBusWidth = aBusWidth; + SetBusWidth(aBusWidth); + } + } + +/** + * Sets iConfig.iBusConfig.iBusClock - which the PSL SHOULD use to set the clock before every command. + * + * Some PSLs, however, may only change the clock when SetBusConfigDefaults() is called, + * so if the clock has changed, SetBusConfigDefaults() is called + * + * @param aClock The requested clock frequency in Kilohertz + */ +void DMMCStack::DoSetClock(TUint32 aClock) + { + iConfig.iBusConfig.iBusClock = aClock; + + if (iPoweredUp&&(iBody->iCurrentSelectedClock != aClock)) + { + iBody->iCurrentSelectedClock = aClock; + SetBusConfigDefaults(iConfig.iBusConfig, aClock); + } + } + + +TUint DMMCStack::MaxTranSpeedInKilohertz(const TMMCard& aCard) const + { + TUint32 highSpeedClock = aCard.HighSpeedClock(); + return highSpeedClock ? highSpeedClock : aCard.MaxTranSpeedInKilohertz(); + } + + + +EXPORT_C void DMMCStack::SetBusWidth(TUint32 /*aBusWidth*/) + { + } + +EXPORT_C void DMMCStack::MachineInfo(TDes8& /*aMachineInfo*/) + { + } + +TBusWidth DMMCStack::BusWidthEncoding(TInt aBusWidth) const +/** + * Returns the bus width as a TBusWidth given a card's bus width + * expressed as an integer (1,4 or 8) + * @return the bus width encoded as a TBusWidth + */ + { + TBusWidth busWidth = EBusWidth1; + + switch(aBusWidth) + { + case 8: + busWidth = EBusWidth8; + break; + case 4: + busWidth = EBusWidth4; + break; + case 1: + case 0: + busWidth = EBusWidth1; + break; + default: + DMMCSocket::Panic(DMMCSocket::EMMCBadBusWidth); + + } + return busWidth; + } + +/** + * class DMMCSocket + */ +EXPORT_C DMMCSocket::DMMCSocket(TInt aSocketNumber, TMMCPasswordStore* aPasswordStore) +/** + * Constructs a DMMCSocket class + * @param aSocketNumber the socket ID + * @param aPasswordStore pointer to the password store + */ + :DPBusSocket(aSocketNumber), + iPasswordStore(aPasswordStore) + { + } + +TInt DMMCSocket::TotalSupportedCards() +/** + * Returns the total number of MMC slots supported by the socket. + * @return The number of MMC slots supported by the socket + */ + { + return iMachineInfo.iTotalSockets; + } + + +// -------- Password store management -------- + +// +// The persistent file is a contiguous sequence of entries. +// An entry format is [CID@16 | PWD_LEN@4 | PWD@PWD_LEN]. +// CID and PWD_LEN are both stored in big endian format. +// + +TInt DMMCSocket::PrepareStore(TInt aBus, TInt aFunc, TLocalDrivePasswordData &aData) +/** + * Called from media driver before CMD42 session engaged, in kernel server context + * so that mappings can be allocated or deallocated. + * + * Using zero-length passwords for MMC operations is disallowed by this function. + * Locking with and clearing a null password is failed with KErrAccessDenied. + * If the drive is already mounted, then TBusLocalDrive::Unlock() will fail with + * KErrAlreadyExists. Otherwise, this function will fail with KErrLocked, which + * is translated to KErrAccessDenied in Unlock(), in the same way as unlocking + * a locked card with the wrong password + * + * @param aBus The card to be unlocked. + * @param aFunc The operation to perform (EPasswordLock, EPasswordUnlock, EPasswordClear). + * @param aData TLocalDrivePasswordData reference containing the password + * @return KErrAccessDenied An attempt to lock or clear was made with a NULL password. + * @return KErrLocked An an attempt to unlock was made with a NULL password. + * @return KErrNone on success + */ + { + TInt r = 0; + + TMMCard *card=iStack->CardP(aBus); + __ASSERT_ALWAYS(card, Panic(EMMCSessionNoPswdCard)); + const TCID &cid = card->CID(); + + switch (aFunc) + { + case DLocalDrive::EPasswordLock: + { + TMediaPassword newPwd = *aData.iNewPasswd; + + if (newPwd.Length() == 0) + r = KErrAccessDenied; + else + r = PasswordControlStart(cid, aData.iStorePasswd ? &newPwd : NULL); + } + break; + + case DLocalDrive::EPasswordUnlock: + { + TMediaPassword curPwd = *aData.iOldPasswd; + + if (curPwd.Length() == 0) + r = KErrLocked; + else + r = PasswordControlStart(cid, aData.iStorePasswd ? &curPwd : NULL); + } + break; + + case DLocalDrive::EPasswordClear: + { + TMediaPassword curPwd = *aData.iOldPasswd; + + if (curPwd.Length() == 0) + r = KErrAccessDenied; + else + r = PasswordControlStart(cid, aData.iStorePasswd ? &curPwd : NULL); + } + break; + + default: + Panic(EMMCSessionPswdCmd); + break; + } + + return r; + } + + +TInt DMMCSocket::PasswordControlStart(const TCID &aCID, const TMediaPassword *aPWD) +/** + * Remove any non-validated mappings from the store, and allocate a binding for + * the card's CID if necessary. + * + * s = source (current) password stored; t = target (new) password should be stored + * f = failure + * + * t is equivalent to iMPTgt.Length() > 0, which is used by PasswordControlEnd(). + * + * The target password is not stored in the store at this point, but in the stack. + * This leaves any existing mapping which can be used for recovery if the operation + * fails. This means the user does not have to re-enter the right password after + * trying to unlock a card with the wrong password. + * + * See PasswordControlEnd() for recovery policy. + */ + { + TInt r = KErrNone; // error code + + TBool changed = EFalse; // compress store if changed + + TBuf8 cid; // convert to TBuf8<> for comparison + cid.SetLength(KMMCCIDLength); + aCID.Copy(&cid[0]); + + TBool s = EFalse; // source password (current mapping) + TBool t = aPWD != NULL; // target pasword (new value for mapping) + + // remove any bindings which were not validated. This is all non EStValid + // bindings - the previous operation could have failed before CMD42 was sent, + // in which case its state would be EStPending, not EStInvalid. + + // an inefficiency exists where an invalid binding for the target CID exists. + // This could be reused instead of being deallocated and reallocated. This + // situation would occur if the user inserted a card whose password was not + // known to the machine, unlocked it with the wrong password and tried again. + // The case is rare and the cost is run-time speed, which is not noticeable, + // The run-time memory usage is equivalent, so it is probably not worth the + // extra rom bytes and logic. + + for (TInt i = 0; i < iPasswordStore->iStore->Count(); ) + { + if ((*iPasswordStore->iStore)[i].iState != TMapping::EStValid) + { + iPasswordStore->iStore->Remove(i); // i becomes index for next item + changed = ETrue; + } + else + { + if ((*iPasswordStore->iStore)[i].iCID == cid) + s = ETrue; + ++i; + } + } + + if (! t) + iStack->iMPTgt.Zero(); + else + { + iStack->iMPTgt = *aPWD; + + if (!s) + { + TMediaPassword mp; // empty, to indicate !s + if ((r = iPasswordStore->InsertMapping(aCID, mp, TMapping::EStPending)) != KErrNone) + return r; + + changed = ETrue; + } + } + + if (changed) + iPasswordStore->iStore->Compress(); + + return r; + } + + + +void DMMCSocket::PasswordControlEnd(DMMCSession *aSessP, TInt aResult) +/** + * called by DMMCStack::SchedCompletionPass() after CMD42 has completed to + * update internal store. This function does not run in ks context and so + * can only invalidate bindings for later removal in PasswordControlStart(). + * + * s = source (current) password stored; t = target (new) password should be stored + * f = failure + * + * If the operation fails, then a recovery policy is used so the user does + * not lose the good current binding and have to re-enter the password. + * ' + * f = 0 f = 1 + * T T + * 0 1 0 1 + * S 0 N V S 0 N I + * 1 W V 1 R R + * + * N nothing V validate W wipe + * I invalidate R restore + * ' + * See PasswordControlStart() for details of how store set up. + */ + { + // autounlock is a special case because the PasswordControlStart() will + // not have been called (the CID is not known in ks context.) The mapping + // for this specific card is removed on failure, because it is the current + // mapping that is definitely wrong. + + TBuf8 cid; // convert to TBuf8<> for comparison + cid.SetLength(KMMCCIDLength); + aSessP->CardP()->CID().Copy(&cid[0]); + + if (aSessP == &iStack->iAutoUnlockSession) + { + TBool changed = EFalse; // compress store if changed + + for (TInt j = 0; j < iPasswordStore->iStore->Count(); ) + { + TMapping &mp = (*iPasswordStore->iStore)[j]; + if (mp.iCID == cid) + { + mp.iState = (aResult == KErrNone ? TMapping::EStValid : TMapping::EStInvalid); + if(mp.iState == TMapping::EStInvalid) + { + iPasswordStore->iStore->Remove(j); + changed = ETrue; + } + else + { + j++; + } + } + else + { + j++; + } + } + + if (changed) + iPasswordStore->iStore->Compress(); + } + else + { + const TMediaPassword &mpTgt = iStack->iMPTgt; + TBool s = EFalse; // default value in case no mapping + TBool t = mpTgt.Length() > 0; + TBool f = (aResult != KErrNone); + + TMapping mp, *pmp; // get mapping to mutate + mp.iCID = cid; + TInt psn = iPasswordStore->iStore->Find(mp, iPasswordStore->iIdentityRelation); + if (psn == KErrNotFound) + { + return; + } + else + { + pmp = &(*iPasswordStore->iStore)[psn]; + s = pmp->iPWD.Length() > 0; + } + + if (f) + { + if (s) // s & ~f + pmp->iState = TMapping::EStValid; // restore + else + { + if (t) // ~s & t & f + pmp->iState = TMapping::EStInvalid; // invalidate + } + } + else + { + if (t) // t & ~f + { + pmp->iState = TMapping::EStValid; // validate + pmp->iPWD = mpTgt; + } + else + { + if (s) // s & ~t & ~f + pmp->iState = TMapping::EStInvalid; // wipe + } + } // else (f) + } // else if (aSessP == &iStack->iAutoUnlockSession) + } + + +TMMCPasswordStore::TMMCPasswordStore() +/** + * Contructor + */ + : iIdentityRelation(TMMCPasswordStore::CompareCID) + { + } + +TInt TMMCPasswordStore::Init() +/** + * Initialises the password store and allocates resources. + * @return KErrNone if successful, standard error code otherwise. + */ + { + // We don't have a destructor yet as this object lasts forever + iStore = new RArray(4, _FOFF(TMapping, iCID)); + if(!iStore) + return KErrNoMemory; + return KErrNone; + } + +EXPORT_C TBool TMMCPasswordStore::IsMappingIncorrect(const TCID& aCID, const TMediaPassword& aPWD) +/** + * Returns true if the password is definitely incorrect, i.e. if a valid entry with a + * different password exists. Returns false if correct (because the mapping matches,) + * or if cannot tell (because no valid mapping.) + */ + { + TMapping* pmp = FindMappingInStore(aCID); + return (pmp != 0 && pmp->iState == TMapping::EStValid && pmp->iPWD.Compare(aPWD) != 0); + } + +TMapping *TMMCPasswordStore::FindMappingInStore(const TCID &aCID) +/** + * return pointer to aCID mapping in store or NULL if not found + */ + { + TMapping *pmp = NULL; + TMapping mp; // 8 + 16 + 8 + 16 + 4 bytes + mp.iCID.SetLength(KMMCCIDLength); + aCID.Copy(&mp.iCID[0]); + + TInt psn=iStore->Find(mp, iIdentityRelation); + if(psn!=KErrNotFound) + { + pmp = &(*iStore)[psn]; + } + return pmp; + } + +TInt TMMCPasswordStore::InsertMapping(const TCID &aCID, const TMediaPassword &aPWD, TMapping::TState aState) +/** + * Ensures that a mapping from aCID to aPWD exists in the store. If an + * existing entry does not exist to mutate, then insert a new one. This + * may cause an allocation, depending on the granularity and count. + * + * If the CID is already bound to something in the store, then this operation + * is a binary search, otherwise it may involve kernel heap allocation. + */ + { + TInt r = KErrNone; + TMapping mpN; + mpN.iCID.SetLength(KMMCCIDLength); + aCID.Copy(&mpN.iCID[0]); // copies from aCID into buffer. + + TInt psn = iStore->Find(mpN, iIdentityRelation); + if(psn == KErrNotFound) + { + mpN.iPWD.Copy(aPWD); + mpN.iState = aState; + r=iStore->Insert(mpN, iStore->Count()); + } + else + { + TMapping &mpE = (*iStore)[psn]; + mpE.iPWD.Copy(aPWD); + mpE.iState = aState; + r = KErrNone; + } + + return r; + } + +TInt TMMCPasswordStore::PasswordStoreLengthInBytes() +/** + * virtual from DPeriphBusController, kern exec + * return number of bytes needed for persistent file representation + * of the password store + */ + { + TInt sz = 0; + + for (TInt i = 0; i < iStore->Count(); ++i) + { + const TMapping &mp = (*iStore)[i]; + if (mp.iState == TMapping::EStValid) + sz += KMMCCIDLength + sizeof(TInt32) + mp.iPWD.Length(); + } + + return sz; + } + +TBool TMMCPasswordStore::ReadPasswordData(TDes8 &aBuf) +/** + * virtual from DPeriphBusController, kern exec + * fills descriptor with persistent representation of password store + * data. aBuf is resized to contain exactly the password data from + * the store. If its maximum length is not enough then KErrOverflow + * is returned and aBuf is not mutated. + */ + { + TInt r=KErrNone; // error code + + if (PasswordStoreLengthInBytes() > aBuf.MaxLength()) + r = KErrOverflow; + else + { + aBuf.Zero(); + for (TInt i = 0; i < iStore->Count(); ++i) + { + const TMapping &mp = (*iStore)[i]; + + if (mp.iState == TMapping::EStValid) + { + aBuf.Append(mp.iCID); + + TUint8 lenBuf[sizeof(TInt32)]; // length, big-endian + TMMC::BigEndian4Bytes(lenBuf, TInt32(mp.iPWD.Length())); + aBuf.Append(&lenBuf[0], sizeof(TInt32)); + + aBuf.Append(mp.iPWD); + } + } + + r = KErrNone; + } + + return r; + } + + +TInt TMMCPasswordStore::WritePasswordData(TDesC8 &aBuf) +/** + * virtual from DPeriphBusController, kern server + * replace current store with data from persistent representation in aBuf. + */ + { + // should only be called at boot up, but remove chance of duplicate entries + iStore->Reset(); + + TInt iBIdx; // buffer index + + // check buffer integrity + + TBool corrupt = EFalse; // abort flag + for (iBIdx = 0; iBIdx < aBuf.Length(); ) + { + // enough raw data for CID, PWD_LEN and 1 byte of PWD + corrupt = TUint(aBuf.Length() - iBIdx) < KMMCCIDLength + sizeof(TUint32) + 1; + if (corrupt) + break; + + // PWD_LEN is valid and enough raw data left for PWD + iBIdx += KMMCCIDLength; + const TInt32 pwd_len(TMMC::BigEndian32(&aBuf[iBIdx])); + corrupt = !( + (pwd_len <= TInt32(KMaxMediaPassword)) + && aBuf.Length() - iBIdx >= TInt(sizeof(TUint32)) + pwd_len ); + if (corrupt) + break; + + // skip over PWD_LEN and PWD to next entry + iBIdx += sizeof(TInt32) + pwd_len; + } + + if (corrupt) + return KErrCorrupt; + + // Build the store from the entries in the buffer. + TInt r = KErrNone; // error code + for (iBIdx = 0; r == KErrNone && iBIdx < aBuf.Length(); ) + { + TPtrC8 pCID(&aBuf[iBIdx], KMMCCIDLength); // CID + const TCID cid(pCID.Ptr()); + + const TInt32 pwd_len(TMMC::BigEndian32(&aBuf[iBIdx + KMMCCIDLength])); + TMediaPassword pwd; + pwd.Copy(&aBuf[iBIdx + KMMCCIDLength + sizeof(TInt32)], pwd_len); + + iBIdx += KMMCCIDLength + sizeof(TInt32) + pwd_len; + r = InsertMapping(cid, pwd, TMapping::EStValid); + } + + // it may be acceptable to use a partially created store, providing the + // sections that do exist are valid. Alternatively, the operation should + // atomic from the startup thread's point of view. + + if (r != KErrNone) + iStore->Reset(); + + return r; + } + +TInt TMMCPasswordStore::CompareCID(const TMapping& aLeft, const TMapping& aRight) +/** + * CID Comparason Functions for RArray::Find + */ + { + return(aLeft.iCID == aRight.iCID); + } + +void DMMCSocket::InitiatePowerUpSequence() +/** + * Initiates a power up sequence on the stack + */ + { + iStack->PowerUpStack(); + } + +TBool DMMCSocket::CardIsPresent() +/** + * Indicates the presence of a card. + * @return ETrue if a card is present, EFalse otherwise + */ + { + return(iStack->HasCardsPresent()); + } + +void DMMCSocket::AdjustPartialRead(const TMMCard* aCard, TUint32 aStart, TUint32 aEnd, TUint32* aPhysStart, TUint32* aPhysEnd) const +/** + * Calculates the minimum range that must be read off a card, an optimisation that takes advantage + * of the partial read feature found on some cards. It takes the logical range that the media driver + * wants to read from the card, and increases it to take into account factors such as FIFO width and + * minimum DMA transfer size. + * @param aCard A pointer to the MMC Card + * @param aStart The required start position + * @param aEnd The required end position + * @param aPhysStart The adjusted start position + * @param aPhysEnd The adjusted end position + */ + { + iStack->AdjustPartialRead(aCard, aStart, aEnd, aPhysStart, aPhysEnd); + } + +void DMMCSocket::GetBufferInfo(TUint8** aMDBuf, TInt* aMDBufLen) +/** + * Returns the details of the buffer allocated by the socket for data transfer operations. The buffer + * is allocated and configured at the variant layer to allow , for example, contiguous pages to be + * allocated for DMA transfers. + * @param aMDBuf A pointer to the allocated buffer + * @param aMDBufLen The length of the allocated buffer + */ + { + iStack->GetBufferInfo(aMDBuf, aMDBufLen); + } + +void DMMCSocket::Reset1() +/** + * Resets the socket by powering down the stack. + * If there are operations in progress (inCritical), this call will be deferred + * until the operation is complete. In the case of an emergency power down, + * this will occur immediately. + */ + { + if (iState == EPBusCardAbsent) + { + // Reset is result of card eject! + iStack->iStackState |= KMMCStackStateCardRemoved; + } + + + iStack->PowerDownStack(); + } + +void DMMCSocket::Reset2() +/** + * Resets the socket in response to a PSU fault or media change. + * Called after Reset1, gives the opportunity to free upp allocated resources + */ + { + // No need to do anything here, as the only thing to do is power down the + // stack, which is performed in ::Reset1 + } + +TInt DMMCSocket::Init() +/** + * Allocates resources and initialises the MMC socket and associated stack object. + * @return KErrNotReady if no stack has been allocated, standard error code otherwise + */ + { + __KTRACE_OPT(KPBUS1,Kern::Printf(">MMC:Init")); + + GetMachineInfo(); + + // We need to make sure the stack is initialised, + // as DPBusSocket::Init() will initiate a power up sequence + if(iStack == NULL) + return KErrNotReady; + + TInt r = iStack->Init(); + if (r!=KErrNone) + return r; + + r = DPBusSocket::Init(); + if (r!=KErrNone) + return r; + + return KErrNone; + } + +void DMMCSocket::GetMachineInfo() +/** + * Gets the platform specific configuration information. + * @see TMMCMachineInfo + */ + { + // Get machine info from the stack + iStack->MachineInfo(iMachineInfo); + + __KTRACE_OPT(KPBUS1, Kern::Printf(">GetMI : iTotalSockets %u", iMachineInfo.iTotalSockets)); + __KTRACE_OPT(KPBUS1, Kern::Printf(">GetMI : iTotalMediaChanges %u", iMachineInfo.iTotalMediaChanges)); + __KTRACE_OPT(KPBUS1, Kern::Printf(">GetMI : iTotalPrimarySupplies %u", iMachineInfo.iTotalPrimarySupplies)); + __KTRACE_OPT(KPBUS1, Kern::Printf(">GetMI : iSPIMode %u", iMachineInfo.iSPIMode)); + __KTRACE_OPT(KPBUS1, Kern::Printf(">GetMI : iBaseBusNumber %u", iMachineInfo.iBaseBusNumber)); + + } + + +// MMC specific functions + +EXPORT_C void DMMCSocket::Panic(TMMCPanic aPanic) +/** + * Panic the MMC Controller + * @param aPanic The panic code + */ + { + + _LIT(KPncNm,"PBUS-MMC"); + Kern::PanicCurrentThread(KPncNm,aPanic); + } + +EXPORT_C DMMCPsu::DMMCPsu(TInt aPsuNum, TInt aMediaChangedNum) +/** + * Constructor for a DMMCPsu object + * @param aPsuNum The power supply number + * @param aMediaChangedNum The associated media change number + */ + : DPBusPsuBase(aPsuNum, aMediaChangedNum) + { + + iVoltageSetting=0x00ffc000; // Default voltage range - 2.6V to 3.6V (OCR reg. format). + } + +EXPORT_C TInt DMMCPsu::DoCreate() +/** + * Create a DMMCPsu object. + * This should be overridden at the variant layer to allow interrupts and + * other variant-specific parameters to be initialised. The default + * implementation does nothing. + * @return Standard Symbian OS error code. + */ + { + return KErrNone; + } + + +void DMMCPsu::SleepCheck(TAny* aPtr) +/** + * Checks if media can be placed in Sleep state + * and therefore if VccQ supply can be turned off. + * + * @Param aPtr reference to DMMCPsu Object to be acted upon. + */ + { + DMMCPsu& self = *static_cast(aPtr); + + if ( + (self.iNotLockedTimeout&&!self.IsLocked()&&++self.iNotLockedCount>self.iNotLockedTimeout) || + (self.iInactivityTimeout&&++self.iInactivityCount>self.iInactivityTimeout) + ) + { + DMMCSocket* socket = static_cast(self.iSocket); + socket->iStack->QSleepStack(); + } + } + +EXPORT_C DMMCMediaChange::DMMCMediaChange(TInt aMediaChangeNum) +/** + * Constructor for a DMMCMediaChange object + * @param aMediaChangeNum The media change number + */ + : DMediaChangeBase(aMediaChangeNum) + {} + +EXPORT_C TInt DMMCMediaChange::Create() +/** + * Create a DMMCMediaChange object. + * This should be overridden at the variant layer to allow interrupts and + * other variant-specific parameters to be initialised. The base class implementation + * should be called prior to any variant-specific initialisation. + * @return Standard Symbian OS error code. + */ + { + return DMediaChangeBase::Create(); + } +