// Copyright (c) 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:
// Partition Management for Embedded MMC devices
// 
//

#include <drivers/emmcptn.h>

const TInt KDiskSectorShift=9;

class DLegacyEMMCPartitionInfo : public DEMMCPartitionInfo
	{
public:
	 DLegacyEMMCPartitionInfo();
	~DLegacyEMMCPartitionInfo();
public:
	virtual TInt Initialise(DMediaDriver* aDriver);
	virtual TInt PartitionInfo(TPartitionInfo& anInfo, const TMMCCallBack& aCallBack);
	virtual TInt PartitionCaps(TLocDrv& aDrive, TDes8& aInfo);
	
protected:
	void SetPartitionEntry(TPartitionEntry* aEntry, TUint aFirstSector, TUint aNumSectors);
	
private:
	static void SessionEndCallBack(TAny* aSelf);
		   void DoSessionEndCallBack();
	virtual TInt DecodePartitionInfo();
	
protected:
	DMediaDriver*   iDriver;
	TPartitionInfo* iPartitionInfo;
	TMMCCallBack	iSessionEndCallBack;
	TMMCCallBack 	iCallBack;         // Where to report the PartitionInfo completion
	DMMCSession*	iSession;
	TMMCard*		iCard;
	TUint8*			iIntBuf;
	};

DLegacyEMMCPartitionInfo::DLegacyEMMCPartitionInfo()
  : iSessionEndCallBack(DLegacyEMMCPartitionInfo::SessionEndCallBack, this)
	{
	}

DLegacyEMMCPartitionInfo::~DLegacyEMMCPartitionInfo()
	{
	delete iSession;
	}

TInt DLegacyEMMCPartitionInfo::Initialise(DMediaDriver* aDriver)
	{
	iDriver = aDriver;

	DMMCSocket* socket = ((DMMCSocket*)((DPBusPrimaryMedia*)(iDriver->iPrimaryMedia))->iSocket);
	if(socket == NULL)
		return(KErrNoMemory);

	DMMCStack* stack = socket->Stack(0);
	iCard = stack->CardP(((DPBusPrimaryMedia*)(iDriver->iPrimaryMedia))->iSlotNumber);
	
	iSession = stack->AllocSession(iSessionEndCallBack);
	if (iSession == NULL)
		return(KErrNoMemory);

	iSession->SetStack(stack);
	iSession->SetCard(iCard);

	// this gets used before any access
	TInt bufLen, minorBufLen;
	stack->BufferInfo(iIntBuf, bufLen, minorBufLen);

	return(KErrNone);
	}

TInt DLegacyEMMCPartitionInfo::PartitionInfo(TPartitionInfo& anInfo, const TMMCCallBack& aCallBack)
	{
	iPartitionInfo = &anInfo;
	iCallBack = aCallBack;
	// If media driver is persistent (see EMediaDriverPersistent), 
	// the card may have changed since last power down, so reset CID
	iSession->SetCard(iCard);
	
	iSession->SetupCIMReadBlock(0, iIntBuf);

	TInt r = iDriver->InCritical();
	if (r == KErrNone)
		r = iSession->Engage();

	if(r != KErrNone)
		iDriver->EndInCritical();
	
	return(r);
	}

TInt DLegacyEMMCPartitionInfo::PartitionCaps(TLocDrv& aDrive, TDes8& aInfo)
	{
	 TLocalDriveCapsV6Buf& Info = static_cast< TLocalDriveCapsV6Buf&> (aInfo);
	
	// is this query for the swap partition ?
	if (aDrive.iPartitionType == KPartitionTypePagedData)
		{
		Info().iFileSystemId = KDriveFileNone;
		Info().iDriveAtt|= KDriveAttHidden;
		}

	// is this query for the ROFS partition ?
	if (aDrive.iPartitionType == KPartitionTypeRofs)
		{
		Info().iFileSystemId = KDriveFileSysROFS;
		Info().iMediaAtt&= ~KMediaAttFormattable;
		Info().iMediaAtt|= KMediaAttWriteProtected;
		}
	
	// is this query for the ROM partition ?
	if (aDrive.iPartitionType == KPartitionTypeROM)
		{
		Info().iFileSystemId = KDriveFileNone;
		Info().iMediaAtt&= ~KMediaAttFormattable;
		Info().iMediaAtt|= KMediaAttWriteProtected;
		}
	
	return KErrNone;
	}

