// Copyright (c) 1996-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:
// f32\sfat32\sl_fatcache.cpp
// FAT12 and FAT16 cache implementation
// 
//

/**
 @file
*/

#include "sl_std.h"
#include "sl_fatcache.h"


//#################################################################################################################################
//  CFatCacheBase implementation
//  Base class for all types of FAT cache
//#################################################################################################################################

CFatCacheBase::~CFatCacheBase()
    {
    Close(ETrue); //-- deallocate cache's memory discarding any dirty data
    }

CFatCacheBase::CFatCacheBase()
    {
    iCurrentFatNo = KInvalidFatNo;
    SetDirty(EFalse);
    }


/**
    FAT cache initialisation.

    @param  aOwner pointer to the owning FAT mount
*/
void CFatCacheBase::InitialiseL(CFatMountCB* aOwner)
    {
    ASSERT(aOwner);
    
    Close(ETrue); //-- deallocate cache's memory discarding any dirty data
    
    //-- populate parameters from the owning mount
    iFatType = aOwner->FatType();
    __ASSERT_ALWAYS((iFatType == EFat12 || iFatType == EFat16 || iFatType == EFat32), User::Leave(KErrCorrupt));  
    
    ipDrive = &aOwner->DriveInterface();
    iFatStartPos = aOwner->FirstFatSector() << aOwner->SectorSizeLog2(); 
    iFatSize = aOwner->FatSizeInBytes();
    iNumFATs = (TUint16)aOwner->NumberOfFats();
    iFatSecSzLog2   = (TUint16)aOwner->SectorSizeLog2(); 
    iFatClustSzLog2 = (TUint16)aOwner->ClusterSizeLog2();

    __ASSERT_ALWAYS(iNumFATs >=1, User::Leave(KErrCorrupt));

    __PRINT3(_L("#-CFatCacheBase::InitialiseL() FatStart:%u, FatSz:%d, drv:%d"),iFatStartPos, iFatSize, aOwner->DriveNumber());
    }

//-----------------------------------------------------------------------------
/**
    This method shall be called to check if we are allowed to invalidate dirty cache, i.e. discard non-flushed data.
    The behaviour is hardcoded (see KAllowInvalidateDirtyCache constant)

    @return ETrue if invalidating dirty cache is allowed. Otherwise panics the current thread
*/
TBool CFatCacheBase::CheckInvalidatingDirtyCache() const
    {
    
    //-- If not EFalse, invalidating dirty cache (pages) is allowed. This shall be OK, because
    //-- invalidating the cache is required only after direct media writes to the FAT by RawWrite, which can corrupt it anyway. 
    TBool KAllowInvalidateDirtyCache = ETrue;
    
    if(!IsDirty())
        return KAllowInvalidateDirtyCache;        

    __PRINT(_L("#-CFatCacheBase::Invalidating dirty cache !"));
    
    if(!KAllowInvalidateDirtyCache)
        {
        __ASSERT_ALWAYS(0, Fault(EFatCache_DiscardingDirtyData));
        }

    return KAllowInvalidateDirtyCache;        
    }

//-----------------------------------------------------------------------------

/**
    Read portion of raw data from 1st FAT copy.
    
    @param  aPos   media position in the _FIRST_ FAT to start reading with
    @param  aLen   number of bytes to read
    @param  aData  data descriptor

    @return standard error code.
*/
TInt CFatCacheBase::ReadFatData(TUint32 aPos, TUint32 aLen, TDes8& aData) const
    {
    //__PRINT2(_L("#-CFatCacheNew::ReadFatData() pos:%u, Len:%d"), aPos, aLen);

    //-- this method can pick up data corresponding to invalid FAT entries, like FAT[0], FAT[1] and
    //-- the last portion beyond FAT because of read granularity. This isn't a problem, because the data there
    //-- won't be written on disk.
    ASSERT(aPos >= FatStartPos()); 

    return ipDrive->ReadNonCritical(aPos, aLen, aData);
    }

//-----------------------------------------------------------------------------

/** 
    Writes data to the FAT table, which number is set in iCurrentFatNo member variable. 
    @param  aPos   data media position in the _FIRST_ FAT copy
    @param  aData  data descriptor
    @return standard error code.
*/
TInt CFatCacheBase::WriteFatData(TUint32 aPos, const TDesC8& aData) const
    {
    //__PRINT3(_L("#-CFatCacheBase::WriteFatData() pos:%u, Len:%d, FAT:%d"), aPos, aData.Length(), iCurrentFatNo);

#ifdef _DEBUG    
    //-- FAT[0] and FAT[1] entries are reserved and we must not write data there. It's up to the caller of this method to
    //-- calculate correct data position in FAT
    TInt reserved_Entries_Offset=0;
    switch(iFatType)
        {
        case EFat32: reserved_Entries_Offset = KFatFirstSearchCluster*sizeof(TFat32Entry); break;  //-- FAT32  
        case EFat16: reserved_Entries_Offset = KFatFirstSearchCluster*sizeof(TFat16Entry); break;  //-- FAT16  
        case EFat12: reserved_Entries_Offset = 3;   break;                                         //-- FAT12
        default: ASSERT(0); break;
        }
    ASSERT(aPos >= FatStartPos()+reserved_Entries_Offset);
    ASSERT((aPos+aData.Length()) <= FatStartPos()+FatSize());
    ASSERT(iCurrentFatNo < iNumFATs);
#endif    

    //-- goto the required FAT copy. iCurrentFatNo shall contain FAT number we are writing to.
    aPos+=iCurrentFatNo*FatSize(); 

    return ipDrive->WriteCritical(aPos, aData);
    }

