// Copyright (c) 2010 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 <emmcptn.h>
#include "bgahsmmcptn.h"
#include "toc.h"
//#define __DEBUG_PARTITIONS_
//#define __DEBUG_CHECK_PARTITION_
const TInt    KDiskSectorShift          = 9;

class DBB5PartitionInfo : public DEMMCPartitionInfo
	{
public:
	 DBB5PartitionInfo();
	~DBB5PartitionInfo();

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:
	virtual TInt ReadPartition(TUint32 aPtOffset);
	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;
	TUint32 		iPartitionAttributes[KMaxLocalDrives];
	TBool           iCheckTOC;
	Toc*            iTocPtr;
	};

DBB5PartitionInfo::DBB5PartitionInfo()
  : iSessionEndCallBack(DBB5PartitionInfo::SessionEndCallBack, this),
    iCheckTOC(EFalse)
	{
	}

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

TInt DBB5PartitionInfo::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 DBB5PartitionInfo::PartitionInfo(TPartitionInfo& aInfo, const TMMCCallBack& aCallBack)
	{
	iPartitionInfo = &aInfo;
	iCallBack = aCallBack;

	// Preferred partition scheme is BB5, which is located in the last block of the media.
    const TUint32 ptiOffset = (I64LOW(iCard->DeviceSize64() >> KDiskSectorShift)) - KPIOffsetFromMediaEnd;
	return ReadPartition(ptiOffset);
	}
	
TInt DBB5PartitionInfo::ReadPartition(TUint32 aPtOffset)
    {
	// If media driver is persistent (see EMediaDriverPersistent)
	// the card may have changed since last power down, so reset CID
	iSession->SetCard(iCard);

	iSession->SetupCIMReadBlock(aPtOffset, iIntBuf);
	
	TInt r = iDriver->InCritical();
	if (r == KErrNone)
		r = iSession->Engage();

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

	return(r);
	}

TInt DBB5PartitionInfo::PartitionCaps(TLocDrv& aDrive, TDes8& aInfo)
	{
	TLocalDriveCapsV6Buf& Info = static_cast< TLocalDriveCapsV6Buf&> (aInfo);

	if (aDrive.iPartitionType == KPartitionTypePagedData)
		{
		Info().iFileSystemId = KDriveFileNone;
		Info().iDriveAtt |= KDriveAttHidden;
		}
	else if (aDrive.iPartitionType == KPartitionTypeRofs)
		{
		Info().iFileSystemId = KDriveFileSysROFS;
		Info().iMediaAtt &= ~KMediaAttFormattable;
		Info().iMediaAtt |= KMediaAttWriteProtected;
		}
	else if ((aDrive.iPartitionType == KPartitionTypeROM) ||
			 (aDrive.iPartitionType == KPartitionTypeEmpty))
		{
		Info().iFileSystemId = KDriveFileNone;
		Info().iMediaAtt &= ~KMediaAttFormattable;
		Info().iMediaAtt |= KMediaAttWriteProtected;
		}
    else if ((aDrive.iPartitionType == KPartitionTypePartitionMagic) || //CPS/PMM
             (aDrive.iPartitionType == KPartitionTypeSymbianCrashLog))
        {
        Info().iFileSystemId = KDriveFileNone;
        Info().iMediaAtt |= KMediaAttFormattable;
        }
	else if ( PartitionIsFAT(aDrive.iPartitionType) || PartitionIsFAT32(aDrive.iPartitionType)	)
		{		
		Info().iDriveAtt |= iPartitionAttributes[aDrive.iPartitionNumber];
		}

	return KErrNone;
	}

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

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

	TInt r = iSession->EpocErrorCode();

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

	if (!iCheckTOC)
	    {        
	    iDriver->PartitionInfoComplete(r == KErrNone ? r : KErrNotReady);
	    }
	}

