// Copyright (c) 2006-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:
// e32test\defrag\t_pagemove.cpp
//
//--------------------------------------------------------------------------------------------------
//! @SYMTestCaseID KBASE-T_PAGEMOVE-0572
//! @SYMTestType UT
//! @SYMPREQ PREQ308
//! @SYMTestCaseDesc Test physical page moving
//! t_pagemove loads and opens the logical device driver ("D_PAGEMOVE.LDD").
//! Following this, it requests that the driver attempt to move
//! various kinds of pages directly.
//!
//! API Information:
//! RBusLogicalChannel
//!
//! Platforms/Drives/Compatibility:
//! Hardware only. No defrag support on emulator.
//!
//! @SYMTestActions 1 - Move regular local data pages
//! 2 - Move regular global data pages
//! 3 - Move DLL writable static data pages
//! 4 - Move user self-modifying code chunk pages
//! 5 - Move RAM drive pages
//! 6 - Move kernel heap pages (*********DISABLED************)
//! 7 - Move kernel stack pages
//! 8 - Move kernel code pages
//! 9 - Move regular code pages
//! 10 - Move code whilst the page is being modified
//! 11 - Move code (async) whilst the page is being modified
//! 12 - Move ROM locale DLL pages
//! 13 - Move RAM locale DLL pages
//! 14 - Moving pages whilst they are being virtually pinned and unpinned.
//! 15 - Moving pages whilst they are being physically pinned and unpinned.
//! @SYMTestExpectedResults All tests should pass.
//! @SYMTestPriority High
//! @SYMTestStatus Implemented
//--------------------------------------------------------------------------------------------------
//
#define __E32TEST_EXTENSION__
#include <e32test.h>
#include <e32math.h>
#include <e32uid.h>
#include <e32hal.h>
#include <e32std.h>
#include <e32std_private.h>
#include <dptest.h>
#include "d_pagemove.h"
#include "t_pagemove_dll.h"
#include "t_pmwsd.h"
#include "..\mmu\mmudetect.h"
#include "..\debug\d_codemodifier.h"
#include "..\mmu\d_memorytest.h"
//#define _DEBUG_MSG
#ifdef _DEBUG_MSG
#define _R_PRINTF(x) RDebug::Printf(x)
#define _T_PRINTF(x) test.Printf(x)
#else
#define _R_PRINTF(x)
#define _T_PRINTF(x)
#endif
LOCAL_D RTest test(_L("T_PAGEMOVE"));
_LIT(ELOCL_DEFAULT, "");
_LIT(ELOCLUS, "T_LOCLUS_RAM");
_LIT(ELOCLUS_ROM, "T_LOCLUS");
LOCAL_C TInt E32TestLocale(TInt);
RCodeModifierDevice Device;
extern TInt TestCodeModFunc();
extern TInt Increment(TInt);
extern TUint Increment_Length();
extern TInt Decrement(TInt);
extern TUint Decrement_Length();
typedef TInt (*PFI)(TInt);
LOCAL_C void StartCodeModifierDriver();
LOCAL_C void StopCodeModifierDriver();
LOCAL_C TInt TestCodeModification(RPageMove &);
LOCAL_C TInt TestCodeModificationAsync(RPageMove& pagemove);
const TPtrC KLddFileName=_L("D_PAGEMOVE.LDD");
TInt Repitions=4000;
TInt PageSize;
TUint NumberOfCpus;
volatile TBool ThreadDie;
TBool gDataPagingSupported;
TBool gRomPagingSupported;
TBool gCodePagingSupported;
TBool gPinningSupported;
// This executable is ram loaded (see mmp file) so this function will do fine
// as a test of RAM-loaded code.
TInt RamLoadedFunction()
{
return KArbitraryNumber;
}
struct SPinThreadArgs
{
TLinAddr iLinAddr;
TTestFunction iTestFunc;
RThread iParentThread;
User::TRealtimeState iRealtimeState;
};
void StartThreads( TUint aNumThreads, RThread* aThreads, TRequestStatus* aStatus,
TThreadFunction aThreadFunc, SPinThreadArgs& aThreadArgs)
{
for (TUint i = 0; i < aNumThreads; i++)
{
test_KErrNone(aThreads[i].Create(KNullDesC, aThreadFunc, KDefaultStackSize, NULL, &aThreadArgs));
aThreads[i].Logon(aStatus[i]);
TRequestStatus threadInitialised;
aThreads[i].Rendezvous(threadInitialised);
aThreads[i].Resume();
_T_PRINTF(_L("wait for child\n"));
User::WaitForRequest(threadInitialised);
test_KErrNone(threadInitialised.Int());
}
}
void EndThreads(TUint aNumThreads, RThread* aThreads, TRequestStatus* aStatus)
{
for (TUint i = 0; i < aNumThreads; i++)
{
User::WaitForRequest(aStatus[i]);
test_Equal(EExitKill, aThreads[i].ExitType());
test_KErrNone(aThreads[i].ExitReason());
aThreads[i].Close();
}
}
void Reschedule(TInt64& aSeed)
{
if (NumberOfCpus == 1)
{
TInt rand = Math::Rand(aSeed);
if ((rand & 0x5) == 5)
User::AfterHighRes(rand & 0x7);
}
}
TInt ReadWriteByte(TAny* aParam)
{
SPinThreadArgs* args = (SPinThreadArgs*)aParam;
volatile TUint8* byte = (volatile TUint8*)args->iLinAddr;
TInt64 seed = Math::Random()*Math::Random();
test_KErrNone(User::SetRealtimeState(args->iRealtimeState));
// Ensure the the parentThread has moved the page at least once
// before we start accessing it.
TRequestStatus status;
args->iParentThread.Rendezvous(status);
RThread::Rendezvous(KErrNone);
_R_PRINTF("wait for parent");
User::WaitForRequest(status);
_R_PRINTF("acesssing page");
FOREVER
{
*byte = *byte;
Reschedule(seed);
if (ThreadDie)
break;
}
return KErrNone;
}
TInt RunCodeThread(TAny* aParam)
{
TInt64 seed = Math::Random()*Math::Random();
SPinThreadArgs* args = (SPinThreadArgs*)aParam;
test_KErrNone(User::SetRealtimeState(args->iRealtimeState));
// Ensure the the parentThread has moved the page at least once
// before we start accessing it.
TRequestStatus status;
args->iParentThread.Rendezvous(status);
RThread::Rendezvous(KErrNone);
_R_PRINTF("wait for parent");
User::WaitForRequest(status);
_R_PRINTF("acesssing page");
FOREVER
{
TInt r = args->iTestFunc();
if (r != KArbitraryNumber)
return KErrGeneral;
Reschedule(seed);
if (ThreadDie)
break;
}
return KErrNone;
}
TInt VirtualPinPage(TAny* aParam)
{
TInt64 seed = Math::Random()*Math::Random();
SPinThreadArgs* args = (SPinThreadArgs*)aParam;
RMemoryTestLdd ldd;
test_KErrNone(ldd.Open());
test_KErrNone(ldd.CreateVirtualPinObject());
TBool firstRun = ETrue;
FOREVER
{
// Pin the page of aParam.
test_KErrNone(ldd.PinVirtualMemory(args->iLinAddr, PageSize));
if (firstRun)
{// On the first run ensure that the page is definitely pinned when
// the parent thread first attempts to move it.
TRequestStatus status;
args->iParentThread.Rendezvous(status);
RThread::Rendezvous(KErrNone);
User::WaitForRequest(status);
test_KErrNone(status.Int());
firstRun = EFalse;
}
Reschedule(seed);
test_KErrNone(ldd.UnpinVirtualMemory());
if (ThreadDie)
break;
}
test_KErrNone(ldd.DestroyVirtualPinObject());
ldd.Close();
return KErrNone;
}
TInt PhysicalPinPage(TAny* aParam)
{
TInt64 seed = Math::Random()*Math::Random();
SPinThreadArgs* args = (SPinThreadArgs*)aParam;
RMemoryTestLdd ldd;
test_KErrNone(ldd.Open());
test_KErrNone(ldd.CreatePhysicalPinObject());
TBool firstRun = ETrue;
FOREVER
{
// Pin the page of aParam, use a read only pinning so that pinning code
// doesn't return KErrAccessDenied as writable mappings not allowed on code.
test_KErrNone(ldd.PinPhysicalMemoryRO(args->iLinAddr, PageSize));
if (firstRun)
{// On the first run ensure that the page is definitely pinned when
// the parent thread first attempts to move it.
TRequestStatus status;
args->iParentThread.Rendezvous(status);
RThread::Rendezvous(KErrNone);
User::WaitForRequest(status);
test_KErrNone(status.Int());
firstRun = EFalse;
}
Reschedule(seed);
test_KErrNone(ldd.UnpinPhysicalMemory());
if (ThreadDie)
break;
}
test_KErrNone(ldd.DestroyPhysicalPinObject());
ldd.Close();
return KErrNone;
}
TInt ModifyCodeThread(TAny* aParam)
{
SPinThreadArgs* args = (SPinThreadArgs*)aParam;
TUint8* p = (TUint8*)args->iLinAddr;
PFI func = (PFI)p;
// Ensure the the parentThread has moved the page at least once
// before we start accessing it.
TRequestStatus status;
args->iParentThread.Rendezvous(status);
RThread::Rendezvous(KErrNone);
_R_PRINTF("wait for parent");
User::WaitForRequest(status);
_R_PRINTF("modifiying page");
while (!ThreadDie)
{
Mem::Copy(p, (TAny*)&Increment, Increment_Length());
User::IMB_Range(p, p+Increment_Length());
test_Equal(8, func(7));
Mem::Copy(p, (TAny*)&Decrement, Decrement_Length());
User::IMB_Range(p, p+Decrement_Length());
test_Equal(6, func(7));
}
return KErrNone;
}
enum TMovingPinStage
{
ENoPinning,
EVirtualPinning,
EPhysicalPinning,
EMovingPinStages,
};
void TestUserData(RPageMove& pagemove, TUint8* array, TInt size, TBool aPagedData=EFalse)
{
_T_PRINTF(_L("Fill the array with some data\n"));
for (TInt i=0; i<size; i++) array[i] = i*i;
TUint8* firstpage = (TUint8*)_ALIGN_DOWN((TLinAddr)array, PageSize);
RThread thread;
thread.Open(RThread().Id());
SPinThreadArgs threadArgs;
threadArgs.iLinAddr = (TLinAddr)array;
threadArgs.iParentThread = thread;
threadArgs.iRealtimeState = User::ERealtimeStateOff;
TMovingPinStage endStage = EMovingPinStages;
if (!gPinningSupported)
endStage = EVirtualPinning;
for (TUint state = ENoPinning; state < (TUint)endStage; state++)
{
TThreadFunction threadFunc = NULL;
switch (state)
{
case ENoPinning:
test.Printf(_L("Attempt to move pages while they are being modified\n"));
threadFunc = &ReadWriteByte;
break;
case EVirtualPinning:
test.Printf(_L("Attempt to move pages while they are being virtually pinned\n"));
threadFunc = &VirtualPinPage;
break;
case EPhysicalPinning:
test.Printf(_L("Attempt to move pages while they are being physically pinned\n"));
threadFunc = &PhysicalPinPage;
break;
}
ThreadDie = EFalse;
TUint numThreads = (NumberOfCpus > 1) ? NumberOfCpus - 1 : 1;
RThread* userDataThread = new RThread[numThreads];
TRequestStatus* s = new TRequestStatus[numThreads];
StartThreads(numThreads, userDataThread, s, threadFunc, threadArgs);
_T_PRINTF(_L("Move first array page repeatedly\n"));
TBool success=EFalse;
TUint inuse = 0;
*(volatile TUint8*)array = *array; // Ensure the page of the first entry is paged in for the first move.
for (TInt i=0; i < Repitions*2; i++)
{
TInt r = pagemove.TryMovingUserPage(firstpage, ETrue);
if (i == 0)
{// If this is the first run allow the pinning threads to
// unpin the memory now that we've definitely done at least
// one page move with the page pinned.
_T_PRINTF(_L("signal to child\n"));
RThread::Rendezvous(KErrNone);
}
switch (r)
{
case KErrInUse:
inuse++;
break;
case KErrArgument:
// The page was paged out, this should only happen for paged data.
test(aPagedData);
break;
default:
test_KErrNone(r);
success=ETrue;
break;
}
}
// Can't guarantee that for paged data the page and its page tables will
// be paged in, in most cases it will be at least once.
// Pinning the page should always return KErrInUse except for virtually
// pinned non-paged memory as virtual pinning is a nop for unpaged memory.
test.Printf(_L("inuse test removed; inuse %d\n"),inuse);
//test(inuse || aPagedData || state == EVirtualPinning);
test(success || state == EPhysicalPinning);
ThreadDie = ETrue;
EndThreads(numThreads, userDataThread, s);
_T_PRINTF(_L("Validate page data\n"));
for (TInt i=0; i<size; i++)
test_Equal((TUint8)(i*i), array[i]);
}
thread.Close();
}
void TestMovingCode(RPageMove& aPagemove, TTestFunction aFunc, TBool aPaged=EFalse)
{
TUint8* firstpage = (TUint8*)_ALIGN_DOWN((TLinAddr)aFunc, PageSize);
RThread thread;
thread.Open(RThread().Id());
SPinThreadArgs threadArgs;
threadArgs.iLinAddr = (TLinAddr)firstpage;
threadArgs.iTestFunc = aFunc;
threadArgs.iParentThread = thread;
threadArgs.iRealtimeState = User::ERealtimeStateOff;
TMovingPinStage endStage = EMovingPinStages;
if (!gPinningSupported)
endStage = EVirtualPinning;
for (TUint state = ENoPinning; state < (TUint)endStage; state++)
{
TThreadFunction threadFunc = NULL;
switch (state)
{
case ENoPinning:
test.Printf(_L("Attempt to move pages while they are being executed\n"));
threadFunc = &RunCodeThread;
test_Equal(KArbitraryNumber, aFunc()); // Ensure the page is paged in.
break;
case EVirtualPinning:
test.Printf(_L("Attempt to move pages while they are being virtually pinned\n"));
threadFunc = &VirtualPinPage;
break;
case EPhysicalPinning:
test.Printf(_L("Attempt to move pages while they are being physically pinned\n"));
threadFunc = &PhysicalPinPage;
break;
}
ThreadDie = EFalse;
TUint numThreads = (NumberOfCpus > 1) ? NumberOfCpus - 1 : 1;
RThread* codeRunThread = new RThread[numThreads];
TRequestStatus* s = new TRequestStatus[numThreads];
StartThreads(numThreads, codeRunThread, s, threadFunc, threadArgs);
_T_PRINTF(_L("Move first code page repeatedly\n"));
test_Equal(KArbitraryNumber, aFunc());
TBool inuse=EFalse, success=EFalse;
for (TInt i=0; i < Repitions; i++)
{
TInt r = aPagemove.TryMovingUserPage(firstpage, ETrue);
if (i == 0)
{// If this is the first run allow the pinning threads to
// unpin the memory now that we've definitely done at least
// one page move with the page pinned.
_T_PRINTF(_L("signal to child\n"));
RThread::Rendezvous(KErrNone);
}
switch (r)
{
case KErrInUse:
inuse=ETrue;
break;
case KErrArgument:
// The page was paged out, this should only happen for paged code.
test(aPaged);
break;
default:
test_KErrNone(r);
success=ETrue;
break;
}
}
// Physical pinning or adding a new pinning while a page is being moved
// should prevent code pages being moved.
switch (state)
{
case ENoPinning :
test(!inuse || aPaged); // Stealing may get KErrInUse but this should only happen for paged code.
case EVirtualPinning :
test(success);
break;
case EPhysicalPinning :
break;
}
ThreadDie = ETrue;
EndThreads(numThreads, codeRunThread, s);
_T_PRINTF(_L("Validate page data\n"));
test_Equal(KArbitraryNumber, aFunc());
}
thread.Close();
}
void TestMovingRealtime(RPageMove& aPagemove, TUint8* aArray, TInt aSize, TTestFunction aFunc, TBool aCode, TBool aPaged=EFalse)
{
TThreadFunction threadFunc;
TLinAddr pageAddr;
RThread thread;
TUint8* firstpage;
thread.Open(RThread().Id());
SPinThreadArgs threadArgs;
threadArgs.iParentThread = thread;
if (aCode)
{
pageAddr = (TLinAddr)aFunc;
firstpage = (TUint8*)_ALIGN_DOWN(pageAddr, PageSize);
threadArgs.iLinAddr = (TLinAddr)firstpage;
threadFunc = RunCodeThread;
threadArgs.iTestFunc = aFunc;
test_Equal(KArbitraryNumber, aFunc());
}
else
{
pageAddr = (TLinAddr)aArray;
firstpage = (TUint8*)_ALIGN_DOWN(pageAddr, PageSize);
threadArgs.iLinAddr = (TLinAddr)aArray;
threadFunc = ReadWriteByte;
_T_PRINTF(_L("Fill the array with some data\n"));
for (TInt i=0; i<aSize; i++) aArray[i] = i*i;
}
RMemoryTestLdd ldd;
TMovingPinStage endStage = EMovingPinStages;
if (gPinningSupported)
{
test_KErrNone(ldd.Open());
test_KErrNone(ldd.CreateVirtualPinObject());
test_KErrNone(ldd.CreatePhysicalPinObject());
}
else
endStage = EVirtualPinning;
for (TUint state = ENoPinning; state < (TUint)endStage; state++)
{
switch (state)
{
case ENoPinning:
test.Printf(_L("Attempt to move pages while they are being accessed\n"));
break;
case EVirtualPinning:
test.Printf(_L("Attempt to move pages while they are virtually pinned\n"));
test_KErrNone(ldd.PinVirtualMemory((TLinAddr)firstpage, PageSize));
break;
case EPhysicalPinning:
test.Printf(_L("Attempt to move pages while they are physically pinned\n"));
test_KErrNone(ldd.PinPhysicalMemoryRO((TLinAddr)firstpage, PageSize));
break;
}
for ( TUint realtimeState = User::ERealtimeStateOff;
realtimeState <= User::ERealtimeStateWarn;
realtimeState++)
{
ThreadDie = EFalse;
RThread accessThread;
TRequestStatus s;
threadArgs.iRealtimeState = (User::TRealtimeState)realtimeState;
test_KErrNone(accessThread.Create(_L("Realtime Thread"), threadFunc, KDefaultStackSize, NULL, &threadArgs));
accessThread.Logon(s);
TRequestStatus threadInitialised;
accessThread.Rendezvous(threadInitialised);
accessThread.Resume();
_T_PRINTF(_L("wait for child\n"));
User::WaitForRequest(threadInitialised);
test_KErrNone(threadInitialised.Int());
_T_PRINTF(_L("Move page repeatedly\n"));
TBool success=EFalse, pagedOut=EFalse;
TUint inuse=0;
if (aCode)
{
test_Equal(KArbitraryNumber, aFunc());
}
else
{
*(volatile TUint8*)aArray = *aArray;
}
for (TInt i=0; i < Repitions; i++)
{
TInt r = aPagemove.TryMovingUserPage(firstpage, ETrue);
if (i == 0)
{
_T_PRINTF(_L("signal to child\n"));
RThread::Rendezvous(KErrNone);
}
switch (r)
{
case KErrInUse:
inuse++;
break;
case KErrArgument:
// The page was paged out, this should only happen for paged code.
test(aPaged);
pagedOut = ETrue;
break;
default:
test_KErrNone(r);
success=ETrue;
break;
}
}
ThreadDie = ETrue;
User::WaitForRequest(s);
test.Printf(_L("inuse %d\n"),inuse);
switch (state)
{
case ENoPinning :
test(success);
if (EExitPanic == accessThread.ExitType())
{
test(accessThread.ExitCategory()==_L("KERN-EXEC"));
test_Equal(EIllegalFunctionForRealtimeThread, accessThread.ExitReason());
test(aPaged && realtimeState == User::ERealtimeStateOn);
}
else
{
test_Equal(EExitKill,accessThread.ExitType());
test_KErrNone(accessThread.ExitReason());
}
// Ensure the page is paged in before we attempt to move it again with a different realtime state.
if (aCode)
{
test_Equal(KArbitraryNumber, aFunc());
}
else
{
*(volatile TUint8*)aArray = *aArray;
}
break;
case EVirtualPinning :
test(!aCode || !inuse);
test(success);
test(!pagedOut);
test_Equal(EExitKill,accessThread.ExitType());
test_KErrNone(accessThread.ExitReason());
break;
case EPhysicalPinning :
test(!success);
break;
}
accessThread.Close();
}
if (gPinningSupported)
{
// Unpin any pinned memory.
test_KErrNone(ldd.UnpinVirtualMemory());
test_KErrNone(ldd.UnpinPhysicalMemory());
}
_T_PRINTF(_L("Validate page data\n"));
if (aCode)
{
test_Equal(KArbitraryNumber, aFunc());
}
else
{
for (TInt i=0; i<aSize; i++)
test_Equal((TUint8)(i*i), aArray[i]);
}
}
if (gPinningSupported)
{
test_KErrNone(ldd.DestroyVirtualPinObject());
test_KErrNone(ldd.DestroyPhysicalPinObject());
ldd.Close();
}
thread.Close();
}
// Only commits and decommits the first page as that is the only page that is being moved.
// Plus this ensures the page table and page directories of the chunk are always allocated
// and therefore prevents Epoc::LinearToPhysical() from crashing the system.
TInt CommitDecommit(TAny* aParam)
{
RChunk* chunk = (RChunk*) aParam;
volatile TUint8* byte = chunk->Base();
FOREVER
{
*byte = *byte;
User::AfterHighRes(0);
TInt r = chunk->Decommit(0, PageSize);
if (r != KErrNone)
return r;
User::AfterHighRes(0);
r = chunk->Commit(0, PageSize);
if (r != KErrNone)
return r;
}
}
void TestCommitDecommit(RPageMove& pagemove, RChunk& aChunk)
{
test.Printf(_L("Attempt to move a page while it is being committed and decommited\n"));
RThread thread;
TRequestStatus s;
test_KErrNone(thread.Create(_L("CommitDecommit"), &CommitDecommit, KDefaultStackSize, NULL, (TAny*)&aChunk));
thread.Logon(s);
thread.SetPriority(EPriorityMore);
thread.Resume();
TUint8* firstpage=(TUint8*)_ALIGN_DOWN((TLinAddr)aChunk.Base(), PageSize);
for (TInt i=0; i < Repitions; i++)
{
TInt r = pagemove.TryMovingUserPage(firstpage, ETrue);
// Allow all valid return codes as we are only testing that this doesn't
// crash the kernel and the page could be commited, paged out or decommited
// at any one time.
test_Value(r, r <= KErrNone);
}
thread.Kill(KErrNone);
User::WaitForRequest(s);
test_Equal(EExitKill,thread.ExitType());
test_KErrNone(thread.ExitReason());
thread.Close();
}
void TestPageTableDiscard(RPageMove& pagemove, TUint8* array, TUint size)
{
_T_PRINTF(_L("Fill the array with some data\n"));
for (TUint i=0; i<size; i++) array[i] = i*i;
TUint8* firstpage = (TUint8*)_ALIGN_DOWN((TLinAddr)array, PageSize);
RThread thread;
thread.Open(RThread().Id());
SPinThreadArgs threadArgs;
threadArgs.iLinAddr = (TLinAddr)array;
threadArgs.iParentThread = thread;
threadArgs.iRealtimeState = User::ERealtimeStateOff;
TMovingPinStage endStage = EMovingPinStages;
if (!gPinningSupported)
endStage = EVirtualPinning;
for (TUint pageTableInfo = 0; pageTableInfo < 2; pageTableInfo++)
{
for (TUint state = ENoPinning; state < (TUint)endStage; state++)
{
TThreadFunction threadFunc = NULL;
if (!pageTableInfo)
{
switch (state)
{
case ENoPinning:
test.Printf(_L("Attempt to move page tables whilst the pages they map are being modified\n"));
threadFunc = &ReadWriteByte;
break;
case EVirtualPinning:
test.Printf(_L("Attempt to move page tables whilst the pages they map are being virtually pinned\n"));
threadFunc = &VirtualPinPage;
break;
case EPhysicalPinning:
test.Printf(_L("Attempt to move page tables whilst the pages they map are being physically pinned\n"));
threadFunc = &PhysicalPinPage;
break;
}
}
else
{
switch (state)
{
case ENoPinning:
test.Printf(_L("Attempt to move page table infos whilst pages they refer to are being modified\n"));
threadFunc = &ReadWriteByte;
break;
case EVirtualPinning:
test.Printf(_L("Attempt to move page table infos whilst pages they refer to are being virtually pinned\n"));
threadFunc = &VirtualPinPage;
break;
case EPhysicalPinning:
test.Printf(_L("Attempt to move page table infos whilst pages they refer to are being physically pinned\n"));
threadFunc = &PhysicalPinPage;
break;
}
}
ThreadDie = EFalse;
TUint numThreads = (NumberOfCpus > 1) ? NumberOfCpus - 1 : 1;
RThread* threads = new RThread[numThreads];
TRequestStatus* s = new TRequestStatus[numThreads];
StartThreads(numThreads, threads, s, threadFunc, threadArgs);
_T_PRINTF(_L("Move first array page repeatedly\n"));
TUint inuse = 0;
for (TInt i=0; i < Repitions; i++)
{
TInt r;
if (!pageTableInfo)
r = pagemove.TryMovingPageTable(firstpage);
else
r = pagemove.TryMovingPageTableInfo(firstpage);
if (i == 0)
{// If this is the first run allow the pinning threads to
// unpin the memory now that we've definitely done at least
// one page move with the page pinned.
_T_PRINTF(_L("signal to child\n"));
RThread::Rendezvous(KErrNone);
}
switch (r)
{
case KErrInUse:
inuse++;
break;
case KErrNotFound:
// The page table or page table info page was paged out.
break;
default:
test_KErrNone(r);
break;
}
}
test.Printf(_L("inuse %d\n"),inuse);
// A virtually pinned page should always return KErrInUse at least once.
test(state != EVirtualPinning || inuse);
ThreadDie = ETrue;
EndThreads(numThreads, threads, s);
_T_PRINTF(_L("Validate page data\n"));
for (TUint i=0; i<size; i++)
test_Equal((TUint8)(i*i), array[i]);
}
}
thread.Close();
}
// Basic testing of moving rom pages.
void TestMovingRom(RPageMove& aPageMove)
{
TUint8* pPage=(TUint8*)User::Alloc(PageSize);
test(pPage!=NULL);
TUint romHdr = UserSvr::RomHeaderAddress();
if (gPinningSupported)
{
// Pin an unpaged rom page to get the physical address of the rom page.
// Pinning unpaged rom actually does nothing except return the physical
// address of the page.
RMemoryTestLdd ldd;
test_KErrNone(ldd.Open());
test_KErrNone(ldd.CreatePhysicalPinObject());
// Save contents of rom page.
Mem::Move(pPage,(TAny*)romHdr,PageSize);
test_KErrNone(ldd.PinPhysicalMemoryRO(romHdr, PageSize));
test_KErrNone(ldd.UnpinPhysicalMemory());
// Now move the page, d_memorytest saves the address of the pinned page
// depsite it being unpinned.
// Will get KErrArgument as rom pages don't have an SPageInfo so memory
// model doesn't treat rom as though they are in ram, which in most cases
// they are.
test_Equal(KErrArgument, ldd.MovePinnedPhysicalMemory(0));
test_KErrNone(Mem::Compare((TUint8*)romHdr,PageSize,pPage,PageSize));
test_KErrNone(ldd.DestroyPhysicalPinObject());
ldd.Close();
}
if (gRomPagingSupported)
{
// Use paged part of rom for testing
TRomHeader* romHeader = (TRomHeader*)UserSvr::RomHeaderAddress();
test(romHeader->iPageableRomStart);
TUint romAddr = (TUint)((TUint8*)romHeader + romHeader->iPageableRomStart + 64 * PageSize);
// We will use the 64th pagable rom page so check that it exists.
test(romHeader->iPageableRomSize >= 65 * PageSize);
// Page in the rom page and save it contents.
Mem::Move(pPage,(TAny*)romAddr,PageSize);
// This will actually discard the page not move it.
test_KErrNone(aPageMove.TryMovingUserPage(pPage));
test_KErrNone(Mem::Compare((TUint8*)romAddr,PageSize,pPage,PageSize));
}
}
void TestMovingCodeChunk(RPageMove& pagemove, RChunk aChunk, TBool aPagedData)
{
TUint8* p = aChunk.Base();
TUint8* firstpage = (TUint8*)_ALIGN_DOWN((TLinAddr)p, PageSize);
RThread thread;
thread.Open(RThread().Id());
SPinThreadArgs threadArgs;
threadArgs.iLinAddr = (TLinAddr)p;
threadArgs.iParentThread = thread;
test.Printf(_L("Attempt to move pages while they are being executed and modified\n"));
ThreadDie = EFalse;
RThread modCodeThread;
TRequestStatus s;
test_KErrNone(modCodeThread.Create(_L("User Data thread"), &ModifyCodeThread, KDefaultStackSize, NULL, &threadArgs));
modCodeThread.Logon(s);
TRequestStatus threadInitialised;
modCodeThread.Rendezvous(threadInitialised);
modCodeThread.Resume();
_T_PRINTF(_L("wait for child\n"));
User::WaitForRequest(threadInitialised);
test_KErrNone(threadInitialised.Int());
_T_PRINTF(_L("Move code chunk page repeatedly\n"));
TBool success=EFalse;
*(volatile TUint8*)p = *p; // Ensure the page of the first entry is paged in for the first move.
for (TInt i=0; i < Repitions; i++)
{
TInt r = pagemove.TryMovingUserPage(firstpage, ETrue);
if (i == 0)
{// If this is the first run allow the modifying thread to run now
// we've done one move.
_T_PRINTF(_L("signal to child\n"));
RThread::Rendezvous(KErrNone);
}
switch (r)
{
case KErrInUse:
break;
case KErrArgument:
// The page was paged out, this should only happen for paged data.
test(aPagedData);
break;
default:
test_KErrNone(r);
success=ETrue;
break;
}
}
test(success);
ThreadDie = ETrue;
User::WaitForRequest(s);
test_Equal(EExitKill,modCodeThread.ExitType());
test_KErrNone(modCodeThread.ExitReason());
modCodeThread.Close();
thread.Close();
}
GLDEF_C TInt E32Main()
{
test.Title();
if (!HaveMMU())
{
test.Printf(_L("This test requires an MMU\n"));
return KErrNone;
}
test.Start(_L("Load test LDD"));
TInt r=User::LoadLogicalDevice(KLddFileName);
test(r==KErrNone || r==KErrAlreadyExists);
test_KErrNone(UserHal::PageSizeInBytes(PageSize));
// Determine which types of paging are supported
TUint32 attrs = DPTest::Attributes();
gRomPagingSupported = (attrs & DPTest::ERomPaging) != 0;
gCodePagingSupported = (attrs & DPTest::ECodePaging) != 0;
gDataPagingSupported = (attrs & DPTest::EDataPaging) != 0;
// Does this memory model support pinning.
TInt mm = UserSvr::HalFunction(EHalGroupKernel, EKernelHalMemModelInfo, 0, 0) & EMemModelTypeMask;
gPinningSupported = mm >= EMemModelTypeFlexible;
RPageMove pagemove;
test.Next(_L("Open test LDD"));
test_KErrNone(pagemove.Open());
// Determine whether this is a smp device.
NumberOfCpus = pagemove.NumberOfCpus();
if (NumberOfCpus > 1)
Repitions = 1000; // SMP system therefore likely to get KErrInUse in less repitions.
test.Next(_L("Attempting to move regular local data pages"));
{
const TInt size=16384;
TUint8* array = new TUint8[size];
test_NotNull(array);
TestUserData(pagemove, array, size);
_T_PRINTF(_L("Walk heap\n"));
User::Check();
delete [] array;
}
test.Next(_L("Attempting to move regular global coarse data pages"));
{
const TInt size=1<<20; // Make this chunk multiple of 1MB so it is a coarse memory object on FMM
RChunk chunk;
test_KErrNone(chunk.CreateDisconnectedGlobal(_L("Dave"), 0, size, size));
TUint8* array = chunk.Base();
TestUserData(pagemove, array, size);
TestMovingRealtime(pagemove, array, size, NULL, EFalse);
TestCommitDecommit(pagemove, chunk);
chunk.Close();
}
if (gDataPagingSupported)
{
test.Next(_L("Attempting to move demand paged fine local user data pages"));
const TInt size=16384;
TChunkCreateInfo createInfo;
createInfo.SetDisconnected(0, size, size);
createInfo.SetPaging(TChunkCreateInfo::EPaged);
RChunk chunk;
test_KErrNone(chunk.Create(createInfo));
TUint8* array = chunk.Base();
TestUserData(pagemove, array, size, ETrue);
TestMovingRealtime(pagemove, array, size, NULL, EFalse, ETrue);
TestPageTableDiscard(pagemove, array, size);
TestCommitDecommit(pagemove, chunk);
chunk.Close();
test.Next(_L("Attempting to move demand paged coarse global user data pages"));
const TInt sizeCoarse = 1 << 20; // Make this chunk multiple of 1MB so it is a coarse memory object on FMM
TChunkCreateInfo createInfoCoarse;
createInfoCoarse.SetDisconnected(0, sizeCoarse, sizeCoarse);
createInfoCoarse.SetGlobal(_L("Dave"));
createInfoCoarse.SetPaging(TChunkCreateInfo::EPaged);
RChunk chunkCoarse;
test_KErrNone(chunkCoarse.Create(createInfoCoarse));
array = chunkCoarse.Base();
TestUserData(pagemove, array, sizeCoarse, ETrue);
TestMovingRealtime(pagemove, array, sizeCoarse, NULL, EFalse, ETrue);
TestPageTableDiscard(pagemove, array, sizeCoarse);
TestCommitDecommit(pagemove, chunkCoarse);
chunkCoarse.Close();
}
test.Next(_L("Attempting to move DLL writable static data pages"));
{
const TInt size=16384;
TUint8* array = DllWsd::Address();
TestUserData(pagemove, array, size);
}
test.Next(_L("Attempting to move user self-mod code chunk page when IMB'ing and executing"));
RChunk codeChunk;
test_KErrNone(codeChunk.CreateLocalCode(PageSize,PageSize));
TestMovingCodeChunk(pagemove, codeChunk, EFalse);
codeChunk.Close();
if (gDataPagingSupported)
{
test.Next(_L("Attempting to move paged user self-mod code chunk page when IMB'ing and executing"));
TChunkCreateInfo createInfo;
createInfo.SetCode(PageSize, PageSize);
createInfo.SetPaging(TChunkCreateInfo::EPaged);
RChunk pagedCodeChunk;
test_KErrNone(pagedCodeChunk.Create(createInfo));
TestMovingCodeChunk(pagemove, pagedCodeChunk, ETrue);
pagedCodeChunk.Close();
}
test.Next(_L("Attempting to move RAM drive"));
if ((MemModelAttributes()&EMemModelTypeMask) == EMemModelTypeMultiple)
{
for (TInt i=0; i<Repitions; i++)
test_KErrNone(pagemove.TryMovingUserPage((TAny*)0xA0000000));
}
else if ((MemModelAttributes()&EMemModelTypeMask) == EMemModelTypeMoving)
{
for (TInt i=0; i<Repitions; i++)
test_KErrNone(pagemove.TryMovingUserPage((TAny*)0x40000000));
}
else if ((MemModelAttributes()&EMemModelTypeMask) == EMemModelTypeFlexible)
{
// do nothing, RAM drive is not special
}
else
{
test.Printf(_L("Don't know where the RAM drive is!"));
test(0);
}
#if 0
test.Next(_L("Attempting to move kernel heap pages"));
for (TInt i=0; i<Repitions; i++)
test_KErrNone(pagemove.TryMovingKHeap());
#endif
if ((MemModelAttributes()&EMemModelTypeMask) != EMemModelTypeFlexible)
{// Only the moving and multiple memory models move kernel stack pages.
test.Next(_L("Attempting to move kernel stack pages"));
for (TInt i=0; i<Repitions; i++)
test_KErrNone(pagemove.TryMovingKStack());
}
test.Next(_L("Attempting to move ROM pages"));
TestMovingRom(pagemove);
test.Next(_L("Attempting to move kernel code pages"));
for (TInt i=0; i<Repitions; i++)
test_KErrNone(pagemove.TryMovingKCode());
test.Next(_L("Attempting to move regular code pages"));
TestMovingCode(pagemove, RamLoadedFunction);
TestMovingRealtime(pagemove, NULL, 0, RamLoadedFunction, ETrue, EFalse);
if (gCodePagingSupported)
{
test.Next(_L("Attempting to move demand paged code pages"));
TestMovingCode(pagemove, DllTestFunction, ETrue);
TestMovingRealtime(pagemove, NULL, 0, DllTestFunction, ETrue, ETrue);
}
/* Setup CodeModifier Test Driver */
StartCodeModifierDriver();
test(KErrNone==Device.InitialiseCodeModifier(/* Max break points */ 5 ));
test.Next(_L("Attempting to move code page being modified\n"));
test_KErrNone(TestCodeModification(pagemove));
test.Next(_L("Attempting to move code (async) while page being modified"));
test_KErrNone(TestCodeModificationAsync(pagemove));
StopCodeModifierDriver();
test.Next(_L("Attempting to move ROM Locale DLL Page"));
test_KErrNone(E32TestLocale(1));
test.Next(_L("Attempting to move RAM Locale DLL Page"));
test_KErrNone(E32TestLocale(0));
test.Next(_L("Close test LDD"));
pagemove.Close();
User::FreeLogicalDevice(KLddFileName);
test.End();
return(KErrNone);
}
void testUS(const TLocale& aLocale)
{
test.Printf(_L("Test US\n"));
test(aLocale.CountryCode()==1);
test(aLocale.DateFormat()==EDateAmerican);
test(aLocale.TimeFormat()==ETime12);
test(aLocale.CurrencySymbolPosition()==ELocaleBefore);
test(aLocale.CurrencySpaceBetween()==FALSE);
test(aLocale.CurrencyDecimalPlaces()==2);
test(aLocale.CurrencyNegativeInBrackets()==EFalse);
test(aLocale.CurrencyTriadsAllowed()==TRUE);
test(aLocale.ThousandsSeparator()==',');
test(aLocale.DecimalSeparator()=='.');
test(aLocale.DateSeparator(0)==0);
test(aLocale.DateSeparator(1)=='/');
test(aLocale.DateSeparator(2)=='/');
test(aLocale.DateSeparator(3)==0);
test(aLocale.TimeSeparator(0)==0);
test(aLocale.TimeSeparator(1)==':');
test(aLocale.TimeSeparator(2)==':');
test(aLocale.TimeSeparator(3)==0);
test(aLocale.AmPmSymbolPosition()==TRUE);
test(aLocale.AmPmSpaceBetween()==TRUE);
test(aLocale.HomeDaylightSavingZone()==EDstNorthern);
test(aLocale.WorkDays()==0x1f);
test(aLocale.StartOfWeek()==ESunday);
test(aLocale.ClockFormat()==EClockAnalog);
test(aLocale.UnitsGeneral()==EUnitsImperial);
test(aLocale.UnitsDistanceShort()==EUnitsImperial);
test(aLocale.UnitsDistanceLong()==EUnitsImperial);
}
void testUK(const TLocale& aLocale)
{
//#ifdef __WINS__
test(aLocale.CountryCode()==44);
test(aLocale.DateFormat()==EDateEuropean);
test(aLocale.TimeFormat()==ETime12);
test(aLocale.CurrencySymbolPosition()==ELocaleBefore);
test(aLocale.CurrencySpaceBetween()==FALSE);
test(aLocale.CurrencyDecimalPlaces()==2);
test(aLocale.CurrencyNegativeInBrackets()==EFalse);
test(aLocale.CurrencyTriadsAllowed()==TRUE);
test(aLocale.ThousandsSeparator()==',');
test(aLocale.DecimalSeparator()=='.');
test(aLocale.DateSeparator(0)==0);
test(aLocale.DateSeparator(1)=='/');
test(aLocale.DateSeparator(2)=='/');
test(aLocale.DateSeparator(3)==0);
test(aLocale.TimeSeparator(0)==0);
test(aLocale.TimeSeparator(1)==':');
test(aLocale.TimeSeparator(2)==':');
test(aLocale.TimeSeparator(3)==0);
test(aLocale.AmPmSymbolPosition()==TRUE);
test(aLocale.AmPmSpaceBetween()==TRUE);
test(aLocale.HomeDaylightSavingZone()==EDstEuropean);
test(aLocale.WorkDays()==0x1f);
test(aLocale.StartOfWeek()==EMonday);
test(aLocale.ClockFormat()==EClockAnalog);
test(aLocale.UnitsGeneral()==EUnitsImperial);
test(aLocale.UnitsDistanceShort()==EUnitsImperial);
test(aLocale.UnitsDistanceLong()==EUnitsImperial);
//#endif
}
void testChangeLocale(TInt isrom)
{
TLocale locale;
#ifdef __WINS__
//We get a power-change notification 1 second after switch-on
//So we wait for a second on WINS.
//Should we fix this bug??
User::After(1000000);
#endif
RChangeNotifier notifier;
TInt res=notifier.Create();
test(res==KErrNone);
TRequestStatus stat;
res=notifier.Logon(stat);
test(res==KErrNone);
//initial pattern of stat is already tested by t_chnot
res=notifier.Logon(stat);
test(res==KErrNone);
test(stat==KRequestPending);
if (isrom == 0)
{
test.Printf(_L("Change to RAM US Locale\n"));
res=UserSvr::ChangeLocale(ELOCLUS);
}
else
{
test.Printf(_L("Change to ROM US Locale\n"));
res=UserSvr::ChangeLocale(ELOCLUS_ROM);
}
test.Printf(_L("res=%d\n"),res);
test(res==KErrNone);
test(stat.Int() & EChangesLocale);
res=notifier.Logon(stat);
test(res==KErrNone);
test(stat==KRequestPending);
locale.Refresh();
testUS(locale);
}
LOCAL_C void LocaleLanguageGet(SLocaleLanguage& locale)
{
TPckg<SLocaleLanguage> localeLanguageBuf(locale);
TInt r = RProperty::Get(KUidSystemCategory, KLocaleLanguageKey, localeLanguageBuf);
test(r == KErrNone || r == KErrNotFound);
}
LOCAL_C TInt E32TestLocale(TInt isrom)
{
TInt r;
TAny *LocaleAddr;
TLocale locale;
/* Setup the US Locale DLL and ensure the Locale got modified (testUS) */
testChangeLocale(isrom);
/* Now get a pointer to some data in the DLL. This will be used to move a
** page from the dll
*/
SLocaleLanguage localeLanguage;
LocaleLanguageGet(localeLanguage);
LocaleAddr = (TAny *) localeLanguage.iDateSuffixTable;
test(LocaleAddr != NULL);
RPageMove pagemove;
r=pagemove.Open();
test_KErrNone(r);
r=pagemove.TryMovingLocaleDll(LocaleAddr);
if (isrom == 0)
{
test_KErrNone(r);
}
else
{
// When the locale is in rom it is in the unpaged part of rom and
// Epoc::LinearToPhysical() won't be able to find the address.
test_Equal(KErrArgument, r)
}
test.Printf(_L("Locale Test: Page move done\n"));
/* Test US again. The kernel should have cached the locale informaton, so this will not
* really be testing the pagmove.
*/
locale.Refresh();
testUS(locale);
/* Reload the Default Locale */
test.Printf(_L("Locale Test: Change to UK Default\n"));
r=UserSvr::ChangeLocale(ELOCL_DEFAULT);
test(r==KErrNone);
locale.Refresh();
testUK(locale);
/* This will ACTUALLY test the page which was moved by making the kernel reload the Locale
* information from the DLL.
*/
if (isrom == 0)
{
test.Printf(_L("RAM Locale Test: Change to US Again\n"));
r=UserSvr::ChangeLocale(ELOCLUS);
}
else
{
test.Printf(_L("ROM Locale Test: Change to US Again\n"));
r=UserSvr::ChangeLocale(ELOCLUS_ROM);
}
test(r==KErrNone);
locale.Refresh();
testUS(locale);
/* Reset the Locale to the default */
r=UserSvr::ChangeLocale(ELOCL_DEFAULT);
test(r==KErrNone);
locale.Refresh();
testUK(locale);
return(KErrNone);
}
LOCAL_C void StartCodeModifierDriver()
{
test.Printf(_L("Start CodeModifier Driver\n"));
TInt r = User::LoadLogicalDevice(KCodeModifierName);
test( r==KErrNone || r==KErrAlreadyExists);
if((r = Device.Open())!=KErrNone)
{
User::FreeLogicalDevice(KCodeModifierName);
test.Printf(_L("Could not open LDD"));
test(0);
}
}
LOCAL_C void StopCodeModifierDriver()
{
test.Printf(_L("Stop Code Modifier Driver\n"));
test(KErrNone==Device.CloseCodeModifier());
Device.Close();
User::FreeLogicalDevice(KCodeModifierName);
}
LOCAL_C void TestCodeSetupDrive(RThread &thread)
{
/* The CodeModifier driver (look in ../debug/d_codemodifier) takes two threads, we just use the
** first one */
test(KErrNone==Device.ThreadId(0, thread.Id()));
}
LOCAL_C TUint GetCodeData(TInt *CodePtr, TInt& Ignore, TInt& FirstJump, TInt& SecondJump)
{
TUint ModAddr;
Ignore = *CodePtr++;
ModAddr = (TUint)CodePtr;
FirstJump = *CodePtr++;
SecondJump = *CodePtr++;
return ModAddr;
}
LOCAL_C TInt TestCodeModification(RPageMove &pagemove)
{
TInt Ignore;
TUint ModAddr;
TInt FirstJump;
TInt SecondJump;
RThread thread;
ModAddr = GetCodeData((TInt *)TestCodeModFunc, Ignore, FirstJump, SecondJump);
test.Printf(_L("User Test code Returns = %d\n"), TestCodeModFunc());
test.Printf(_L("Ignore = %x First Jump = %x Second = %x \n"), Ignore, FirstJump, SecondJump);
TestCodeSetupDrive(thread);
for (TInt i=0; i<Repitions * 10; i++)
{
TInt r=Device.WriteCode(0, ModAddr,SecondJump,sizeof(TInt));
test_KErrNone(r);
r = TestCodeModFunc();
test (2 == r);
test_KErrNone(pagemove.TryMovingUserPage((TAny*)TestCodeModFunc));
r = Device.RestoreCode(0, ModAddr);
test_KErrNone(r);
r = TestCodeModFunc();
test (1 == r);
test_KErrNone(pagemove.TryMovingUserPage((TAny*)TestCodeModFunc));
}
test.Printf(_L("User Test code = %d\n"), TestCodeModFunc());
return KErrNone;
}
LOCAL_C int TestCodeAsync(TAny *NotUsed)
{
TInt Ignore;
TUint ModAddr;
TInt FirstJump;
TInt SecondJump;
ModAddr = GetCodeData((TInt *)TestCodeModFunc, Ignore, FirstJump, SecondJump);
FOREVER
{
TInt r = Device.WriteCode(0, ModAddr,SecondJump,sizeof(TInt));
test_KErrNone(r);
r = TestCodeModFunc();
test (2 == r);
r = Device.RestoreCode(0, ModAddr);
test_KErrNone(r);
r = TestCodeModFunc();
test (1 == r);
User::AfterHighRes(10);
}
}
/*
* Creates a Thread that modifies its code in a tight loop while the main
* thread moves the functions page around
*/
LOCAL_C TInt TestCodeModificationAsync(RPageMove& pagemove)
{
TInt ret;
RThread CodeThread;
TRequestStatus s;
/* Create the Thread to modify the code segment */
test_KErrNone(CodeThread.Create(_L("TestCodeAsync"), TestCodeAsync, KDefaultStackSize, NULL, NULL));
CodeThread.Logon(s);
CodeThread.SetPriority(EPriorityMore);
CodeThread.Resume();
TestCodeSetupDrive(CodeThread);
/* Loop trying to move the code page while the thread (CodeThread) modifies it */
for (TInt i=0; i<Repitions; i++)
{
test_KErrNone(pagemove.TryMovingUserPage((TAny*)TestCodeModFunc));
}
CodeThread.Kill(KErrNone);
User::WaitForRequest(s);
test_Equal(EExitKill, CodeThread.ExitType());
test_KErrNone(CodeThread.ExitReason());
CodeThread.Close();
ret = TestCodeModFunc();
test(ret == 1 || ret == 2);
return KErrNone;
}