//-----------------------------------------------------------------------------
/** 
    get a pointer to the CFatBitCache interface. 
    @return NULL because it is not present here 
*/
CFatBitCache* CFatCacheBase::BitCacheInterface() 
    {
    return NULL;
    }
    

//#################################################################################################################################
//  CFatPagedCacheBase implementation
//  Base class for all paged FAT caches
//#################################################################################################################################

CFatPagedCacheBase::CFatPagedCacheBase()
               :CFatCacheBase() 
    {
    }


//#################################################################################################################################
//  CFatCachePageBase implementation
//  Base class for FAT cache pages (FAT16 fixed and FAT32 LRU)
//#################################################################################################################################

CFatCachePageBase::CFatCachePageBase(CFatPagedCacheBase& aCache)
                  :iCache(aCache)
    {
    ASSERT(IsPowerOf2(aCache.PageSize()));
    iStartIndexInFAT = KMaxTUint;

    //-- calculate number of FAT entries in the page, it depends on FAT type
    switch(aCache.FatType())
        {
        case EFat32:
            iFatEntriesInPage = PageSize() >> KFat32EntrySzLog2;
        break;
    
        case EFat16:
            iFatEntriesInPage = PageSize() >> KFat16EntrySzLog2;
        break;
    
        default:
            ASSERT(0);
            Fault(EFatCache_BadFatType);
        break;

        };

    SetState(EInvalid); 
    }

CFatCachePageBase::~CFatCachePageBase()
    {
    iData.Close();
    }

//-----------------------------------------------------------------------------
/**
    Mark the page as "invalid". I.e containing inalid data.
    On the first read/write access to such page it will be re-read from the media

    @param aIgnoreDirtyData if ETrue, it is allowed to ignore the fact that the page contains dirty (not flushed) data.
*/
void CFatCachePageBase::Invalidate(TBool aIgnoreDirtyData /*= EFalse*/)
    {
    if(!aIgnoreDirtyData && IsDirty())
        {
        __PRINT1(_L("#-CFatCachePageBase::Invalidate() dirty page! FAT idx:%d"), iStartIndexInFAT);
        __ASSERT_ALWAYS(0, Fault(EFatCache_DiscardingDirtyData));
        }

    iDirtySectors.Clear(); //-- clear dirty sectors bitmap
    SetState(EInvalid);
    }

//-----------------------------------------------------------------------------
/**
    Flush all dirty page sectors to the media and mark the page as "clean" if required.
    If the page is "clean" i.e doesn't contain changed data, does nothing.

    @param  aKeepDirty  if ETrue, the "dirty" flag isn't reset after page flushing.
*/
void CFatCachePageBase::FlushL(TBool aKeepDirty)
    {
    if(!IsDirty())
        return;

    if(!IsValid())
        {
        __PRINT1(_L("#-CFatCachePageBase::FlushL() Invalid page! FAT idx:%d"), iStartIndexInFAT);
        ASSERT(0);
        User::Leave(KErrCorrupt);
        return;
        }

    //__PRINT1(_L("#-CFatCachePageBase::FlushL() FAT idx:%d"), iStartIndexInFAT);

    //-- write dirty FAT sectors  to the media one by one. 
    //-- merging adjacent dirty subsectors into larger clusters and writing them at once looks like a good idea, but
    //-- in reality it showed FAT performance degradation, at least on MMC/SD media.
    
    const TInt MaxSectors = iCache.SectorsInPage();
    
    for(TInt i=0; i<MaxSectors; ++i)
        {
        if(iDirtySectors[i])
            {
            DoWriteSectorL(i);
            }
        }

    //-- All data flushed; mark page as clean if it isn't required not to do.
    if(!aKeepDirty)
        SetClean(); 

    }


//#################################################################################################################################
//  CFat16FixedCache implementation
//  Fixed cache (caches all FAT16) but organised as an array of pages
//#################################################################################################################################

CFat16FixedCache::CFat16FixedCache()
                 :CFatPagedCacheBase(),iPages(1) //-- array granularity is 1
    {
    }

//-----------------------------------------------------------------------------
/**
    FAT16 fixed cache factory function.
    @param  aOwner              pointer to the owning FAT mount
    @param  aFatSize            size of the FAT table in bytes
    @param  aRdGranularityLog2  Log2(read granularity)
    @param  aWrGranularityLog2  Log2(write granularity)

    @return pointer to the constructed object.
*/
CFat16FixedCache* CFat16FixedCache::NewL(CFatMountCB* aOwner, TUint32 aFatSize, TUint32 aRdGranularityLog2, TUint32 aWrGranularityLog2)
    {
    __PRINT(_L("#-CFat16FixedCache::NewL()"));

    CFat16FixedCache* pSelf = NULL;
    pSelf = new (ELeave) CFat16FixedCache;

    CleanupStack::PushL(pSelf);
    pSelf->InitialiseL(aOwner, aFatSize, aRdGranularityLog2, aWrGranularityLog2);
    CleanupStack::Pop();
    
    return pSelf;
    }