TInt DBB5PartitionInfo::DecodePartitionInfo()
//
// Decode partition info that was read into internal buffer
//
	{
	__KTRACE_OPT(KPBUSDRV, Kern::Printf(">Mmc:PartitionInfo()"));
	TUint partitionCount = iPartitionInfo->iPartitionCount = 0;

	
	if (iCheckTOC)
	    {
        // Try utilising the TOC (Table Of Contents) partitioning scheme 
        const TText8* KRofsNames[KNoOfROFSPartitions] = { KTocRofs1Generic,
                                                          KTocRofs2Generic,
                                                          KTocRofs3Generic,
                                                          KTocRofs4Generic,
                                                          KTocRofs5Generic,                                                            
                                                          KTocRofs6Generic,                                                            
                                                          };
                                        
        STocItem item;
        iTocPtr = reinterpret_cast<Toc*>(&iIntBuf[0]);
        iTocPtr->iTocStartSector = KTocStartSector;
        TInt r = KErrNone;

// USER Drive - Only 1        
        r = iTocPtr->GetItemByName(KTocUserName, item); 
        if (KErrNone == r)
            {
            __KTRACE_OPT(KPBUSDRV, Kern::Printf("[MD  :   ] (%11s) in TOC found : Start addr = 0x%X  Size = 0x%X", item.iFileName, item.iStart, item.iSize));
            iPartitionInfo->iEntry[partitionCount].iPartitionType     = KPartitionTypeFAT16;           
            iPartitionInfo->iEntry[partitionCount].iPartitionBaseAddr = (Int64)item.iStart;                         
            iPartitionInfo->iEntry[partitionCount].iPartitionLen      = (Int64)item.iSize;
            iPartitionAttributes[partitionCount] = 0; // No Additional Attributes required.           
            partitionCount++;
            }   
        
// ROM Drive        
        r = iTocPtr->GetItemByName(KTocRomGeneric, item); 
        if (KErrNone == r)
            {
            __KTRACE_OPT(KPBUSDRV, Kern::Printf("[MD  :   ] (%11s) in TOC found : Start addr = 0x%x  Size = 0x%x", item.iFileName, item.iStart, item.iSize));
            iPartitionInfo->iEntry[partitionCount].iPartitionType     = KPartitionTypeROM;           
            iPartitionInfo->iEntry[partitionCount].iPartitionBaseAddr = (Int64) item.iStart + (KBB5HeaderSizeInSectors << KDiskSectorShift);                         
            iPartitionInfo->iEntry[partitionCount].iPartitionLen      = (Int64) item.iSize - (KBB5HeaderSizeInSectors << KDiskSectorShift);          
            partitionCount++;
            }
        
// ROFS            
        for (TUint i = 0; i < KNoOfROFSPartitions; i++)
            {
            /* Search ROFSn item */            
            r = iTocPtr->GetItemByName(KRofsNames[i], item);
            if (r == KErrNone)
                {
                __KTRACE_OPT(KPBUSDRV, Kern::Printf("[MD  :   ] (%11s) in TOC found : Start addr = 0x%X  Size = 0x%X", item.iFileName, item.iStart, item.iSize));
                iPartitionInfo->iEntry[partitionCount].iPartitionType     = KPartitionTypeRofs;           
                iPartitionInfo->iEntry[partitionCount].iPartitionBaseAddr = (Int64) item.iStart + (KBB5HeaderSizeInSectors << KDiskSectorShift);                         
                iPartitionInfo->iEntry[partitionCount].iPartitionLen      = (Int64) item.iSize - (KBB5HeaderSizeInSectors << KDiskSectorShift);
                partitionCount++;
                }
            }         

// CPS Drive - Only 1        
        r = iTocPtr->GetItemByName(KTocCps, item); 
        if (KErrNone == r)
            {
            __KTRACE_OPT(KPBUSDRV, Kern::Printf("[MD  :   ] (%11s) in TOC found : Start addr = 0x%X  Size = 0x%X", item.iFileName, item.iStart, item.iSize));
            iPartitionInfo->iEntry[partitionCount].iPartitionType     = KPartitionTypePartitionMagic;           
            iPartitionInfo->iEntry[partitionCount].iPartitionBaseAddr = (Int64) item.iStart;                         
            iPartitionInfo->iEntry[partitionCount].iPartitionLen      = (Int64) item.iSize;
            partitionCount++;
            }
        
// CRASH Drive - Only 1        
        r = iTocPtr->GetItemByName(KTocCrashLog, item); 
        if (KErrNone == r)
            {
            __KTRACE_OPT(KPBUSDRV, Kern::Printf("[MD  :   ] (%11s) in TOC found : Start addr = 0x%X  Size = 0x%X", item.iFileName, item.iStart, item.iSize));
            iPartitionInfo->iEntry[partitionCount].iPartitionType     = KPartitionTypeSymbianCrashLog;           
            iPartitionInfo->iEntry[partitionCount].iPartitionBaseAddr = (Int64) item.iStart;                         
            iPartitionInfo->iEntry[partitionCount].iPartitionLen      = (Int64) item.iSize;
            partitionCount++;
            }
        
// SWAP Partition - Only 1        
        r = iTocPtr->GetItemByName(KTocSwap, item); 
        if (KErrNone == r)
            {
            __KTRACE_OPT(KPBUSDRV, Kern::Printf("[MD  :   ] (%11s) in TOC found : Start addr = 0x%X  Size = 0x%X", item.iFileName, item.iStart, item.iSize));
            iPartitionInfo->iEntry[partitionCount].iPartitionType     = KPartitionTypePagedData;           
            iPartitionInfo->iEntry[partitionCount].iPartitionBaseAddr = (Int64) item.iStart;                         
            iPartitionInfo->iEntry[partitionCount].iPartitionLen      = (Int64) item.iSize;
            partitionCount++;
            }                

#ifdef __DEBUG_PARTITIONS_
        for (TInt i = 0; i<partitionCount; i++)
            {
            Kern::Printf("iPartitionType....: %d", iPartitionInfo->iEntry[i].iPartitionType);                
            Kern::Printf("iPartitionBaseAddr: 0x%lx (sectors: %d)", iPartitionInfo->iEntry[i].iPartitionBaseAddr, (TUint32)(iPartitionInfo->iEntry[i].iPartitionBaseAddr >> KDiskSectorShift));                
            Kern::Printf("iPartitionLen.....: 0x%lx (sectors: %d)", iPartitionInfo->iEntry[i].iPartitionLen, iPartitionInfo->iEntry[i].iPartitionLen >> KDiskSectorShift);
            Kern::Printf("iPartitionAttribs.: 0x%x", iPartitionAttributes[i]);
            Kern::Printf(" ");
            }
#endif //__DEBUG_PARTITIONS_
        
        iCheckTOC = EFalse;
	    }
	else
	    {
        // Try utilising the BB5 partitioning scheme	
        BGAHSMMCPTN_PI_STR *partitionTable = (BGAHSMMCPTN_PI_STR*)(&iIntBuf[0]);
    
        // Verify that this is the Nokia partition table
        if( memcompare( (TUint8*)&(partitionTable->iId[0]), sizeof(BGAHSMMCPTN_PI_ID), (TUint8*)BGAHSMMCPTN_PI_ID, sizeof(BGAHSMMCPTN_PI_ID)) == 0 )
            {
			__KTRACE_OPT(KPBUSDRV, Kern::Printf("Nokia partition structure found"));
			__KTRACE_OPT(KPBUSDRV, Kern::Printf("partitionTable->id..............: %s", partitionTable->iId ));
			__KTRACE_OPT(KPBUSDRV, Kern::Printf("partitionTable->sector_size.....: %d = 0x%x", partitionTable->iSector_size, partitionTable->iSector_size));
			__KTRACE_OPT(KPBUSDRV, Kern::Printf("partitionTable->major_ver.......: %d", partitionTable->iMajor_ver));
			__KTRACE_OPT(KPBUSDRV, Kern::Printf("partitionTable->minor_ver.......: %d", partitionTable->iMinor_ver));
			__KTRACE_OPT(KPBUSDRV, Kern::Printf("partitionTable->partition_amount: %d", partitionTable->iPartition_amount));
            
            TUint8 partitionType = 0;		
            // Check Supported Version is present
            if (partitionTable->iMajor_ver <= BGAHSMMCPTN_PI_VER_MAJOR)
                {
                for( TUint8 index = 0; (index < partitionTable->iPartition_amount) && (index < BGAHSMMCPTN_LAST_DRIVE); index++ )
                    {
                    if (partitionTable->iMinor_ver >= BGAHSMMCPTN_PART_TYPE_SUPP_VER_MINOR)
                        partitionType = partitionTable->iPartitions[index].iPartition_type;
                    else                    
                        partitionType = partitionTable->iPartitions[index].iPartition_id;
                
                    // FAT/PMM/CPS/SWAP/CORE/ROFS/CRASH
                    if( (partitionTable->iPartitions[index].iSize > 0) &&
                        ( PartitionIsFAT(partitionType) ||
                          PartitionIsFAT32(partitionType) ||
                         (KPartitionTypeSymbianCrashLog == partitionType) ||
                         (KPartitionTypePartitionMagic == partitionType) || //CPS/PMM
                         (KPartitionTypeRofs == partitionType) || 
                         (KPartitionTypeEmpty == partitionType) ||
                         (KPartitionTypeROM == partitionType) ||
                         (KPartitionTypePagedData == partitionType) ) )
                        {                   
                        iPartitionInfo->iEntry[partitionCount].iPartitionType	  = partitionType;
                        iPartitionAttributes[partitionCount]                      = partitionTable->iPartitions[index].iPartition_attributes;
                        
                        // ROM/ROFS partitions have a BB5 checksum header that must be offset for the Symbian OS.
                        const TUint32 KstartOffset = ((KPartitionTypeROM == partitionType) || (KPartitionTypeRofs == partitionType) || (KPartitionTypeEmpty == partitionType)) ? KBB5HeaderSizeInSectors : 0;
                        
                        iPartitionInfo->iEntry[partitionCount].iPartitionBaseAddr = (Int64) ((partitionTable->iPartitions[index].iStart_sector + KstartOffset) << KDiskSectorShift);
                        iPartitionInfo->iEntry[partitionCount].iPartitionLen      = (Int64) ((partitionTable->iPartitions[index].iSize - KstartOffset) << KDiskSectorShift);
        
                    	__KTRACE_OPT(KPBUSDRV, Kern::Printf("Registering partition #%d:", partitionCount));
                    	__KTRACE_OPT(KPBUSDRV, Kern::Printf("partitionCount....: %d", partitionCount));
                    	__KTRACE_OPT(KPBUSDRV, Kern::Printf("startSector.......: 0x%x", partitionTable->iPartitions[index].iStart_sector ));
                    	__KTRACE_OPT(KPBUSDRV, Kern::Printf("iPartitionBaseAddr: 0x%lx (sectors: %d)", iPartitionInfo->iEntry[partitionCount].iPartitionBaseAddr, (TUint32)(iPartitionInfo->iEntry[partitionCount].iPartitionBaseAddr >> KDiskSectorShift)));
                    	__KTRACE_OPT(KPBUSDRV, Kern::Printf("size..............: 0x%lx", partitionTable->iPartitions[index].iSize ));
                    	__KTRACE_OPT(KPBUSDRV, Kern::Printf("iPartitionLen.....: 0x%lx (sectors: %d)", iPartitionInfo->iEntry[partitionCount].iPartitionLen, iPartitionInfo->iEntry[partitionCount].iPartitionLen >> KDiskSectorShift));
                    	__KTRACE_OPT(KPBUSDRV, Kern::Printf("iPartitionType....: %d", iPartitionInfo->iEntry[partitionCount].iPartitionType));
                    	__KTRACE_OPT(KPBUSDRV, Kern::Printf("iPartitionAttribs.: 0x%x", iPartitionAttributes[partitionCount]));
                    	__KTRACE_OPT(KPBUSDRV, Kern::Printf(" "));
        
                        partitionCount++;
                        }
                    }
                } 
            }
        else
            {
            __KTRACE_OPT(KPBUSDRV, Kern::Printf("BGAHSMMC signature not found - try TOC layout"));
            iCheckTOC = ETrue;
            
            TInt r = ReadPartition(KTocStartSector);
            return r;
            }
	    }
	
	
	// Validate partition address boundaries
	if(partitionCount == 0)
		{
		__KTRACE_OPT(KPBUSDRV, Kern::Printf("Mmc: No supported partitions found!"));
		return KErrCorrupt;
		}
#ifdef __DEBUG_CHECK_PARTITION_	
	else
		{
		// at least one entry for a supported partition found
		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;
			}

		// Go through all partition entries and check boundaries
		for(TInt 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 - check #%d", i));
				return KErrCorrupt;
				}
			}
		}
#endif // _DEBUG_CHECK_PARTITION_

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

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

	__KTRACE_OPT(KPBUSDRV, Kern::Printf("<Mmc:PartitionInfo (C:%d)", partitionCount));
	return KErrNone;
	}


// End - DBB5PartitionInfo


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

DECLARE_STANDARD_EXTENSION()
	{
	return KErrNone;
	}

//	End of File