void DLegacyEMMCPartitionInfo::SessionEndCallBack(TAny* aSelf)
	{
	DLegacyEMMCPartitionInfo& self = *static_cast<DLegacyEMMCPartitionInfo*>(aSelf);
	self.DoSessionEndCallBack();
	}

void DLegacyEMMCPartitionInfo::DoSessionEndCallBack()
	{
	iDriver->EndInCritical();

	TInt r = iSession->EpocErrorCode();

	if (r == KErrNone)
		r = DecodePartitionInfo();

	iDriver->PartitionInfoComplete(r == KErrNone ? r : KErrNotReady);
	}

TInt DLegacyEMMCPartitionInfo::DecodePartitionInfo()
//
// decode partition info that was read into internal buffer 
//
	{
	TUint partitionCount=iPartitionInfo->iPartitionCount=0;
	TInt defaultPartitionNumber=-1;
	TMBRPartitionEntry* pe;
	const TUint KMBRFirstPartitionOffsetAligned = KMBRFirstPartitionOffset & ~3;
	TInt i;

	// Read of the first sector successful so check for a Master Boot Record
	if (*(TUint16*)(&iIntBuf[KMBRSignatureOffset])!=0xAA55)
		// If no valid signature give up now, No way to re-format an internal drive correctly
		return KErrCorrupt;

	__ASSERT_COMPILE(KMBRFirstPartitionOffsetAligned + KMBRMaxPrimaryPartitions * sizeof(TMBRPartitionEntry) <= KMBRSignatureOffset);

	memmove(&iIntBuf[0], &iIntBuf[2],
		KMBRFirstPartitionOffsetAligned + KMBRMaxPrimaryPartitions * sizeof(TMBRPartitionEntry)); 


	for (i=0, pe = (TMBRPartitionEntry*)(&iIntBuf[KMBRFirstPartitionOffsetAligned]);
		pe->iPartitionType != 0 && i < KMaxPartitionEntries; i++,pe--)
		{
		if (pe->IsDefaultBootPartition())
			{
			SetPartitionEntry(&iPartitionInfo->iEntry[0],pe->iFirstSector,pe->iNumSectors);
			defaultPartitionNumber=i;
			partitionCount++;
			break;
			}
		}

	// Now add any other partitions
	for (i=0, pe = (TMBRPartitionEntry*)(&iIntBuf[KMBRFirstPartitionOffsetAligned]);
		pe->iPartitionType != 0 && i < KMaxPartitionEntries; i++,pe--)
		{
		if (defaultPartitionNumber==i)
			{
			// Already sorted
			}

		// FAT partition ?
		else if (pe->IsValidDosPartition() || pe->IsValidFAT32Partition() || pe->IsValidExFATPartition())
			{
			SetPartitionEntry(&iPartitionInfo->iEntry[partitionCount],pe->iFirstSector,pe->iNumSectors);
			__KTRACE_OPT(KLOCDPAGING, Kern::Printf("Mmc: FAT partition found at sector #%u", pe->iFirstSector));
			partitionCount++;
			}

		else if (pe->iPartitionType == KPartitionTypeROM)
			{
			TPartitionEntry& partitionEntry = iPartitionInfo->iEntry[partitionCount];
			SetPartitionEntry(&iPartitionInfo->iEntry[partitionCount],pe->iFirstSector,pe->iNumSectors);
			partitionEntry.iPartitionType = pe->iPartitionType;
			partitionCount++;				 

			__KTRACE_OPT(KLOCDPAGING, Kern::Printf("Mmc: KPartitionTypeROM found at sector #%u", pe->iFirstSector));
			}

		// ROFS partition ?
		else if (pe->iPartitionType == KPartitionTypeRofs)
			{
			
// Don't expose this for normal operation only boot?			
			TPartitionEntry& partitionEntry = iPartitionInfo->iEntry[partitionCount];
			SetPartitionEntry(&iPartitionInfo->iEntry[partitionCount],pe->iFirstSector,pe->iNumSectors);
			partitionEntry.iPartitionType = pe->iPartitionType;
			__KTRACE_OPT(KLOCDPAGING, Kern::Printf("Mmc: KPartitionTypeRofs found at sector #%u", pe->iFirstSector));
			partitionCount++;
			}
 
		// Swap partition ?
		else if (pe->iPartitionType == KPartitionTypePagedData)
			{
			__KTRACE_OPT(KLOCDPAGING, Kern::Printf("Mmc: KPartitionTypePagedData found at sector #%u", pe->iFirstSector));

			TPartitionEntry& partitionEntry = iPartitionInfo->iEntry[partitionCount];
			SetPartitionEntry(&iPartitionInfo->iEntry[partitionCount],pe->iFirstSector,pe->iNumSectors);
			partitionEntry.iPartitionType = pe->iPartitionType;
			partitionCount++;
			}
		}

	// Check the validity of the partition address boundaries
	// If there is any MBR errors
	if(partitionCount > 0)
		{
		const TInt64 deviceSize = iCard->DeviceSize64();
		TPartitionEntry& part = iPartitionInfo->iEntry[partitionCount - 1];
		// Check that the card address space boundary is not exceeded by the last partition
		if(part.iPartitionBaseAddr + part.iPartitionLen > deviceSize)
			{
			__KTRACE_OPT(KPBUSDRV, Kern::Printf("Mmc: MBR partition exceeds card memory space"));
			return KErrCorrupt;
			}
		
		// More than one partition. Go through all of them
		if (partitionCount > 0)
			{
			for(i=partitionCount-1; i>0; i--)
				{
				const TPartitionEntry& curr = iPartitionInfo->iEntry[i];
				TPartitionEntry& prev = iPartitionInfo->iEntry[i-1];
				// Check if partitions overlap
				if(curr.iPartitionBaseAddr < (prev.iPartitionBaseAddr + prev.iPartitionLen))
					{
					__KTRACE_OPT(KPBUSDRV, Kern::Printf("Mmc: Overlapping partitions"));
					return KErrCorrupt;
					}
				}
			}
		}

	if (defaultPartitionNumber==(-1) && partitionCount==0)
		{
		__KTRACE_OPT(KPBUSDRV, Kern::Printf("No Valid Partitions Found!"));
		return KErrCorrupt;
		}

	iPartitionInfo->iPartitionCount=partitionCount;
	iPartitionInfo->iMediaSizeInBytes=iCard->DeviceSize64();

#ifdef _DEBUG
	__KTRACE_OPT(KPBUSDRV, Kern::Printf("<Mmc:PartitionInfo (C:%d)",partitionCount));
	for (TUint x=0; x<partitionCount; x++)
		__KTRACE_OPT(KPBUSDRV, Kern::Printf("     Partition%d (B:%xH L:%xH)",x,I64LOW(iPartitionInfo->iEntry[x].iPartitionBaseAddr),I64LOW(iPartitionInfo->iEntry[x].iPartitionLen)));
#endif

	//Notify medmmc that partitioninfo is complete.
	iCallBack.CallBack();
	
	return(KErrNone);
	}


void DLegacyEMMCPartitionInfo::SetPartitionEntry(TPartitionEntry* aEntry, TUint aFirstSector, TUint aNumSectors)
//
// auxiliary static function to record partition information in TPartitionEntry object
//
	{
	aEntry->iPartitionBaseAddr=aFirstSector;
	aEntry->iPartitionBaseAddr<<=KDiskSectorShift;
	aEntry->iPartitionLen=aNumSectors;
	aEntry->iPartitionLen<<=KDiskSectorShift;
	aEntry->iPartitionType=KPartitionTypeFAT12;
	}

// End - DLegacyEMMCPartitionInfo


EXPORT_C DEMMCPartitionInfo* CreateEmmcPartitionInfo()
	{
	return new DLegacyEMMCPartitionInfo;
	}

DECLARE_STANDARD_EXTENSION()
	{
	return KErrNone;
	}