//-----------------------------------------------------------------------------
/**
    FAT16 fixed cache initialisation.
    @param  aOwner              pointer to the owning FAT mount
    @param  aFatSize            size of the FAT table in bytes
    @param  aRdGranularityLog2  Log2(read granularity)
    @param  aWrGranularityLog2  Log2(write granularity)
*/
void CFat16FixedCache::InitialiseL(CFatMountCB* aOwner, TUint32 aFatSize, TUint32 aRdGranularityLog2, TUint32 aWrGranularityLog2)
    {
    const TUint32 ReadGranularity = Pow2(aRdGranularityLog2);
    const TUint32 WriteGranularity = Pow2(aWrGranularityLog2);

    __PRINT3(_L("#-CFat16FixedCache::InitialiseL FatSz:%u, RdGr:%d, WrGr:%d"),aFatSize, ReadGranularity, WriteGranularity);
    (void)ReadGranularity;
    (void)WriteGranularity;

    TBool bParamsValid = (aRdGranularityLog2 >= aWrGranularityLog2) && (aWrGranularityLog2 >= KDefSectorSzLog2);
    __ASSERT_ALWAYS(bParamsValid, Fault(EFatCache_BadGranularity));

    CFatPagedCacheBase::InitialiseL(aOwner);
    
    ASSERT(FatType() == EFat16);

    //-- See FAT specs, and round up the limit to the FAT sector boundary
    const TUint32 KMaxFat16Size = ((65524*sizeof(TFat16Entry)+FAT_SectorSz()-1) >> FAT_SectorSzLog2()) << FAT_SectorSzLog2(); 
    const TUint32 KMinFat16Size = 4086*sizeof(TFat16Entry);  //-- See FAT specs
    
    bParamsValid = aFatSize >= KMinFat16Size && aFatSize <= KMaxFat16Size;
	__ASSERT_ALWAYS(bParamsValid, User::Leave(KErrCorrupt));

    //-- cache page size is (2^aRdGranularityLog2) bytes and consists of 2^(aRdGranularityLog2-aWrGranularity) sectors.
    iPageSizeLog2 = aRdGranularityLog2;
    iSectorSizeLog2 = aWrGranularityLog2; //-- Log2(number of sectors in cache page)
    
    __ASSERT_ALWAYS(SectorsInPage() < KMaxSectorsInPage, Fault(EFatCache_BadGranularity));

    const TUint numPages = (aFatSize+(PageSize()-1)) >> iPageSizeLog2;
    __PRINT1(_L("#-CFat16FixedCache Num Pages:%d"), numPages);

    //-- prepare pointer array for pages. NULL entry in the array means that the page at this index isn't allocated.
    for(TUint i=0; i<numPages; ++i)
        iPages.AppendL(NULL);
    
    }


//-----------------------------------------------------------------------------
/**
    Close the cache and deallocate its memory.
    @param  aDiscardDirtyData if ETrue, will ignore dirty data. If EFalse, will panic on atempt to close dirty cache.  
*/
void CFat16FixedCache::Close(TBool aDiscardDirtyData)
    {
    __PRINT1(_L("#-CFat16FixedCache::Close(%d)"), aDiscardDirtyData);

    TInt cnt = iPages.Count();
    while(cnt--)
        {//-- delete pages
        CFat16FixedCachePage *pPage = iPages[cnt];
        if(pPage && (pPage->IsDirty()))
            {//-- trying to destroy the cache that has dirty pages
            __PRINT1(_L("#-CFat16FixedCache::Close() The page is dirty! Start idx:%d"), pPage->StartFatIndex());
            if(!aDiscardDirtyData)
                {
                __ASSERT_ALWAYS(0, Fault(EFatCache_DiscardingDirtyData));
                }
            //-- ignore this fact if requested.
            }
        
        delete pPage;
        }

    iPages.Close();
    SetDirty(EFalse);
    }

//-----------------------------------------------------------------------------
/**
    Read FAT entry from the cache. 

    @param  aIndex FAT entry index to read
    @return FAT entry value at the index "aIndex" 
*/
TUint32 CFat16FixedCache::ReadEntryL(TUint32 aIndex)
    {
    //__PRINT1(_L("#-CFat16FixedCache::ReadEntryL() FAT idx:%d"), aIndex);
    ASSERT(aIndex >= KFatFirstSearchCluster &&  aIndex < (FatSize() >> KFat16EntrySzLog2));

    //-- calculate page index in the array. Theoretically, aIndex can't be wrong because it is checked by the caller;
    //-- but in some strange situations (malformed volume ?) aIndex might get bigger than number of usable clusters.
    const TUint pgIdx = aIndex >> (PageSizeLog2()-KFat16EntrySzLog2);
    
    if(pgIdx >= (TUint)iPages.Count())
        {
        __PRINT1(_L("#-CFat16FixedCache::ReadEntryL() FAT idx:%d #1"), aIndex);
        ASSERT(0);
        User::Leave(KErrCorrupt);
        }
    
    CFat16FixedCachePage *pPage = iPages[pgIdx];
    
    TUint32 entry = KMaxTUint;

    if(!pPage)
        {//-- page at this position isn't allocated yet
        pPage = CFat16FixedCachePage::NewL(*this);
        iPages[pgIdx] = pPage;
        
        //-- read the page from media
        entry = pPage->ReadFromMediaL(aIndex);
        }
    else
        {//-- get cached entry from the page
        TBool bRes = pPage->ReadCachedEntryL(aIndex, entry);
        ASSERT(bRes);
        (void)bRes;
        }

    return entry;
    }

