kerneltest/e32test/lffs/tf_write.cpp
changeset 0 a41df078684a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kerneltest/e32test/lffs/tf_write.cpp	Mon Oct 19 15:55:17 2009 +0100
@@ -0,0 +1,1185 @@
+// Copyright (c) 2001-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:
+//
+
+#include <e32std.h>
+#include <e32std_private.h>
+#include <e32svr.h>
+#include <e32test.h>
+#include "randgen.h"
+#include "user_config.h"
+#include "tf_write.h"
+
+_LIT( KTestName, "TF_WRITE" );
+RTest test( KTestName );
+
+
+const TInt64 KRandomSeed1(MAKE_TINT64(0x3e000111,0xAFCBDF0F));
+
+
+GLDEF_C void Panic( TPanicNo aPanic )
+	{
+	User::Panic( KTestName, aPanic );
+	}
+
+
+// **********************************************************************
+// Implementation of the writer classes
+
+TWriteBase::TWriteBase( CWriteTest& aOwner )
+	: iOwner( aOwner )
+	{
+	}
+
+void TWriteBase::CheckedWrite(TInt aPos,const TDesC8& aSrc)
+	{
+	Write( aPos, aSrc );
+	test( iOwner.CompareAgainstFlash( aPos, aSrc ) );
+	}
+
+
+TSimpleWrite::TSimpleWrite( CWriteTest& aOwner )
+	: TWriteBase( aOwner ), iDrive( aOwner.Drive() )
+	{
+	}
+
+void TSimpleWrite::Write(TInt aPos,const TDesC8& aSrc)
+	{
+	TInt64	pos( aPos );
+//	test( KErrNone == iDrive.Write( pos, aSrc ) );
+	TInt rv = iDrive.Write( pos, aSrc );
+	if( KErrNone != rv )
+		{
+		test.Printf( _L("TBusLocalDrive::Write returned %d"), rv );
+		test( EFalse );
+		}
+	}
+
+
+	
+TThreadWrite::TThreadWrite( CWriteTest& aOwner )
+	: TWriteBase( aOwner ), iDrive( aOwner.Drive() ),
+	iThreadHandle( aOwner.DummyThreadHandle() )
+	{
+	}
+
+void TThreadWrite::Write(TInt aPos,const TDesC8& aSrc)
+	{
+	TInt64	pos( aPos );
+#if 0
+	test( KErrNone == iDrive.Write( pos, aSrc.Length(), &aSrc, iThreadHandle, 0 ) );
+#else
+	test( KErrNone == iDrive.Write( pos, aSrc.Length(), &aSrc, KLocalMessageHandle, 0 ) );
+#endif
+	}
+
+		
+void TThreadWrite::CheckedThreadWrite(TInt aPos, TInt aLength, const TDesC8& aSrc, TInt aDescOffset )
+	{
+	TInt64	pos( aPos );
+#if 0
+	test( KErrNone == iDrive.Write( pos, aLength, &aSrc, iThreadHandle, aDescOffset ) );
+#else
+	test( KErrNone == iDrive.Write( pos, aLength, &aSrc, KLocalMessageHandle, aDescOffset ) );
+#endif
+	test( iOwner.CompareAgainstFlash( aPos, aLength, aSrc, aDescOffset ) );
+	}
+
+void TThreadWrite::CurrentThreadCheckedThreadWrite(TInt aPos, TInt aLength, const TDesC8& aSrc, TInt aDescOffset )
+	{
+	TInt64	pos( aPos );
+#if 0
+	test( KErrNone == iDrive.Write( pos, aLength, &aSrc, RThread().Handle(), aDescOffset ) );
+#else
+	test( KErrNone == iDrive.Write( pos, aLength, &aSrc, KLocalMessageHandle, aDescOffset ) );
+#endif
+	test( iOwner.CompareAgainstFlash( aPos, aLength, aSrc, aDescOffset ) );
+	}
+
+// **********************************************************************
+// Implementation of CBlockManager
+
+CBlockManager::CBlockManager( TBusLocalDrive& aDrive, CWriteTest& aOwner )
+	: iDrive( aDrive ), iOwner( aOwner )
+	{
+	}
+
+CBlockManager::~CBlockManager()
+	{
+	delete[] iEraseArray;
+	}
+
+void CBlockManager::CreateL()
+	{
+	//
+	// Get size of Flash drive
+	//
+	test.Printf( _L("Reading block info...") );
+	TLocalDriveCapsV2Buf info;
+    iDrive.Caps(info);
+	TUint flashSize = I64LOW(info().iSize);
+	test( 0 == I64HIGH(info().iSize));
+	iBlockSize = info().iEraseBlockSize;
+	test( 0 == (iBlockSize & 3) );
+	iBlockCount = flashSize / iBlockSize;
+	test( 0 != iBlockCount );
+
+	test.Printf( _L("Flash block size=0x%x; block count=%d\n"), iBlockSize, iBlockCount );
+	
+	iEraseArray = new(ELeave) TEraseStatus[iBlockCount];
+	test.Printf( _L("Erase status array created") );
+	}
+
+
+void CBlockManager::EraseBlock( TInt aBlockNumber )
+	{
+	__ASSERT_ALWAYS( aBlockNumber < iBlockCount, Panic( EPanicEraseBlockOOR ) );
+	__ASSERT_ALWAYS( aBlockNumber < iBlockCount, Panic( EPanicEraseBlockNeg ) );
+	_LIT( KEraseMsg, "Erasing block %d" );
+	test.Printf( KEraseMsg, aBlockNumber );
+	test( KErrNone == iDrive.Format( BlockAddress( aBlockNumber ), iBlockSize ) );
+	VerifyErased( aBlockNumber );
+	iEraseArray[ aBlockNumber ] = EErased;
+	}
+
+void CBlockManager::EraseAllBlocks()
+	{
+	_LIT( KEraseMsg, "Erasing all blocks" );
+	test.Printf( KEraseMsg );
+	for( TInt i = 0; i < iBlockCount; i++ )
+		{
+		EraseBlock( i );
+		}
+	}
+
+void CBlockManager::VerifyErased( TInt aBlockNumber )
+	{
+	TUint offset = aBlockNumber * iBlockSize;
+	
+	TBool failed = EFalse;
+	const TInt readBufLen = iReadBuffer.MaxLength();
+
+	for( TInt remaining = iBlockSize; remaining > 0 && !failed ;)
+		{
+		TInt r = iDrive.Read( offset, readBufLen, iReadBuffer );
+		if( r != KErrNone )
+			{
+			test.Printf( _L("... FAIL: read failed (%d) at offset 0x%x\n"), r, offset );
+			test( KErrNone == r );
+			}
+		test( iReadBuffer.Length() == readBufLen );
+
+		const TUint32* p = (const TUint32*)iReadBuffer.Ptr();
+		for( TInt i = 0; i < readBufLen; i += 4 )
+			{
+			if( 0xFFFFFFFF != *p )
+				{
+				failed = ETrue;
+				test.Printf( _L("... FAILED: byte @ offs=0x%x, read=0x%x, expected=0xFF\n"), 
+								offset+i, p[0] );
+				test(EFalse);
+				}
+			++p;
+			}
+		offset += readBufLen;
+		remaining -= readBufLen;
+		}
+	}
+
+
+void CBlockManager::InitialiseSequentialBlockAllocator()
+	//
+	// Clears the erase status and resets to block zero
+	//
+	{
+	for( TInt i = 0; i < iBlockCount; i++ )
+		{
+		iEraseArray[i] = ENotErased;
+		}
+	iNextBlock = 0;
+	}
+
+
+TInt CBlockManager::NextErasedBlock()
+	{
+	if( iNextBlock >= iBlockCount )
+		{
+		iNextBlock = 0;
+		}
+
+	if( ENotErased == iEraseArray[iNextBlock] )
+		{
+		EraseBlock( iNextBlock );
+		}
+	iEraseArray[iNextBlock] = ENotErased;	// assume it is going to be used
+	return iNextBlock;
+	}
+
+void CBlockManager::InitialiseDataChunkAllocator()
+	{
+	iDataBlock = NextErasedBlock();
+	iDataOffset = 0;
+	}
+
+
+TUint CBlockManager::NextErasedDataChunk( TInt aRequiredLength, TInt aMultiple )
+	//
+	// Request a chunk of erased flash of size aRequiredLength bytes on a
+	// boundary of aMultiple bytes. E,g, to allocate a buffer on 12 bytes length
+	// on a 32-byte boundary, aRequiredLength = 12, aMultiple=12
+	//
+	// The byte count is rounded up to a multiple of 4 bytes
+	//
+	{
+	aRequiredLength = (aRequiredLength + 3) & ~0x3;
+	
+	TUint chunkBase = ((iDataOffset + aMultiple - 1) / aMultiple) * aMultiple;
+	if( chunkBase > (TUint)iBlockSize || chunkBase + aRequiredLength > (TUint)iBlockSize )
+		{
+		iDataBlock = NextErasedBlock();
+		chunkBase = 0;
+		}
+	
+	iDataOffset = ( chunkBase + aRequiredLength + 3) & ~0x3;
+
+	return BlockAddress( iDataBlock ) + chunkBase;
+	}
+
+
+
+inline TInt CBlockManager::BlockCount() const
+	{
+	return iBlockCount;
+	}
+
+inline TInt CBlockManager::BlockSize() const
+	{
+	return iBlockSize;
+	}
+
+inline TInt CBlockManager::FlashSize() const
+	{
+	return iBlockSize * iBlockCount;
+	}
+
+inline TUint CBlockManager::BlockAddress( TInt aBlockNumber ) const
+	{
+	return (TUint)aBlockNumber * (TUint)iBlockSize;
+	}
+
+
+// **********************************************************************
+// Implementation of CWriteTest
+
+CWriteTest::~CWriteTest()
+	{
+	if( iDriveOpened )
+		{
+		iDrive.Disconnect();
+		}
+
+	delete iBlocks;
+	delete iSimpleWriter;
+	delete iThreadWriter;
+	}
+
+
+void CWriteTest::CreateL()
+	{
+	//
+	// Load the device drivers
+	//
+	TInt r;
+#ifndef SKIP_PDD_LOAD
+	test.Printf( _L("Loading %S\n"), &KLfsDriverName );
+	r = User::LoadPhysicalDevice( KLfsDriverName );
+	test( KErrNone == r || KErrAlreadyExists == r );
+#endif
+
+#ifdef UNMOUNT_DRIVE
+	RFs fs;
+	test( KErrNone == fs.Connect() );
+#if 0
+	// XXX - not EKA2
+	test( KErrNone == fs.SetDefaultPath( _L("Z:\\") ) );
+#endif
+	TFullName name;
+	fs.FileSystemName( name, KLffsLogicalDriveNumber );
+	if( name.Length() > 0 )
+		{
+		test.Printf( _L("Unmounting drive") );
+		test( KErrNone == fs.DismountFileSystem( _L("Lffs"), KLffsLogicalDriveNumber) );
+		User::After( 2000000 );
+		test.Printf( _L("Drive unmounted") );
+		}
+	fs.Close();
+#endif
+
+	//
+	// Open a TBusLogicalDevice to it
+	//
+	test.Printf( _L("Opening media channel\n") );
+	TBool changedFlag = EFalse;
+	r = iDrive.Connect( KDriveNumber, changedFlag );
+	User::LeaveIfError( r );
+	iDriveOpened = ETrue;
+
+	//
+	// Initialise the block manager
+	//
+	iBlocks = new(ELeave) CBlockManager( iDrive, *this );
+	iBlocks->CreateL();
+
+	//
+	// Create a dummy thread that we can use to force
+	// other-thread write operations
+	//
+#if 0
+	test( KErrNone == iDummyThread.Create( _L("DUMMY"), DummyThread, 256, KMinHeapSize, KMinHeapSize, NULL ) );
+#else
+	test( KErrNone == iDummyThread.Create( _L("DUMMY"), DummyThread, KDefaultStackSize, KMinHeapSize, KMinHeapSize, NULL ) );
+#endif
+	test.Printf( _L("Main thread handle=%d; dummy thread handle=%d"),
+		RThread().Handle(), DummyThreadHandle() );
+
+	//
+	// Create the writer classes
+	//
+	iSimpleWriter = new(ELeave) TSimpleWrite( *this );
+	iThreadWriter = new(ELeave) TThreadWrite( *this );
+	
+	//
+	// Seed the pseudo-random number generator
+	//
+	iRandom.SetSeed( KRandomSeed1 );
+
+
+	test.Printf( _L("CWriteTest::CreateL complete\n") );
+	}
+
+
+TInt CWriteTest::DummyThread( TAny* /* aParam */ )
+	//
+	// Thread does nothing at all
+	//
+	{
+	for(;;)
+		{
+		User::WaitForAnyRequest();	// just block
+		}
+	}
+
+void CWriteTest::CreateRandomData( TDes8& aDestBuf, TInt aLength )
+	//
+	// Fills supplied descriptor with aLength bytes of pseudo-random test data
+	//
+	{
+	aDestBuf.SetLength( aLength );
+	TUint32* p = (TUint32*)aDestBuf.Ptr();
+	for( TInt j = aLength/4; j > 0 ; j-- )
+		{
+		*p++ = iRandom.Next();
+		}
+	
+	if( aLength & 0x3 )
+		{
+		TUint8* q = (TUint8*)p;
+		for( TInt k = aLength & 3; k > 0; k-- )
+			{
+			*q++ = (TUint8)iRandom.Next();
+			}
+		}
+	}
+
+
+TBool CWriteTest::CheckOnes( TUint aFlashOffset, TInt aLength )
+	//
+	// Checks that aLength bytes of data from offset aFlashOffset
+	// all contain 0xFF
+	//
+	{
+	TUint offset = aFlashOffset;
+	
+	TBool failed = EFalse;
+	const TInt readBufLen = iReadBuffer.MaxLength();
+
+	for( TInt remaining = aLength; remaining > 0 && !failed ;)
+		{
+		TInt readLen = Min( remaining, readBufLen );
+		TInt r = iDrive.Read( offset, readLen, iReadBuffer );
+		if( r != KErrNone )
+			{
+			test.Printf( _L("... FAIL: read failed (%d) at offset 0x%x\n"), r, offset );
+			test( KErrNone == r );
+			}
+		test( iReadBuffer.Length() == readLen );
+
+		const TUint8* p = iReadBuffer.Ptr();
+		for( TInt i = 0; i < readLen; ++i )
+			{
+			if( 0xFF != *p )
+				{
+				failed = ETrue;
+				test.Printf( _L("... FAILED: byte @ offs=0x%x, read=0x%x, expected=0xFF\n"), 
+								offset+i, p[0] );
+				break;
+				}
+			++p;
+			}
+		offset += readLen;
+		remaining -= readLen;
+		}
+	
+	return !failed;
+	}
+
+
+TBool CWriteTest::CompareAgainstFlash( TInt aFlashOffset, TInt aLength, const TDesC8& aDes, TInt aDescOffset )
+	//
+	// Checks that the data in aDes matches that in the Flash at position
+	// aFlashOffset.
+	// The test starts at offset aDescOffset in aSampleData. aLength bytes
+	// are tested.
+	//
+	{
+	__ASSERT_ALWAYS( aDescOffset + aLength <= aDes.Length(), Panic( EPanicCompareDescOverflow ) );
+	TInt dataLength = aLength;
+	const TUint8* srcPtr = aDes.Ptr() + aDescOffset;
+
+	TUint offset = aFlashOffset;
+	
+	TBool failed = EFalse;
+	const TInt readBufLen = iReadBuffer.MaxLength();
+
+	while( (dataLength > 0) && !failed )
+		{
+		TInt len = Min( dataLength, readBufLen );
+		TInt r = iDrive.Read( offset, len, iReadBuffer );
+		if( r != KErrNone )
+			{
+			test.Printf( _L("... FAIL: read failed (%d) at offset 0x%x\n"), r, offset );
+			test( KErrNone == r );
+			}
+		test( iReadBuffer.Length() == len );
+
+		if( 0 != Mem::Compare( srcPtr, len, iReadBuffer.Ptr(), len ) )
+			{
+			test.Printf( _L("... FAIL: mismatch around offset 0x%x\n"), offset );
+			failed = ETrue;
+			}
+		offset += len;
+		dataLength -= len;
+		srcPtr += len;
+		}
+	
+	return !failed;
+	}
+
+TBool CWriteTest::CompareAgainstFlash( TInt aFlashOffset, const TDesC8& aDes )
+	//
+	// Checks that the data in aDes matches that in the Flash at position
+	// aFlashOffset.
+	// aDes->Length() bytes are tested.
+	//
+	{
+	return CompareAgainstFlash( aFlashOffset, aDes.Length(), aDes, 0 );
+	}
+
+
+void CWriteTest::SimpleWriteTest()
+	{
+	test.Next( _L("Simple write test, simple write function") );
+	DoSimpleWriteTest( *iSimpleWriter );
+	}
+
+void CWriteTest::SimpleThreadWriteTest()
+	{
+	test.Next( _L("Simple write test, thread write function") );
+	DoSimpleWriteTest( *iThreadWriter );
+	}
+
+
+void CWriteTest::DoSimpleWriteTest( MGeneralizedWrite& aWriter )
+	//
+	// Writes some random test data to the start of a block, checks that
+	// it is written correctly and that the source data isn't modified
+	//
+	{
+	TInt blockNo = iBlocks->NextErasedBlock();
+	TUint blockBase = iBlocks->BlockAddress( blockNo );
+
+	TBuf8<512> randomData;
+	CreateRandomData( randomData, randomData.MaxLength() );
+
+	TBuf8<512> randomDataDuplicate;
+	randomDataDuplicate.Copy( randomData );
+	test( randomDataDuplicate == randomData );
+
+	TBuf8<sizeof(TPtr)> ptrCopy;	// used to take copies of descriptors
+
+	//
+	// Write using a constant descriptor TPtrC
+	//
+	test.Printf( _L("Write using TPtrC") );
+	TPtrC8 ptrC( randomData );
+	ptrCopy.Copy( (TUint8*)&ptrC, sizeof(ptrC) );
+
+	aWriter.CheckedWrite( blockBase + 0, ptrC );
+
+	test.Printf( _L("Check descriptor not modified by write function") );
+	test( 0 == Mem::Compare( (TUint8*)&ptrC, sizeof(ptrC), ptrCopy.Ptr(), sizeof(ptrC) ) );
+
+	test.Printf( _L("Check data not modified by write function") );
+	test( randomDataDuplicate == randomData );
+
+	//
+	// Write using a modifiable descriptor TPtr
+	//
+	test.Printf( _L("Write using TPtr") );
+	TPtr8 ptr( (TUint8*)randomData.Ptr(), randomData.Length(), randomData.Length() );
+	ptrCopy.Copy( (TUint8*)&ptr, sizeof(ptr) );
+	
+	aWriter.CheckedWrite( blockBase + 1024, ptr );
+
+	test.Printf( _L("Check descriptor not modified by write function") );
+	test( 0 == Mem::Compare( (TUint8*)&ptr, sizeof(ptr), ptrCopy.Ptr(), sizeof(ptr) ) );
+
+	test.Printf( _L("Check data not modified by write function") );
+	test( randomDataDuplicate == randomData );
+
+	//
+	// Write using a modifiable descriptor TBuf
+	//
+	test.Printf( _L("Write using TBuf") );
+	
+	aWriter.CheckedWrite( blockBase + 2048, randomData );
+
+	test.Printf( _L("Check descriptor not modified by write function") );
+	test( ptrC.Ptr() == randomData.Ptr() );
+	test( 512 == randomData.Length() );
+	test( 512 == randomData.MaxLength() );
+
+	test.Printf( _L("Check data not modified by write function") );
+	test( randomDataDuplicate == randomData );
+
+	//
+	// Read the data back and check it matches
+	//
+	test.Printf( _L("Reading data back with TBusLocalDrive::Read") );
+	test( KErrNone == iDrive.Read( blockBase + 0, 512, randomDataDuplicate ) );
+	test( randomDataDuplicate == randomData );
+	test( KErrNone == iDrive.Read( blockBase + 1024, 512, randomDataDuplicate ) );
+	test( randomDataDuplicate == randomData );
+	test( KErrNone == iDrive.Read( blockBase + 2048, 512, randomDataDuplicate ) );
+	test( randomDataDuplicate == randomData );
+	}
+
+
+
+void CWriteTest::AlignedWriteTest()
+	{
+	test.Next( _L("Aligned write test, simple write function") );
+	DoAlignedWriteTest( *iSimpleWriter );
+	}
+
+void CWriteTest::AlignedThreadWriteTest()
+	{
+	test.Next( _L("Aligned write test, thread write function") );
+	DoAlignedWriteTest( *iThreadWriter );
+	}
+
+
+void CWriteTest::DoAlignedWriteTest( MGeneralizedWrite& aWriter )
+	//
+	// Writes data of various lengths to word-aligned addresses
+	//
+	{
+	iBlocks->InitialiseDataChunkAllocator();
+
+	TBuf8<512> data;	
+
+	_LIT( KWriteMsg, "  writing %d bytes @0x%x" );
+
+	test.Printf( _L("Testing small writes") );
+
+	for( TInt length = 1; length < 16; length++ )
+		{
+		CreateRandomData( data, length );
+		
+		// get a 32-byte data chunk on a word boundary
+		TUint offset = iBlocks->NextErasedDataChunk( 32, 4 );
+
+		test.Printf( KWriteMsg, length, offset );
+		aWriter.CheckedWrite( offset, data );
+		// check that the section after the data still contains all ones
+		test( CheckOnes( offset + length, 32 - length ) );
+		}
+
+
+	test.Printf( _L("Testing large writes") );
+	for( TInt length = 512-32; length <= 512 ; length++ )
+		{
+		CreateRandomData( data, length );
+		
+		// get a 544-byte data chunk on a word boundary
+		TUint offset = iBlocks->NextErasedDataChunk( 544, 4 );
+
+		test.Printf( KWriteMsg, length, offset );
+		aWriter.CheckedWrite( offset, data );
+
+		// check that the section after the data still contains all ones
+		test( CheckOnes( offset + length, 544 - length ) );
+		}
+	}
+
+
+
+
+void CWriteTest::UnalignedWriteTest()
+	{
+	test.Next( _L("Unaligned write test, simple write function") );
+	DoUnalignedWriteTest( *iSimpleWriter );
+	}
+
+void CWriteTest::UnalignedThreadWriteTest()
+	{
+	test.Next( _L("Unaligned write test, thread write function") );
+	DoUnalignedWriteTest( *iThreadWriter );
+	}
+
+
+void CWriteTest::DoUnalignedWriteTest( MGeneralizedWrite& aWriter )
+	//
+	// Tests writing to unaligned addresses. "Unaligned" here means
+	// addresses that are not on a word boundary.
+	//
+	{
+	TBuf8<32> data;
+
+	_LIT( KWriteMsg, "  writing 32 bytes @0x%x" );
+
+
+	for( TInt offset = 1; offset < 32; offset++ )
+		{
+		CreateRandomData( data, data.MaxLength() );
+		
+		//
+		// get a 64-byte data chunk on a 256-byte boundary, then
+		// start the write at <offset> bytes into this buffer
+		//
+		TUint dataChunk = iBlocks->NextErasedDataChunk( 64, 256 );
+
+		test.Printf( KWriteMsg, dataChunk + offset );
+		aWriter.CheckedWrite( dataChunk + offset, data );
+
+		_LIT( KBeforeMsg,  " checking unused portion before data" );
+		test.Printf( KBeforeMsg );
+		test( CheckOnes( dataChunk, offset ) );
+
+		// check that the section after the data still contains all ones
+		_LIT( KAfterMsg, " checking unused portion after data" );
+		test.Printf( KAfterMsg );
+		test( CheckOnes( dataChunk + offset + data.Length(), 64 - offset - data.Length() ) );
+		}
+	}
+
+
+
+void CWriteTest::OffsetDescriptorAlignedWriteTest()
+	//
+	// Tests writing using an offset into the source data buffer. Writes
+	// are done to word-aligned destination addresses.
+	//
+	{
+	test.Next( _L("Offset-desc write test, aligned dest address") );
+
+	TBuf8<64> data;
+
+	_LIT( KWriteMsg, "  writing 32 bytes from offset %d to @0x%x" );
+
+//	CreateRandomData( data, data.MaxLength() );
+	data.SetLength(64);
+	for( TInt i = 0; i < 64; i++ )
+		{
+		data[i] = i;
+		}
+
+	for( TInt descOffset = 1; descOffset < 32; descOffset++ )
+		{
+		//
+		// Get a 32-byte data chunk on a word boundary.
+		//
+		TUint dataChunk = iBlocks->NextErasedDataChunk( 32, 4 );
+
+		test.Printf( KWriteMsg, descOffset, dataChunk );
+		iThreadWriter->CheckedThreadWrite( dataChunk, 32, data, descOffset );
+
+		//
+		// Read the data back out and check it matches
+		//
+		_LIT( KReadBackMsg, "Reading back data" );
+		test.Printf( KReadBackMsg );
+		TBuf8<32> readData;
+		iDrive.Read( dataChunk, 32, readData );
+		TPtrC8 ptr( data.Ptr() + descOffset, 32 );
+		test( ptr == readData );
+		}
+	}
+
+
+void CWriteTest::OffsetDescriptorUnalignedWriteTest()
+	//
+	// This is a variation of OffsetDescriptorAlignedWriteTest that
+	// also writes to non-word-aligned destionation addresses.
+	//
+	{
+	test.Next( _L("Offset-desc write test, unaligned dest address") );
+
+	TBuf8<64> data;
+
+	_LIT( KWriteMsg, "  writing 32 bytes from offset %d to @0x%x" );
+
+	CreateRandomData( data, data.MaxLength() );
+
+	for( TInt descOffset = 1; descOffset < 32; descOffset++ )
+		{
+		for( TInt unalign = 1; unalign < 4; unalign++ )
+			{
+			//
+			// Get a 40-byte data chunk on a word boundary.
+			//
+			TUint dataChunk = iBlocks->NextErasedDataChunk( 40, 4 );
+			TUint destOffset = dataChunk + unalign;
+
+			test.Printf( KWriteMsg, descOffset, destOffset );
+			iThreadWriter->CheckedThreadWrite( destOffset, 32, data, descOffset );
+
+			//
+			// Read the data back out and check it matches
+			//
+			_LIT( KReadBackMsg, "Reading back data" );
+			test.Printf( KReadBackMsg );
+			TBuf8<32> readData;
+			iDrive.Read( destOffset, 32, readData );
+			TPtrC8 ptr( data.Ptr() + descOffset, 32 );
+			test( ptr == readData );
+			}
+		}
+	}
+
+
+void CWriteTest::OffsetDescriptorCurrentThreadAlignedWriteTest()
+	//
+	// Tests writing using an offset into the source data buffer. Writes
+	// are done to word-aligned destination addresses. This uses the
+	// thread variant of the write function but passes the handle
+	// of this thread.
+	//
+	{
+	test.Next( _L("Offset-desc write test, current thread, aligned dest address") );
+
+	TBuf8<64> data;
+
+	_LIT( KWriteMsg, "  writing 32 bytes from offset %d to @0x%x" );
+
+//	CreateRandomData( data, data.MaxLength() );
+	data.SetLength(64);
+	for( TInt i = 0; i < 64; i++ )
+		{
+		data[i] = i;
+		}
+
+	for( TInt descOffset = 1; descOffset < 32; descOffset++ )
+		{
+		//
+		// Get a 32-byte data chunk on a word boundary.
+		//
+		TUint dataChunk = iBlocks->NextErasedDataChunk( 32, 4 );
+
+		test.Printf( KWriteMsg, descOffset, dataChunk );
+		iThreadWriter->CurrentThreadCheckedThreadWrite( dataChunk, 32, data, descOffset );
+
+		//
+		// Read the data back out and check it matches
+		//
+		_LIT( KReadBackMsg, "Reading back data" );
+		test.Printf( KReadBackMsg );
+		TBuf8<32> readData;
+		iDrive.Read( dataChunk, 32, readData );
+		TPtrC8 ptr( data.Ptr() + descOffset, 32 );
+		test( ptr == readData );
+		}
+	}
+
+
+void CWriteTest::OffsetDescriptorCurrentThreadUnalignedWriteTest()
+	//
+	// This is a variation of OffsetDescriptorCurrentThreadAlignedWriteTest
+	// that also writes to non-word-aligned destionation addresses.
+	//
+	{
+	test.Next( _L("Offset-desc write test, current thread, unaligned dest address") );
+
+	TBuf8<64> data;
+
+	_LIT( KWriteMsg, "  writing 32 bytes from offset %d to @0x%x" );
+
+	CreateRandomData( data, data.MaxLength() );
+
+	for( TInt descOffset = 1; descOffset < 32; descOffset++ )
+		{
+		for( TInt unalign = 1; unalign < 4; unalign++ )
+			{
+			//
+			// Get a 40-byte data chunk on a word boundary.
+			//
+			TUint dataChunk = iBlocks->NextErasedDataChunk( 40, 4 );
+			TUint destOffset = dataChunk + unalign;
+
+			test.Printf( KWriteMsg, descOffset, destOffset );
+			iThreadWriter->CurrentThreadCheckedThreadWrite( destOffset, 32, data, descOffset );
+
+			//
+			// Read the data back out and check it matches
+			//
+			_LIT( KReadBackMsg, "Reading back data" );
+			test.Printf( KReadBackMsg );
+			TBuf8<32> readData;
+			iDrive.Read( destOffset, 32, readData );
+			TPtrC8 ptr( data.Ptr() + descOffset, 32 );
+			test( ptr == readData );
+			}
+		}
+	}
+
+
+
+void CWriteTest::JoinedWriteTest()
+	//
+	// Makes two consecutive writes. Checks that the complete
+	// data block was written correctly. The data is written within
+	// a 64-byte window and the join position is moved along to each
+	// possible location
+	{
+	
+	test.Next( _L("Joined write test, simple writes") );
+	
+	//
+	// Reinitialise the chunk allocator
+	//
+	iBlocks->InitialiseDataChunkAllocator();
+	
+	for( TInt join = 1; join < 63; join++ )
+		{
+		TBuf8<64> fullData;
+		CreateRandomData( fullData, fullData.MaxLength() );
+		
+		//
+		// Create two TPtrC8s to the two parts of the data
+		//
+		TPtrC8 first( fullData.Ptr(), join );
+		TPtrC8 second( fullData.Ptr() + join, fullData.MaxLength() - join );
+		__ASSERT_ALWAYS( first.Length() + second.Length() == 64, Panic( EPanicJoinMaths ) );
+
+		//
+		// Get a location in the Flash to write to
+		//
+		TUint dataChunk = iBlocks->NextErasedDataChunk( 64, 64 );
+
+		//
+		// Write the two halves of the data
+		//
+		_LIT( KWriteMsg, "  writing %d bytes @ 0x%x and %d bytes @ 0x%x" );
+		test.Printf( KWriteMsg, first.Length(), dataChunk,
+								second.Length(), dataChunk + first.Length() );
+		test( KErrNone == iDrive.Write( dataChunk, first ) );
+		test( KErrNone == iDrive.Write( dataChunk + first.Length(), second ) );
+
+		//
+		// Compare the data
+		//
+		_LIT( KCompareMsg, "  comparing data against Flash" );
+		test.Printf( KCompareMsg );
+		test( CompareAgainstFlash( dataChunk, fullData ) );
+		}
+	}
+
+
+void CWriteTest::JoinedThreadWriteTest()
+	//
+	// Makes two consecutive writes. Checks that the complete
+	// data block was written correctly. The data is written within
+	// a 64-byte window and the join position is moved along to each
+	// possible location
+	//
+	// This is similar to JoinedWriteTest except that the thread write
+	// function is used with a descriptor offset to chop up the
+	// source data
+	//
+	{
+	
+	test.Next( _L("Joined write test, thread writes") );
+	
+	//
+	// Reinitialise the chunk allocator
+	//
+	iBlocks->InitialiseDataChunkAllocator();
+	
+	for( TInt join = 1; join < 63; join++ )
+		{
+		TBuf8<64> fullData;
+		CreateRandomData( fullData, fullData.MaxLength() );
+		
+		//
+		// Get a location in the Flash to write to
+		//
+		TUint dataChunk = iBlocks->NextErasedDataChunk( 64, 64 );
+
+		//
+		// Write the two halves of the data
+		//
+		_LIT( KWriteMsg, "  writing %d bytes @ 0x%x and %d bytes @ 0x%x" );
+		test.Printf( KWriteMsg, join, dataChunk, 64 - join, dataChunk + join );
+#if 0
+		test( KErrNone == iDrive.Write( dataChunk, join, &fullData, DummyThreadHandle(), 0 ) );
+		test( KErrNone == iDrive.Write( dataChunk + join, 64-join, &fullData, DummyThreadHandle(), join ) );
+#else
+		test( KErrNone == iDrive.Write( dataChunk, join, &fullData, KLocalMessageHandle, 0 ) );
+		test( KErrNone == iDrive.Write( dataChunk + join, 64-join, &fullData, KLocalMessageHandle, join ) );
+#endif
+
+
+		//
+		// Compare the data
+		//
+		_LIT( KCompareMsg, "  comparing data against Flash" );
+		test.Printf( KCompareMsg );
+		test( CompareAgainstFlash( dataChunk, fullData ) );
+		}
+	}
+
+
+
+void CWriteTest::SingleBitOverwriteTest()
+	//
+	// Tests overwriting single bits within a byte. a 32-byte
+	// section of Flash is filled with data, with one byte initially
+	// 0xFF. A bit is then written to zero and the whole data block
+	// is verified.
+	//
+	{
+	test.Next( _L("Single bit overwrite test") );
+
+	iBlocks->InitialiseDataChunkAllocator();
+
+	for( TInt testByteOffset = 0; testByteOffset < 32; testByteOffset++ )
+		{
+		for( TInt testBitNumber = 0; testBitNumber < 8; testBitNumber++ )
+			{
+			TBuf8<32> data;
+			CreateRandomData( data, data.MaxLength() );
+			data[ testByteOffset ] = 0xFF;	// force test byte to 0xFF
+
+			TUint flashOffset = iBlocks->NextErasedDataChunk( 32, 32 );
+			
+			_LIT( KWriteMsg, "writing test data @0x%x, test byte offset=%d; test bit #%d");
+			test.Printf( KWriteMsg, flashOffset, testByteOffset, testBitNumber );
+
+			iSimpleWriter->CheckedWrite( flashOffset, data );
+
+			// clear the test bit
+			TBuf8<1> byte;
+			byte.SetLength(1);
+			byte[0] = ~(1 << testBitNumber);
+			data[ testByteOffset ] = byte[0];
+			
+			iSimpleWriter->CheckedWrite( flashOffset + testByteOffset, byte );
+
+			// check that the contents of the Flash matches the buffer
+			test( CompareAgainstFlash( flashOffset, data ) );
+			}
+		}
+	}
+
+void CWriteTest::TwoBitOverwriteTest()
+	//
+	// Tests overwriting two bits within a byte. a 32-byte
+	// section of Flash is filled with data, with one byte initially
+	// 0xFF. Two bits are then written to zero and the whole data block
+	// is verified.
+	//
+	{
+	static const TUint pattConv[16] =
+		{
+		// used to create a string representation of binary value
+		0x0000, 0x0001, 0x0010, 0x0011, 0x0100, 0x0101, 0x0110, 0x0111,
+		0x1000, 0x1001, 0x1010, 0x1011, 0x1100, 0x1101, 0x1110, 0x1111
+		};
+	test.Next( _L("Two bit overwrite test") );
+
+	for( TInt testByteOffset = 0; testByteOffset < 32; testByteOffset++ )
+		{
+		for( TInt testBitJ = 0; testBitJ < 7; testBitJ++ )
+			{
+			for( TInt testBitK = testBitJ+1; testBitK < 8; testBitK++ )
+				{
+				TBuf8<32> data;
+				CreateRandomData( data, data.MaxLength() );
+				data[ testByteOffset ] = 0xFF;	// force test byte to 0xFF
+
+				TUint flashOffset = iBlocks->NextErasedDataChunk( 32, 32 );
+				
+				TUint8 testPattern = ~((1 << testBitJ) | (1 << testBitK));
+
+				_LIT( KWriteMsg, "writing test data @0x%x, test byte offset=%d; test pattern = %04x%04x");
+				test.Printf( KWriteMsg, flashOffset, testByteOffset, 
+							pattConv[ testPattern >> 4 ], pattConv[ testPattern&0xF ] );
+
+				iSimpleWriter->CheckedWrite( flashOffset, data );
+
+				TBuf8<1> byte;
+				byte.SetLength(1);
+				byte[0] = testPattern;
+				data[ testByteOffset ] = testPattern;
+				
+				iSimpleWriter->CheckedWrite( flashOffset + testByteOffset, byte );
+
+				// check that the contents of the Flash matches the buffer
+				test( CompareAgainstFlash( flashOffset, data ) );
+				}
+			}
+		}
+	}
+
+
+void CWriteTest::RunSimulationTest()
+	//
+	// A simulation of the way the LFFS filesystem will use a Flash block
+	// Alternately writes 24 bytes to bottom of block, 512 bytes to top,
+	// clears a bit in the 24-byte block. Repeats until block is full.
+	//
+	{
+	test.Next( _L("Simulation test") );
+
+	TUint blockBase = iBlocks->BlockAddress( iBlocks->NextErasedBlock() );
+
+	TUint lowAddress = blockBase;
+	TUint highAddress = blockBase + iBlocks->BlockSize() - 512;
+
+	TBuf8<24> lowData;
+	TBuf8<512> highData;
+	TPtrC8 overwritePtr( lowData.Ptr(), 1 );
+
+	while( lowAddress + 24 < highAddress )
+		{
+		CreateRandomData( lowData, lowData.MaxLength() );
+		CreateRandomData( highData, highData.MaxLength() );
+		lowData[0] = 0xE7;	// just some non-0xFF value
+
+		_LIT( KWriteMsg, "Writing block size 24 @ 0x%x; block size 512 @ 0x%x" );
+		test.Printf( KWriteMsg, lowAddress, highAddress );
+
+		iSimpleWriter->CheckedWrite( lowAddress, lowData );
+		iSimpleWriter->Write( highAddress, highData );
+
+		// Overwrite the byte
+		lowData[0] = 0xA7;
+		iSimpleWriter->Write( lowAddress, overwritePtr );
+
+		test( CompareAgainstFlash( lowAddress, lowData ) );
+		test( CompareAgainstFlash( highAddress, highData ) );
+
+		lowAddress += lowData.Length();
+		highAddress -= highData.Length();
+		}
+	}
+
+
+
+void CWriteTest::DoTests()
+	//
+	// Main test dispatcher
+	//
+	{
+	test.Next( _L("Erasing all blocks") );
+	iBlocks->InitialiseSequentialBlockAllocator();
+	iBlocks->EraseAllBlocks();
+
+	//
+	// Basic tests that we can write data correctly without corrupting
+	// the source buffer
+	//
+	SimpleWriteTest();
+	SimpleThreadWriteTest();
+
+	//
+	// Test aligned writes of various lengths
+	//
+	AlignedWriteTest();
+	AlignedThreadWriteTest();
+
+	//
+	// Test writing to unaligned locations
+	//
+	UnalignedWriteTest();
+	UnalignedThreadWriteTest();
+
+	//
+	// Test writes with offset into source desriptor
+	//
+	OffsetDescriptorCurrentThreadAlignedWriteTest();
+	OffsetDescriptorCurrentThreadUnalignedWriteTest();
+	OffsetDescriptorAlignedWriteTest();
+	OffsetDescriptorUnalignedWriteTest();
+
+	//
+	// Test two consecutive writes
+	//
+	JoinedWriteTest();
+	JoinedThreadWriteTest();
+
+	//
+	// Test that we can overwrite bits
+	//
+	SingleBitOverwriteTest();
+	TwoBitOverwriteTest();
+
+	//
+	// A simulation test of LFFS usage
+	//
+	RunSimulationTest();
+	}
+
+
+
+
+
+
+
+
+
+
+TInt E32Main()
+	{
+	test.Title();
+	test.Start(_L("Testing media read operations"));
+
+	CWriteTest writeTest;
+	TRAPD( ret, writeTest.CreateL() );
+	test( KErrNone == ret );
+	writeTest.DoTests();
+	test.End();
+
+	return 0;
+	}