//-----------------------------------------------------------------------------
/**
    Write FAT entry to the cache. 
    Appropriate FAT cache sector will be marked as "dirty" and will be eventually flushed to the media.

    @param  aIndex FAT entry index
    @param  aEntry FAT entry value
*/
void CFat16FixedCache::WriteEntryL(TUint32 aIndex, TUint32 aEntry)
    {
    //__PRINT2(_L("#-CFat16FixedCache::WriteEntryL() FAT idx:%d, val:%d"), aIndex, aEntry);

    ASSERT(aIndex >= KFatFirstSearchCluster &&  aIndex < (FatSize() >> KFat16EntrySzLog2));

    SetDirty(ETrue);

    //-- calculate page index in the array. Theoretically, aIndex can't be wrong because it is checked by the caller;
    //-- but in some strange situations (malformed volume ?) aIndex might get bigger than number of usable clusters.
    const TUint pgIdx = aIndex >> (PageSizeLog2()-KFat16EntrySzLog2);
    
    if(pgIdx >= (TUint)iPages.Count())
        {
        __PRINT2(_L("#-CFat16FixedCache::WriteEntryL() FAT idx:%d, val:%d #1"), aIndex, aEntry);
        ASSERT(0);
        User::Leave(KErrCorrupt);
        }

    CFat16FixedCachePage *pPage = iPages[pgIdx];

    if(!pPage)
        {//-- page at this position isn't allocated yet
        pPage = CFat16FixedCachePage::NewL(*this);
        iPages[pgIdx] = pPage;
        
        //-- read the page from media
        pPage->ReadFromMediaL(aIndex);
        }

    //-- overwrite entry in cache
    TBool bRes = pPage->WriteCachedEntryL(aIndex, aEntry);
    ASSERT(bRes);
    (void)bRes;
    }

/**
    A debug method that asserts that the cache is really clean
*/
void CFat16FixedCache::AssertCacheReallyClean() const
    {
#ifdef _DEBUG 
        for(TUint i=0; i<NumPages(); ++i)
        {
            CFat16FixedCachePage* pPage = iPages[i];
            if(pPage && pPage->IsDirty())
                {
                __PRINT(_L("#-CFat16FixedCache::AssertCacheReallyClean()"));
                ASSERT(0);
                }
        }
#endif   
    }


//-----------------------------------------------------------------------------
/**
    Flushes all dirty data to the media.
*/
void CFat16FixedCache::FlushL()
    {
    if(!IsDirty())
        {
        AssertCacheReallyClean();
        return;
        }


    //-- flush dirty data to all copies of FAT
    for(iCurrentFatNo=0; iCurrentFatNo < NumFATs(); ++iCurrentFatNo)
        {
        const TInt nPages = NumPages();
        for(TInt i=0; i<nPages; ++i)
            {
            const TBool keepDirty = iCurrentFatNo < (NumFATs() - 1);

            CFat16FixedCachePage* pPage = iPages[i];
            if(pPage)
                pPage->FlushL(keepDirty);
            }
       
        }
   
    iCurrentFatNo = KInvalidFatNo;
    SetDirty(EFalse);
    }

//-----------------------------------------------------------------------------
/**
    Invalidate whole cache. All pages will be marked as invalid and will be re-read from the media on first access to them.
    @return always KErrNone
*/
TInt CFat16FixedCache::Invalidate()
    {
    __PRINT(_L("#-CFat16FixedCache::Invalidate()"));
    const TBool bIgnoreDirtyData = CheckInvalidatingDirtyCache();

    //-- iterate through the array of pages marking invalidating every page
    TInt cnt = iPages.Count();
    while(cnt--)
        {//-- delete pages
        CFat16FixedCachePage *pPage = iPages[cnt];
        if(pPage)
            pPage->Invalidate(bIgnoreDirtyData);
        }


    SetDirty(EFalse);

    return KErrNone;
    }

//-----------------------------------------------------------------------------
/**
    Invalidate FAT cache pages that contain FAT entries from aStartIndex to (aStartIndex+aNumEntries)
    These pages will be marked as invalid and will be re-read from the media on first access to them.
    
    @param  aStartIndex FAT start index of the region being invalidated
    @param  aNumEntries number of entries to invalidate
    @return always KErrNone
*/
TInt CFat16FixedCache::InvalidateRegion(TUint32 aStartIndex, TUint32 aNumEntries)
    {
    __PRINT2(_L("#-CFat16FixedCache::InvalidateRegion() startIndex:%d, entries:%d"),aStartIndex, aNumEntries);
    ASSERT(aStartIndex >= KFatFirstSearchCluster &&  aStartIndex < (FatSize() >> KFat16EntrySzLog2));

    if(!aNumEntries)
        {
        ASSERT(0);
        return KErrNone;
        }

    const TBool bIgnoreDirtyData = CheckInvalidatingDirtyCache();
    const TUint startPgIdx  = aStartIndex >> (PageSizeLog2()-KFat16EntrySzLog2);
    const TUint nPagesToInv = 1+(aNumEntries >> (PageSizeLog2()-KFat16EntrySzLog2));

    TUint i;
    //-- invalidate pages that contain [aStartIndex ... aStartIndex+aNumEntries] entries
    for(i=0; i<nPagesToInv; ++i)
        {
        const TUint pageIdx = i+startPgIdx;
        if(pageIdx >= NumPages())
            break;
        
        CFat16FixedCachePage* pPage = iPages[pageIdx];
        if(pPage)
            pPage->Invalidate(bIgnoreDirtyData);
        } 

    SetDirty(EFalse);
   
    //-- check if the cache still has dirty pages
    for(i=0; i<NumPages(); ++i)
        {
        CFat16FixedCachePage* pPage = iPages[i];
        if(pPage && pPage->IsDirty()) 
            {
            SetDirty(ETrue);
            break;
            }      
        }

    return KErrNone;
    }

//#################################################################################################################################
//  CFat16FixedCachePage implementation
//  Page for the FAT16 fixed cache
//#################################################################################################################################

//-----------------------------------------------------------------------------

CFat16FixedCachePage::CFat16FixedCachePage(CFatPagedCacheBase& aCache)
                     :CFatCachePageBase(aCache)
    {
    ASSERT(IsPowerOf2(EntriesInPage()));
    }


/**
    Factory function.
    @param aCache reference to the owning cache.
    @return pointer to the constructed object or NULL on error
*/
CFat16FixedCachePage* CFat16FixedCachePage::NewL(CFatPagedCacheBase& aCache)
    {
    CFat16FixedCachePage* pSelf = NULL;
    pSelf = new (ELeave) CFat16FixedCachePage(aCache);

    CleanupStack::PushL(pSelf);
    
    pSelf->iData.CreateMaxL(aCache.PageSize()); //-- allocate memory for the page
   
    CleanupStack::Pop();

    return pSelf;
    }


//-----------------------------------------------------------------------------
/**
    Read FAT16 entry from the cache. 
    
    1. If page's data are valid, just extracts data from the page buffer.
    2. If page's data are invalid firstly reads data from the media and goto 1
    
    @param  aFatIndex entry's absolute FAT index (from the FAT start)
    @param  aResult on sucess there will be FAT16 entry value
    @return ETrue, because FAT16 cache pages never get eviched.
*/
TBool CFat16FixedCachePage::ReadCachedEntryL (TUint32 aFatIndex, TUint32& aResult)
    {
    if(IsValid())
        {//-- read entry directly from page buffer, the cached data are valid
        aResult = (*GetEntryPtr(aFatIndex)) & KFat16EntryMask;
        }
    else
        {//-- aFatIndex belongs to this page, but the page is invalid and needs to be read from the media
        //__PRINT(_L("#-CFat16FixedCachePage::ReadCachedEntry() The page is invalid, reading from the media"));
        aResult = ReadFromMediaL(aFatIndex);
        }

    return ETrue;
    }

//-----------------------------------------------------------------------------

/**
    Writes FAT cache page sector to the media (to all copies of the FAT)
    @param  aSector sector number winthin this page
*/
void CFat16FixedCachePage::DoWriteSectorL(TUint32 aSector)
    {
    //__PRINT1(_L("#-CFat16FixedCachePage::DoWriteSectorL() startSec:%d, cnt:%d"), aSector);

    ASSERT(aSector < iCache.SectorsInPage());

    TInt offset = 0;

    if(iStartIndexInFAT == 0 && aSector == 0)
        {//-- this is the very beginning of FAT16. We must skip FAT[0] & FAT[1] entries and do not write them to media.    
        offset = KFatFirstSearchCluster << KFat16EntrySzLog2; 
        }    
    
    const TUint8* pData = iData.Ptr()+offset+(aSector << iCache.SectorSizeLog2());
    
    TUint32 dataLen = (1 << iCache.SectorSizeLog2()) - offset;

    const TUint32 mediaPosStart = iCache.FatStartPos() + (iStartIndexInFAT << KFat16EntrySzLog2) + (aSector << iCache.SectorSizeLog2()) + offset; 
    const TUint32 mediaPosEnd = mediaPosStart + dataLen; 

    //-- check if we are going to write beyond FAT. It can happen if the write granularity is bigger that the sector size.
    const TUint32 posFatEnd = iCache.FatStartPos() + iCache.FatSize();
    if(mediaPosEnd > posFatEnd)
        {//-- correct the leength of the data to write.
        dataLen -= (mediaPosEnd-posFatEnd);
        }

    TPtrC8 ptrData(pData, dataLen); //-- source data descriptor 

    TInt nRes = iCache.WriteFatData(mediaPosStart, ptrData);
    
    if(nRes != KErrNone)
        {
        __PRINT1(_L("#-CFat16FixedCachePage::DoWriteSectorsL() failed! code:%d"), nRes);
        User::Leave(nRes);
        }

    }

//-----------------------------------------------------------------------------
/**
    Write FAT16 entry at aFatIndex to the cache. Note that the data are not written to the media, only to the cache page.
    Corresponding page sector is marked as dirty and will be flushed on FlushL() call later.

    1. If page's data are valid, copies data to the page buffer and marks sector as dirty.
    2. If page's data are invalid, firstly reads data from the media and goto 1

    @param  aFatIndex entry's absolute FAT index (from the FAT start)
    @param  aFatEntry FAT16 entry value
    @return ETrue because FAT16 cache pages never get eviched.
*/
TBool CFat16FixedCachePage::WriteCachedEntryL(TUint32 aFatIndex, TUint32 aFatEntry)
    {
    
    ASSERT(IsEntryCached(aFatIndex));

    if(!IsValid())
        {//-- we are trying to write data to the page that has invalid data. //-- read the data from the media first.
        ReadFromMediaL(aFatIndex);
        }

    TFat16Entry* pEntry = GetEntryPtr(aFatIndex);
    
    const TFat16Entry orgEntry = *pEntry;
    *pEntry = (TFat16Entry)((orgEntry & ~KFat16EntryMask) | (aFatEntry & KFat16EntryMask));
    
    //-- mark corresponding sector of the cache page as dirty
    const TUint entryIndexInPage = aFatIndex & (EntriesInPage()-1); //-- number of entries in page is always a power of 2
    const TUint dirtySectorNum   = entryIndexInPage >> (iCache.SectorSizeLog2() - KFat16EntrySzLog2);

    ASSERT(dirtySectorNum < iCache.SectorsInPage());

    iDirtySectors.SetBit(dirtySectorNum);
    SetState(EDirty); //-- mark page as dirty.

    return ETrue;
    }

//-----------------------------------------------------------------------------

/**
    Get a pointer to the FAT16 entry in the page buffer.
    The page 's data shall be valid and the entry shall belong to this page.
    
    @param aFatIndex absolute FAT index (from the FAT start) of the entry
    @return pointer to the FAT16 entry in the page buffer.
*/
TFat16Entry* CFat16FixedCachePage::GetEntryPtr(TUint32 aFatIndex) const
    {
    ASSERT(IsValid() && IsEntryCached(aFatIndex));

    const TUint KEntryIndexInPage = aFatIndex & (EntriesInPage()-1); //-- number of entries in page is always a power of 2
    TFat16Entry* pEntry = ((TFat16Entry*)iData.Ptr()) + KEntryIndexInPage;

    return  pEntry;
    }

//-----------------------------------------------------------------------------
/**
    Read the FAT16 cache page from the media and returns required FAT16 entry.    

    @param  aFatIndex entry's absolute FAT index (from the FAT start)
    @return entry value at aFatIndex.
*/
TUint32 CFat16FixedCachePage::ReadFromMediaL(TUint32 aFatIndex)
    {
    //__PRINT1(_L("#-CFat16FixedCachePage::ReadFromMediaL() FAT idx:%d"), aFatIndex);
    const TUint KFat16EntriesInPageLog2 = iCache.PageSizeLog2()-KFat16EntrySzLog2; //-- number of FAT16 entries in page is always a power of 2

    //-- find out index in FAT this page starts from
    iStartIndexInFAT = (aFatIndex >> KFat16EntriesInPageLog2) << KFat16EntriesInPageLog2;
    SetState(EInvalid); //-- mark the page as invalid just in case if the read fails.

    //-- read page from the media
    const TUint32 pageStartPos = iCache.FatStartPos() + (iStartIndexInFAT << KFat16EntrySzLog2);
    
    TInt nRes = iCache.ReadFatData(pageStartPos, iCache.PageSize(), iData);
    if(nRes != KErrNone)
        {
        __PRINT1(_L("#-CFat16FixedCachePage::ReadFromMediaL() failed! code:%d"), nRes);
        User::Leave(nRes);
        }

    SetClean(); //-- mark this page as clean

    const TFat16Entry entry = (TFat16Entry)((*GetEntryPtr(aFatIndex)) & KFat16EntryMask);

    return entry;
    }


//-----------------------------------------------------------------------------

//#################################################################################################################################
//  CFat12Cache implementation
//  FAT12 non-paged fixed cache. This cache consists from only 1 page, logically divided up to 32 sectors (write granularity unit)
//#################################################################################################################################

CFat12Cache::CFat12Cache()
            :CFatCacheBase()
    {
    }

//-----------------------------------------------------------------------------
/**
    FAT12 fixed cache factory function.
    @param  aOwner              pointer to the owning FAT mount
    @param  aFatSize            size of the FAT table in bytes

    @return pointer to the constructed object.
*/
CFat12Cache* CFat12Cache::NewL(CFatMountCB* aOwner, TUint32 aFatSize)
    {
    __PRINT(_L("#-CFat12Cache::NewL()"));
    CFat12Cache* pSelf = NULL;
    pSelf = new (ELeave) CFat12Cache;

    CleanupStack::PushL(pSelf);
    pSelf->InitialiseL(aOwner, aFatSize);
    CleanupStack::Pop();
    
    return pSelf;
    }

//-----------------------------------------------------------------------------
/**
    FAT16 fixed cache initialisation.
    @param  aOwner              pointer to the owning FAT mount
    @param  aFatSize            size of the FAT table in bytes
*/
void CFat12Cache::InitialiseL(CFatMountCB* aOwner, TUint32 aFatSize)
    {
    __PRINT1(_L("#-CFat12Cache::InitialiseL FatSz:%u"),aFatSize);

    CFatCacheBase::InitialiseL(aOwner);
    ASSERT(FatType() == EFat12);

    //-- see FAT specs; 4084 is a max. number of clusters, fat12 entry is 1.5 bytes; but we need to round up FAT12 size to the sector size
    const TUint32 KMaxFat12Size = ( ((TUint32)(4084*1.5+FAT_SectorSz()-1)) >> FAT_SectorSzLog2()) << FAT_SectorSzLog2();
    const TUint32 KMinFat12Size = FAT_SectorSz();  //-- 1 FAT sector
	__ASSERT_ALWAYS(aFatSize >= KMinFat12Size && aFatSize <= KMaxFat12Size, User::Leave(KErrCorrupt));
    (void)KMaxFat12Size;
    (void)KMinFat12Size;

    //-- as soon as FAT12 max size is 4084 entries or 6126 bytes, the cache is contiguous and divided 
    //-- to logical sectors (write granularity). 

    //-- calculate number write cache sector in the cache
    iSectorsInCache = (aFatSize + (FAT_SectorSz()-1)) >> FAT_SectorSzLog2();
    __ASSERT_ALWAYS(NumSectors() <= KMaxSectorsInCache, Fault(EFatCache_BadGranularity));

    //-- round up cache size to write granularity (sector size)
    const TUint32 cacheSize = NumSectors() << FAT_SectorSzLog2();
    
    //-- create buffer for the whole FAT12
    iData.CreateMaxL(cacheSize);

    //-- this will read whole FAT into the cache 
    User::LeaveIfError(Invalidate()); 
    }

//-----------------------------------------------------------------------------
/**
    Close the cache and deallocate its memory.
    @param  aDiscardDirtyData if ETrue, will ignore dirty data. If EFalse, will panic on atempt to close dirty cache.  
*/
void CFat12Cache::Close(TBool aDiscardDirtyData)
    {
    __PRINT1(_L("#-CFat12Cache::Close(%d)"), aDiscardDirtyData);    
    
    for(TUint32 i=0; i<NumSectors(); ++i)
        {
        if(iDirtySectors[i])
            {//-- trying to destroy the cache that has dirty sectors
            __PRINT1(_L("#-CFat12Cache::Close() The cache is dirty! cache sector:%d"), i);
            if(!aDiscardDirtyData)
                {
                __ASSERT_ALWAYS(0, Fault(EFatCache_DiscardingDirtyData));
                }
            //-- ignore this fact if requested.
            }
        }

    iData.Close();
    SetDirty(EFalse);
    }

//-----------------------------------------------------------------------------
/**
    Read FAT entry from the cache. 

    @param  aIndex FAT entry index to read
    @return FAT entry value at the index "aIndex" 
*/
TUint32 CFat12Cache::ReadEntryL(TUint32 aIndex)
    {
    //__PRINT1(_L("#-CFat12Cache::ReadEntryL() FAT idx:%d"), aIndex);
    ASSERT(aIndex >= KFatFirstSearchCluster &&  aIndex <  (FatSize() + FatSize()/2)); //-- FAT12 entry is 1.5 bytes long

    TUint32 entry;

    if(aIndex & 0x01)
        {//-- odd index
        --aIndex;
        const TUint32 byteIdx = 1 + aIndex + (aIndex >> 1); //-- byteIdx = 1+(aIndex-1)*1.5
        Mem::Copy(&entry, iData.Ptr()+byteIdx, 2); 
        entry >>= 4;   
        }
    else
        {//-- even index
        const TUint32 byteIdx = aIndex + (aIndex >> 1); //-- byteIdx = aIndex*1.5
        Mem::Copy(&entry, iData.Ptr()+byteIdx, 2);
        }

    entry &= KFat12EntryMask; 

    return entry;
    }

//-----------------------------------------------------------------------------
/**
    Write FAT entry to the cache. 
    Appropriate FAT cache sector will be marked as "dirty" and will be eventually flushed to the media.

    @param  aIndex FAT entry index
    @param  aEntry FAT entry value
*/
void CFat12Cache::WriteEntryL(TUint32 aIndex, TUint32 aEntry)
    {
    //__PRINT2(_L("#-CFat12Cache::WriteEntryL() FAT idx:%d, entry:%u"), aIndex, aEntry);
    ASSERT(aIndex >= KFatFirstSearchCluster &&  aIndex <  (FatSize() + FatSize()/2)); //-- FAT12 entry is 1.5 bytes long

    aEntry &= KFat12EntryMask; 
 
    TUint32 byteIdx = 0;
    TUint8 tmp;

    if(aIndex & 0x01)
        {//-- odd index
        --aIndex;
        byteIdx = 1 + aIndex + (aIndex >> 1); //-- byteIdx = 1+(aIndex-1)*1.5
        tmp = (TUint8)(iData[byteIdx] & 0x0F); //-- we modifying a higher nibble 
        tmp |= (TUint8) ((aEntry & 0x0F)<<4);
        iData[byteIdx] = tmp;

        iData[byteIdx+1] = (TUint8)(aEntry >> 4);  
        }
    else
        {//-- even index
        byteIdx = aIndex + (aIndex >> 1); //-- byteIdx = aIndex*1.5
        iData[byteIdx] = (TUint8)aEntry;  

        const TUint32 nextIdx = byteIdx+1;
        tmp = (TUint8)(iData[nextIdx] & 0xF0); //-- we modifying a lower nibble 
        tmp |= (TUint8)((aEntry >> 8) & 0x0F);
        iData[nextIdx] = tmp;

        }

    //-- mark changed sectors dirty. We modified 2 bytes at [byteIdx] and [byteIdx+1]
    iDirtySectors.SetBit(byteIdx >> FAT_SectorSzLog2());
    iDirtySectors.SetBit((byteIdx+1) >> FAT_SectorSzLog2());

    SetDirty(ETrue);
    }

//-----------------------------------------------------------------------------
/**
    A debug method that asserts that the cache is really clean
*/
void CFat12Cache::AssertCacheReallyClean() const
    {
#ifdef _DEBUG 
    if(iDirtySectors.HasBitsSet())
        {
        __PRINT(_L("#-CFat12Cache::AssertCacheReallyClean()"));
        ASSERT(0);
        }

#endif   
    }

//-----------------------------------------------------------------------------
/**
    Flushes all dirty data to the media.
    Walks through all sectors in this cache and flushes dirty ones.
*/
void CFat12Cache::FlushL()
    {
    if(!IsDirty())
        {
        AssertCacheReallyClean();
        return;
        }

    //-- write all dirty sectors to the media (into all copies of FAT)
    for(iCurrentFatNo=0; iCurrentFatNo < NumFATs(); ++iCurrentFatNo)
        {
        for(TUint secNo=0; secNo<NumSectors(); ++secNo)
            {
            if(iDirtySectors[secNo])
                {//-- this sector is dirty, write it to the media

                TInt offset = 0;
                if(secNo == 0)
                    {//-- this is a first sector in FAT. We must skip FAT[0] & FAT[1] entries and do not write them to the media.    
                    offset = 3; //-- 2 FAT12 entries
                    }

                const TUint32 secPos = secNo << FAT_SectorSzLog2(); //-- relative sector position in FAT
                const TUint8* pData = iData.Ptr()+offset+secPos;    //-- pointer to the data in cache buffer
                const TUint32 len = FAT_SectorSz() - offset;          
                TPtrC8 ptrData(pData, len);                         //-- source data descriptor 
                const TUint32 mediaPos = FatStartPos() + secPos + offset;
                
                TInt nRes = WriteFatData(mediaPos, ptrData);

                if(nRes != KErrNone)
                    {
                    __PRINT1(_L("#-CFat12Cache::FlushL() failed! code:%d"), nRes);
                    User::Leave(nRes);
                    }

                }//if(iDirtySectors[secNo])
            }

        }

    iCurrentFatNo = KInvalidFatNo;

    //-- mark the cache as clean
    iDirtySectors.Clear();
    SetDirty(EFalse);
    
    }

//-----------------------------------------------------------------------------
/**
    Invalidates whole cache. Because FAT12 is tiny, just re-reads data from the media to the cache
    @return Media read result code.
*/
TInt CFat12Cache::Invalidate()
    {
    __PRINT(_L("#-CFat12Cache::Invalidate()"));
    CheckInvalidatingDirtyCache();
    
    //-- read whole cache from the media
    const TUint32 posStart = FatStartPos();
    const TUint32 len      = NumSectors() << FAT_SectorSzLog2();
     
    TInt nRes = ReadFatData(posStart, len, iData);
    if(nRes != KErrNone)
        return nRes;

    //-- mark the cache as clean
    SetDirty(EFalse);
    iDirtySectors.Clear();

    return KErrNone;
    }

//-----------------------------------------------------------------------------
/**
    Invalidate wholes cache. Because FAT12 is tiny, just re-reads data from the media to the cache
    @param  aStartIndex ignored
    @param  aNumEntries ignored
    @return Media read result code.
*/
TInt CFat12Cache::InvalidateRegion(TUint32 aStartIndex, TUint32 aNumEntries)
    {
    __PRINT2(_L("#-CFat12Cache::InvalidateRegion() startIndex:%d, entries:%d"),aStartIndex, aNumEntries);
    ASSERT(aStartIndex >= KFatFirstSearchCluster &&  aStartIndex <  (FatSize() + FatSize()/2)); //-- FAT12 entry is 1.5 bytes long
    (void)aStartIndex;
    (void)aNumEntries;

    //-- just re-read all FAT12, it is just 6K max and isn't worth calculating invalid sectors
    return Invalidate();
    }











































