kerneltest/e32test/nkernsa/fastmutex.cpp
changeset 0 a41df078684a
child 90 947f0dc9f7a8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kerneltest/e32test/nkernsa/fastmutex.cpp	Mon Oct 19 15:55:17 2009 +0100
@@ -0,0 +1,1281 @@
+// 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\nkernsa\fastmutex.cpp
+// 
+//
+
+#include <nktest/nkutils.h>
+
+const TInt KReadCount = 100000;
+//const TInt KReadCount = 2000000;
+#ifdef __CPU_ARM
+const TUint32 KTickLimit = (KReadCount>100000) ? (((TUint32)KReadCount)/10*18) : 180000u;
+#else
+const TUint32 KTickLimit = (KReadCount>100000) ? (((TUint32)KReadCount)/10*6) : 60000u;
+#endif
+
+class NFastMutexX
+	{
+public:
+	TInt iRefCount;
+	NFastMutex* iMutex;
+public:
+	NFastMutexX();
+	~NFastMutexX();
+	void Create();
+	TBool Open();
+	TBool Close();
+	TBool Wait();
+	TBool Signal();
+	TBool WaitFull();
+	TBool SignalFull();
+	};
+
+NFastMutexX::NFastMutexX()
+	:	iRefCount(0), iMutex(0)
+	{}
+
+void NFastMutexX::Create()
+	{
+	iMutex = new NFastMutex;
+	TEST_OOM(iMutex);
+	__e32_atomic_store_rel32(&iRefCount, 1);
+	}
+
+NFastMutexX::~NFastMutexX()
+	{
+	TEST_RESULT2(iRefCount==0, "Bad mutex ref count %d %08x", iRefCount, this);
+	memset(this, 0xbf, sizeof(*this));
+	}
+
+TBool NFastMutexX::Open()
+	{
+	return __e32_atomic_tas_ord32(&iRefCount, 1, 1, 0) >  0;
+	}
+
+TBool NFastMutexX::Close()
+	{
+	TInt r = __e32_atomic_tas_ord32(&iRefCount, 1, -1, 0);
+	if (r==1)
+		{
+		memset(iMutex, 0xbf, sizeof(NFastMutex));
+		delete iMutex;
+		iMutex = 0;
+		}
+	return r==1;
+	}
+
+TBool NFastMutexX::Wait()
+	{
+	if (Open())
+		{
+		NKern::FMWait(iMutex);
+		return TRUE;
+		}
+	return FALSE;
+	}
+
+TBool NFastMutexX::Signal()
+	{
+	NKern::FMSignal(iMutex);
+	return Close();
+	}
+
+TBool NFastMutexX::WaitFull()
+	{
+	if (Open())
+		{
+		FMWaitFull(iMutex);
+		return TRUE;
+		}
+	return FALSE;
+	}
+
+TBool NFastMutexX::SignalFull()
+	{
+	FMSignalFull(iMutex);
+	return Close();
+	}
+
+void FMTest0()
+	{
+	TEST_PRINT("Testing non-contention case");
+
+	NFastMutex m;
+
+	TEST_RESULT(!m.HeldByCurrentThread(), "Mutex held by current thread");
+	TEST_RESULT(!NKern::HeldFastMutex(), "Current thread holds a fast mutex");
+
+	NKern::FMWait(&m);
+
+	TEST_RESULT(m.HeldByCurrentThread(), "Mutex not held by current thread");
+	TEST_RESULT(NKern::HeldFastMutex()==&m, "HeldFastMutex() incorrect");
+
+	NKern::FMSignal(&m);
+
+	TEST_RESULT(!m.HeldByCurrentThread(), "Mutex held by current thread");
+	TEST_RESULT(!NKern::HeldFastMutex(), "Current thread holds a fast mutex");
+	}
+
+struct SFMTest1Info
+	{
+	NFastMutex iMutex;
+	volatile TUint32* iBlock;
+	TInt iBlockSize;	// words
+	TInt iPriorityThreshold;
+	volatile TBool iStop;
+	NThread* iThreads[3*KMaxCpus];
+	};
+
+void FMTest1Thread(TAny* a)
+	{
+	SFMTest1Info& info = *(SFMTest1Info*)a;
+	NThread* pC = NKern::CurrentThread();
+	TUint32 seed[2] = {(TUint32)pC, 0};
+	TBool wait = (pC->iPriority > info.iPriorityThreshold);
+	TBool thread0 = (pC==info.iThreads[0]);
+	TInt n = 0;
+	while (!info.iStop)
+		{
+		if (thread0)
+			NKern::ThreadSetPriority(pC, 11);
+		NKern::FMWait(&info.iMutex);
+		TBool ok = verify_block((TUint32*)info.iBlock, info.iBlockSize);
+		TEST_RESULT(ok, "Block corrupt");
+		++info.iBlock[0];
+		setup_block((TUint32*)info.iBlock, info.iBlockSize);
+		++n;
+		NKern::FMSignal(&info.iMutex);
+		if (wait)
+			{
+			TUint32 x = random(seed) & 1;
+			NKern::Sleep(x+1);
+			}
+		}
+	TEST_PRINT2("Thread %T ran %d times", pC, n);
+	}
+
+void FMTest1PInterfererThread(TAny* a)
+	{
+	SFMTest1Info& info = *(SFMTest1Info*)a;
+	NThread* pC = NKern::CurrentThread();
+	TEST_PRINT1("Thread %T start", pC);
+	TUint32 seed[2] = {(TUint32)pC, 0};
+	NThread* t0 = info.iThreads[0];
+	TInt n = 0;
+	while (!__e32_atomic_load_acq32(&info.iStop))
+		{
+		while (!__e32_atomic_load_acq32(&info.iStop) && t0->iPriority != 11)
+			__chill();
+		TUint32 x = random(seed) & 2047;
+		while(x)
+			{
+			__e32_atomic_add_ord32(&x, TUint32(-1));
+			}
+		if (__e32_atomic_load_acq32(&info.iStop))
+			break;
+		NKern::ThreadSetPriority(t0, 9);
+		++n;
+		}
+	TEST_PRINT2("Thread %T ran %d times", pC, n);
+	}
+
+void FMTest1()
+	{
+	TEST_PRINT("Testing mutual exclusion");
+
+	NFastSemaphore exitSem(0);
+	SFMTest1Info* pI = new SFMTest1Info;
+	TEST_OOM(pI);
+	memclr(pI, sizeof(SFMTest1Info));
+	pI->iBlockSize = 256;
+	pI->iBlock = (TUint32*)malloc(pI->iBlockSize*sizeof(TUint32));
+	TEST_OOM(pI->iBlock);
+	pI->iPriorityThreshold = 10;
+	pI->iBlock[0] = 0;
+	setup_block((TUint32*)pI->iBlock, pI->iBlockSize);
+	pI->iStop = FALSE;
+	TInt cpu;
+	TInt threads = 0;
+	for_each_cpu(cpu)
+		{
+		CreateThreadSignalOnExit("FMTest1H", &FMTest1Thread, 11, pI, 0, KSmallTimeslice, &exitSem, cpu);
+		CreateThreadSignalOnExit("FMTest1L0", &FMTest1Thread, 10, pI, 0, KSmallTimeslice, &exitSem, cpu);
+		CreateThreadSignalOnExit("FMTest1L1", &FMTest1Thread, 10, pI, 0, KSmallTimeslice, &exitSem, cpu);
+		threads += 3;
+		}
+	FOREVER
+		{
+		NKern::Sleep(1000);
+		TEST_PRINT1("%d", pI->iBlock[0]);
+		if (pI->iBlock[0] > 65536)
+			{
+			pI->iStop = TRUE;
+			break;
+			}
+		}
+	while (threads--)
+		NKern::FSWait(&exitSem);
+	TEST_PRINT1("Total iterations %d", pI->iBlock[0]);
+	free((TAny*)pI->iBlock);
+	free(pI);
+	}
+
+void FMTest1P()
+	{
+	TEST_PRINT("Testing priority change");
+	if (NKern::NumberOfCpus()==1)
+		return;
+
+	NFastSemaphore exitSem(0);
+	SFMTest1Info* pI = new SFMTest1Info;
+	TEST_OOM(pI);
+	memclr(pI, sizeof(SFMTest1Info));
+	TEST_PRINT1("Info@0x%08x", pI);
+	pI->iBlockSize = 256;
+	pI->iBlock = (TUint32*)malloc(pI->iBlockSize*sizeof(TUint32));
+	TEST_OOM(pI->iBlock);
+	pI->iPriorityThreshold = 9;
+	pI->iBlock[0] = 0;
+	setup_block((TUint32*)pI->iBlock, pI->iBlockSize);
+	pI->iStop = FALSE;
+	TInt cpu;
+	TInt threadCount = 0;
+	TInt pri = 9;
+	char name[16] = "FMTest1P.0";
+	for_each_cpu(cpu)
+		{
+		name[9] = (char)(threadCount + '0');
+		if (cpu==1)
+			pI->iThreads[threadCount] = CreateThreadSignalOnExit("FMTest1PInterferer", &FMTest1PInterfererThread, 12, pI, 0, KSmallTimeslice, &exitSem, 1);
+		else
+			pI->iThreads[threadCount] = CreateThreadSignalOnExit(name, &FMTest1Thread, pri, pI, 0, KSmallTimeslice, &exitSem, cpu);
+		pri = 10;
+		threadCount++;
+		}
+	TUint32 b0 = 0xffffffffu;
+	FOREVER
+		{
+		NKern::Sleep(1000);
+		TUint32 b = pI->iBlock[0];
+		TEST_PRINT1("%d", b);
+		if (b > 1048576)
+			{
+			pI->iStop = TRUE;
+			break;
+			}
+		if (b == b0)
+			{
+			__crash();
+			}
+		b0 = b;
+		}
+	while (threadCount--)
+		NKern::FSWait(&exitSem);
+	TEST_PRINT1("Total iterations %d", pI->iBlock[0]);
+	free((TAny*)pI->iBlock);
+	free(pI);
+	}
+
+struct SFMTest2InfoC
+	{
+	NFastMutex iMutex;
+	TInt iMax;
+	TBool iStop;
+	};
+
+struct SFMTest2InfoT
+	{
+	SFMTest2InfoC* iCommon;
+	TUint32 iMaxDelay;
+	TInt iIterations;
+	TUint32 iSpinTime;
+	TUint32 iBlockTimeMask;
+	TUint32 iBlockTimeOffset;
+	NThread* iThread;
+	union {
+		TUint8 iSpoiler;
+		TUint32 iDelayThreshold;
+		};
+	};
+
+TBool StopTest = FALSE;
+
+void FMTest2Thread(TAny* a)
+	{
+	SFMTest2InfoT& t = *(SFMTest2InfoT*)a;
+	SFMTest2InfoC& c = *t.iCommon;
+	NThreadBase* pC = NKern::CurrentThread();
+	TUint32 seed[2] = {(TUint32)pC, 0};
+	while (!c.iStop)
+		{
+		++t.iIterations;
+		if (t.iSpoiler)
+			{
+			nfcfspin(t.iSpinTime);
+			}
+		else
+			{
+			TUint32 initial = norm_fast_counter();
+			NKern::FMWait(&c.iMutex);
+			TUint32 final = norm_fast_counter();
+			TUint32 delay = final - initial;
+			if (delay > t.iMaxDelay)
+				{
+				t.iMaxDelay = delay;
+				__e32_atomic_add_ord32(&c.iMax, 1);
+				if (delay > t.iDelayThreshold)
+					__crash();
+				}
+			nfcfspin(t.iSpinTime);
+			NKern::FMSignal(&c.iMutex);
+			}
+		if (t.iBlockTimeMask)
+			{
+			TUint32 sleep = (random(seed) & t.iBlockTimeMask) + t.iBlockTimeOffset;
+			NKern::Sleep(sleep);
+			}
+		}
+	TEST_PRINT3("Thread %T %d iterations, max delay %d", pC, t.iIterations, t.iMaxDelay);
+	}
+
+SFMTest2InfoT* CreateFMTest2Thread(	const char* aName,
+									SFMTest2InfoC& aCommon,
+									TUint32 aSpinTime,
+									TUint32 aBlockTimeMask,
+									TUint32 aBlockTimeOffset,
+									TBool aSpoiler,
+									TInt aPri,
+									TInt aTimeslice,
+									NFastSemaphore& aExitSem,
+									TUint32 aCpu
+								  )
+	{
+	SFMTest2InfoT* ti = new SFMTest2InfoT;
+	TEST_OOM(ti);
+	ti->iCommon = &aCommon;
+	ti->iMaxDelay = 0;
+	ti->iDelayThreshold = 0xffffffffu;
+	ti->iIterations = 0;
+	ti->iSpinTime = aSpinTime;
+	ti->iBlockTimeMask = aBlockTimeMask;
+	ti->iBlockTimeOffset = aBlockTimeOffset;
+	ti->iSpoiler = (TUint8)aSpoiler;
+	ti->iThread = 0;
+
+	NThread* t = CreateUnresumedThreadSignalOnExit(aName, &FMTest2Thread, aPri, ti, 0, aTimeslice, &aExitSem, aCpu);
+	ti->iThread = t;
+	DEBUGPRINT("Thread at %08x, Info at %08x", t, ti);
+
+	return ti;
+	}
+
+extern void DebugPrint(const char*, int);
+void FMTest2()
+	{
+	TEST_PRINT("Testing priority inheritance");
+
+	NFastSemaphore exitSem(0);
+	SFMTest2InfoC common;
+	common.iMax = 0;
+	common.iStop = FALSE;
+	TInt cpu;
+	TInt threads = 0;
+	SFMTest2InfoT* tinfo[32];
+	memset(tinfo, 0, sizeof(tinfo));
+	DEBUGPRINT("Common info at %08x", &common);
+	for_each_cpu(cpu)
+		{
+		tinfo[threads++] = CreateFMTest2Thread("FMTest2H", common, 500, 7, 7, FALSE, 60-cpu, KSmallTimeslice, exitSem, cpu);
+		tinfo[threads++] = CreateFMTest2Thread("FMTest2L", common, 500, 0, 0, FALSE, 11, KSmallTimeslice, exitSem, cpu);
+		tinfo[threads++] = CreateFMTest2Thread("FMTest2S", common, 10000, 15, 31, TRUE, 32, -1, exitSem, cpu);
+		}
+	tinfo[0]->iDelayThreshold = 0x300;
+	TInt max = 0;
+	TInt i;
+	TInt iter = 0;
+	for (i=0; i<threads; ++i)
+		{
+		NKern::ThreadResume(tinfo[i]->iThread);
+		}
+	FOREVER
+		{
+		NKern::Sleep(5000);
+		DebugPrint(".",1);	// only print one char since interrupts are disabled for entire time
+
+		TInt max_now = common.iMax;
+		if (max_now==max)
+			{
+			if (++iter==20)
+				break;
+			}
+		else
+			{
+			iter = 0;
+			max = max_now;
+			}
+		}
+	common.iStop = TRUE;
+	for (i=0; i<threads; ++i)
+		NKern::FSWait(&exitSem);
+	DebugPrint("\r\n",2);
+	for (i=0; i<threads; ++i)
+		{
+		TEST_PRINT3("%d: Iter %10d Max %10d", i, tinfo[i]->iIterations, tinfo[i]->iMaxDelay);
+		if (i==0)
+			{
+			TEST_RESULT(tinfo[0]->iMaxDelay < 700, "Thread 0 MaxDelay too high");
+			}
+		else if (i==3)
+			{
+			TEST_RESULT(tinfo[3]->iMaxDelay < 1200, "Thread 1 MaxDelay too high");
+			}
+		}
+	for (i=0; i<threads; ++i)
+		delete tinfo[i];
+	}
+
+struct SWriterInfo
+	{
+	void DoInOp(TUint aWhich);
+	void DoOutOp(TUint aWhich);
+
+	TUint32* iBuf[6];
+	TInt iWords;
+	volatile TUint32 iWrites;
+	volatile TUint32 iIn;
+	volatile TBool iStop;
+	NFastMutex* iM;
+	NFastMutexX* iMX;
+	TUint32 iInSeq;		// do nibble 0 followed by nibble 1 followed by nibble 2
+	TUint32 iOutSeq;	// 0=nothing, 1=mutex, 2=freeze, 3=CS, 4=mutex the long way
+						// 5 = mutexX, 6 = mutexX the long way, 7=join frozen group
+						// 8 = join mutex-holding group, 9=join idle group
+	TInt iFrz;
+	TInt iPriority;
+	TInt iTimeslice;
+	TInt iCpu;
+	NFastSemaphore iHandshake;
+	TUint64 iInitFastCounter;
+	TUint32 iFastCounterDelta;
+	NThread* volatile iIvThrd;
+
+#ifdef __SMP__
+	NThreadGroup* iGroup;
+#endif
+	};
+
+void SWriterInfo::DoInOp(TUint aWhich)
+	{
+	switch ((iInSeq>>(aWhich*4))&0xf)
+		{
+		case 0:	break;
+		case 1:	NKern::FMWait(iM); break;
+		case 2: iFrz=NKern::FreezeCpu(); break;
+		case 3: NKern::ThreadEnterCS(); break;
+		case 4: FMWaitFull(iM); break;
+		case 5: iMX->Wait(); break;
+		case 6: iMX->WaitFull(); break;
+#ifdef __SMP__
+		case 7:
+		case 8:
+		case 9:	NKern::JoinGroup(iGroup); break;
+#endif
+		}
+	}
+
+void SWriterInfo::DoOutOp(TUint aWhich)
+	{
+	switch ((iOutSeq>>(aWhich*4))&0xf)
+		{
+		case 0:	break;
+		case 1:	NKern::FMSignal(iM); break;
+		case 2: NKern::EndFreezeCpu(iFrz); break;
+		case 3: NKern::ThreadLeaveCS(); break;
+		case 4: FMSignalFull(iM); break;
+		case 5: iMX->Signal(); break;
+		case 6: iMX->SignalFull(); break;
+#ifdef __SMP__
+		case 7:
+		case 8:
+		case 9:	NKern::LeaveGroup(); break;
+#endif
+		}
+	}
+
+struct SReaderInfo
+	{
+	enum TTestType
+		{
+		ETimeslice,
+		ESuspend,
+		EKill,
+		EMigrate,
+		EInterlockedSuspend,
+		EInterlockedKill,
+		EInterlockedMigrate,
+		EMutexLifetime,
+		};
+
+	TUint32* iBuf[6];
+	TInt iWords;
+	volatile TUint32 iReads;
+	volatile TUint32 iFails[7];
+	volatile TBool iStop;
+	TUint32 iReadLimit;
+	NThread* volatile iWriter;
+	NThread* volatile iReader;
+	NThread* volatile iIvThrd;
+	NThread* iGroupThrd;
+	SWriterInfo* iWriterInfo;
+	TInt iTestType;
+	NFastSemaphore iExitSem;
+	volatile TUint32 iCapturedIn;
+	volatile TBool iSuspendResult;
+	};
+
+void WriterThread(TAny* a)
+	{
+	SWriterInfo& info = *(SWriterInfo*)a;
+//	TEST_PRINT(">WR");
+
+	while (!info.iStop)
+		{
+		NThread* t = (NThread*)__e32_atomic_swp_ord_ptr(&info.iIvThrd, 0);
+		if (t)
+			NKern::ThreadRequestSignal(t);
+		if (!info.iFastCounterDelta)
+			info.iInitFastCounter = fast_counter();
+		TInt n = ++info.iWrites;
+
+		info.DoInOp(0);
+
+		info.iBuf[0][0] = n;
+		setup_block_cpu(info.iBuf[0], info.iWords);
+
+		info.DoInOp(1);
+
+		info.iBuf[1][0] = n;
+		setup_block_cpu(info.iBuf[1], info.iWords);
+
+		info.DoInOp(2);
+
+		if (NKern::CurrentCpu() == info.iCpu)
+			++info.iIn;
+		info.iBuf[2][0] = n;
+		setup_block_cpu(info.iBuf[2], info.iWords);
+
+		info.DoOutOp(0);
+
+		info.iBuf[3][0] = n;
+		setup_block_cpu(info.iBuf[3], info.iWords);
+
+		info.DoOutOp(1);
+
+		info.iBuf[4][0] = n;
+		setup_block_cpu(info.iBuf[4], info.iWords);
+
+		info.DoOutOp(2);
+
+		info.iBuf[5][0] = n;
+		setup_block_cpu(info.iBuf[5], info.iWords);
+
+		if (!info.iFastCounterDelta)
+			info.iFastCounterDelta = (TUint32)(fast_counter() - info.iInitFastCounter);
+		if (NKern::CurrentCpu() != info.iCpu)
+			{
+			NKern::FSSignal(&info.iHandshake);
+			NKern::WaitForAnyRequest();
+			}
+		}
+//	TEST_PRINT("<WR");
+	}
+
+void ReaderThread(TAny* a)
+	{
+	SReaderInfo& info = *(SReaderInfo*)a;
+	SWriterInfo& winfo = *info.iWriterInfo;
+	TInt this_cpu = NKern::CurrentCpu();
+	NThread* pC = NKern::CurrentThread();
+	info.iReader = pC;
+//	TInt my_pri = pC->i_NThread_BasePri;
+	TBool create_writer = TRUE;
+	NKern::FSSetOwner(&winfo.iHandshake, 0);
+	NFastSemaphore exitSem(0);
+	TUint32 seed[2] = {0,7};
+	TUint32 modulus = 0;
+	TUint32 offset = 0;
+//	TEST_PRINT1(">RD%d",info.iTestType);
+
+	while (!info.iStop)
+		{
+		TInt i;
+		if (create_writer)
+			goto do_create_writer;
+		if (info.iTestType==SReaderInfo::EMigrate || info.iTestType==SReaderInfo::EInterlockedMigrate)
+			{
+			NKern::FSWait(&winfo.iHandshake);
+			}
+		for (i=0; i<6; ++i)
+			{
+			TInt cpu = verify_block_cpu_no_trace(info.iBuf[i], info.iWords);
+			if (cpu<0)
+				++info.iFails[i];
+			}
+		++info.iReads;
+		switch (info.iTestType)
+			{
+			case SReaderInfo::ETimeslice:
+				NKern::ThreadSetTimeslice(info.iWriter, (random(seed) % modulus + offset) );
+				NKern::YieldTimeslice();
+				break;
+			case SReaderInfo::ESuspend:
+				winfo.iIvThrd = info.iIvThrd;
+				NKern::ThreadResume(info.iWriter);
+				break;
+			case SReaderInfo::EKill:
+				NKern::FSWait(&exitSem);
+				create_writer = TRUE;
+				break;
+			case SReaderInfo::EMigrate:
+				NKern::ThreadSetCpuAffinity(info.iWriter, this_cpu);
+				if (info.iGroupThrd)
+					NKern::ThreadSetCpuAffinity(info.iGroupThrd, this_cpu);
+				NKern::ThreadRequestSignal(info.iIvThrd);
+				NKern::ThreadRequestSignal(info.iWriter);
+				break;
+			case SReaderInfo::EInterlockedSuspend:
+				NKern::WaitForAnyRequest();
+				NKern::FMWait(winfo.iM);
+				if (winfo.iIn != info.iCapturedIn && info.iSuspendResult)
+					++info.iFails[6];
+				winfo.iIvThrd = info.iIvThrd;
+				NKern::ThreadResume(info.iWriter, winfo.iM);
+				break;
+			case SReaderInfo::EInterlockedKill:
+				NKern::WaitForAnyRequest();
+				NKern::FSWait(&exitSem);
+				if (winfo.iIn != info.iCapturedIn)
+					++info.iFails[6];
+				create_writer = TRUE;
+				break;
+			case SReaderInfo::EInterlockedMigrate:
+				NKern::WaitForAnyRequest();
+				if (winfo.iIn != info.iCapturedIn)
+					++info.iFails[6];
+				NKern::ThreadSetCpuAffinity(info.iWriter, this_cpu);
+				if (info.iGroupThrd)
+					NKern::ThreadSetCpuAffinity(info.iGroupThrd, this_cpu);
+				NKern::ThreadRequestSignal(info.iIvThrd);
+				NKern::ThreadRequestSignal(info.iWriter);
+				break;
+			}
+do_create_writer:
+		if (create_writer)
+			{
+			create_writer = FALSE;
+			winfo.iCpu = this_cpu;
+			info.iWriter = CreateUnresumedThreadSignalOnExit("Writer", &WriterThread, winfo.iPriority, &winfo, 0, winfo.iTimeslice, &exitSem, this_cpu);
+			TEST_OOM(info.iWriter);
+			winfo.iIvThrd = info.iIvThrd;
+			NKern::ThreadResume(info.iWriter);
+			while (!winfo.iFastCounterDelta)
+				NKern::Sleep(1);
+			modulus = __fast_counter_to_timeslice_ticks(3*winfo.iFastCounterDelta);
+//			offset = __microseconds_to_timeslice_ticks(64);
+			offset = 1;
+			}
+		}
+	winfo.iStop = TRUE;
+	NKern::FSWait(&exitSem);
+//	TEST_PRINT1("<RD%d",info.iTestType);
+	}
+
+void InterventionThread(TAny* a)
+	{
+	SReaderInfo& info = *(SReaderInfo*)a;
+	SWriterInfo& winfo = *info.iWriterInfo;
+	TInt this_cpu = NKern::CurrentCpu();
+	TUint32 seed[2] = {1,0};
+	while (!winfo.iFastCounterDelta)
+		NKern::Sleep(1);
+	TUint32 modulus = 3*winfo.iFastCounterDelta;
+	TUint32 offset = TUint32(fast_counter_freq() / TUint64(100000));
+	NThread* w = info.iWriter;
+	TUint32 lw = 0;
+	TUint32 tc = NKern::TickCount();
+	NKern::FSSetOwner(&info.iExitSem, 0);
+
+	TEST_PRINT3(">IV%d %d %d", info.iTestType, modulus, offset);
+	FOREVER
+		{
+		if (this_cpu == winfo.iCpu)
+			{
+ 			NKern::Sleep(1);
+			}
+		else
+			{
+			TUint32 count = random(seed) % modulus;
+			count += offset;
+			fcfspin(count);
+			}
+		if (info.iReads >= info.iReadLimit)
+			{
+			info.iStop = TRUE;
+			winfo.iStop = TRUE;
+			NKern::FSWait(&info.iExitSem);
+			break;
+			}
+		if (winfo.iWrites >= lw + 3*info.iReadLimit)
+			{
+			lw += 3*info.iReadLimit;
+			TEST_PRINT1("#W=%d",winfo.iWrites);
+			}
+		TUint32 tc2 = NKern::TickCount();
+		if ( (tc2 - (tc+KTickLimit)) < 0x80000000 )
+			{
+			tc = tc2;
+			TEST_PRINT1("##W=%d",winfo.iWrites);
+			DumpMemory("WriterThread", w, 0x400);
+			}
+		switch (info.iTestType)
+			{
+			case SReaderInfo::ETimeslice:
+				break;
+			case SReaderInfo::ESuspend:
+				NKern::ThreadSuspend(info.iWriter, 1);
+	 			NKern::WaitForAnyRequest();
+				break;
+			case SReaderInfo::EKill:
+				{
+				w = info.iWriter;
+				info.iWriter = 0;
+				NKern::ThreadKill(w);
+	 			NKern::WaitForAnyRequest();
+				break;
+				}
+			case SReaderInfo::EMigrate:
+				NKern::ThreadSetCpuAffinity(info.iWriter, this_cpu);
+				if (info.iGroupThrd)
+					NKern::ThreadSetCpuAffinity(info.iGroupThrd, this_cpu);
+	 			NKern::WaitForAnyRequest();
+				break;
+			case SReaderInfo::EInterlockedSuspend:
+				{
+#if 0
+extern TLinAddr __LastIrqRet;
+extern TLinAddr __LastSSP;
+extern TLinAddr __SSTop;
+extern TUint32 __CaptureStack[1024];
+extern TLinAddr __InterruptedThread;
+extern TUint32 __CaptureThread[1024];
+#endif
+				NKern::FMWait(winfo.iM);
+				info.iCapturedIn = winfo.iIn;
+				info.iSuspendResult = NKern::ThreadSuspend(info.iWriter, 1);
+				NKern::FMSignal(winfo.iM);
+				NKern::ThreadRequestSignal(info.iReader);
+#if 0
+				NThread* pC = NKern::CurrentThread();
+				TUint32 tc0 = NKern::TickCount();
+				tc0+=1000;
+				FOREVER
+					{
+					TUint32 tc1 = NKern::TickCount();
+					if ((tc1-tc0)<0x80000000u)
+						{
+						DEBUGPRINT("__LastIrqRet = %08x", __LastIrqRet);
+						DEBUGPRINT("__LastSSP    = %08x", __LastSSP);
+						DEBUGPRINT("__SSTop      = %08x", __SSTop);
+
+						DumpMemory("WriterStack", __CaptureStack, __SSTop - __LastSSP);
+
+						DumpMemory("CaptureThread", __CaptureThread, sizeof(NThread));
+
+						DumpMemory("Writer", info.iWriter, sizeof(NThread));
+
+						DumpMemory("Reader", info.iReader, sizeof(NThread));
+
+						DumpMemory("SubSched0", &TheSubSchedulers[0], sizeof(TSubScheduler));
+						}
+					if (pC->iRequestSemaphore.iCount>0)
+						break;
+					}
+#endif
+	 			NKern::WaitForAnyRequest();
+				break;
+				}
+			case SReaderInfo::EInterlockedKill:
+				{
+				NKern::FMWait(winfo.iM);
+				info.iCapturedIn = winfo.iIn;
+				w = info.iWriter;
+				info.iWriter = 0;
+				NKern::ThreadKill(w, winfo.iM);
+				NKern::ThreadRequestSignal(info.iReader);
+	 			NKern::WaitForAnyRequest();
+				break;
+				}
+			case SReaderInfo::EInterlockedMigrate:
+				NKern::FMWait(winfo.iM);
+				info.iCapturedIn = winfo.iIn;
+				NKern::ThreadSetCpuAffinity(info.iWriter, this_cpu);
+				if (info.iGroupThrd)
+					NKern::ThreadSetCpuAffinity(info.iGroupThrd, this_cpu);
+				NKern::FMSignal(winfo.iM);
+				NKern::ThreadRequestSignal(info.iReader);
+	 			NKern::WaitForAnyRequest();
+				break;
+			}
+		}
+	TEST_PRINT1("<IV%d",info.iTestType);
+	}
+
+// State bits 0-7 show how many times timeslices are blocked
+// State bits 8-15 show how many times suspend/kill are blocked
+// State bits 16-23 show how many times migration is blocked
+// State bit 24 set if in CS when fast mutex held
+// State bit 25 set if CPU frozen when fast mutex held
+TUint32 UpdateState(TUint32 aS, TUint32 aOp, TBool aOut)
+	{
+	TUint32 x = 0;
+	if (aS & 0xff00)
+		x |= 0x01000000;
+	if (aS & 0xff0000)
+		x |= 0x02000000;
+	if (aOut)
+		{
+		switch (aOp)
+			{
+			case 0:
+			case 9:
+				return aS;
+			case 2:
+			case 7:
+			case 8:
+				return aS-0x010000;
+			case 3:
+				return aS-0x000100;
+			case 1:
+			case 4:
+				return aS-0x010101;
+			}
+		}
+	else
+		{
+		switch (aOp)
+			{
+			case 0:
+			case 9:
+				return aS;
+			case 2:
+			case 7:
+			case 8:
+				return aS+0x010000;
+			case 3:
+				return aS+0x000100;
+			case 1:
+			case 4:
+				return (aS+0x010101)|x;
+			}
+		}
+	return aS;
+	}
+
+void CheckResults(SReaderInfo& info)
+	{
+	SWriterInfo& winfo = *info.iWriterInfo;
+	TUint32 state[7];
+	char c[72];
+	memset(c, 32, sizeof(c)), c[71]=0;
+	state[0] = UpdateState(0, (winfo.iInSeq)&0xf, FALSE);
+	state[1] = UpdateState(state[0], (winfo.iInSeq>>4)&0xf, FALSE);
+	state[2] = UpdateState(state[1], (winfo.iInSeq>>8)&0xf, FALSE);
+	state[3] = UpdateState(state[2], (winfo.iOutSeq)&0xf, TRUE);
+	state[4] = UpdateState(state[3], (winfo.iOutSeq>>4)&0xf, TRUE);
+	state[5] = UpdateState(state[4], (winfo.iOutSeq>>8)&0xf, TRUE);
+	state[6] = (state[5] & 0xff000000) ^ 0x07000000;
+
+	TInt i;
+	for (i=0; i<6; ++i)
+		state[i] &= 0x00ffffff;
+
+	TEST_PRINT2("Reads %d Writes %d", info.iReads, winfo.iWrites);
+	for(i=0; i<6; ++i)
+		{
+		if (state[i] & 0xff00)
+			c[i*10] = 'S';
+		if (state[i] & 0xff0000)
+			c[i*10+1] = 'M';
+		if (state[i] & 0xff)
+			c[i*10+2] = 'T';
+		}
+	TEST_PRINT1("%s",c);
+	TEST_PRINT7("F0 %6d F1 %6d F2 %6d F3 %6d F4 %6d F5 %6d F6 %6d", info.iFails[0], info.iFails[1], info.iFails[2], info.iFails[3], info.iFails[4], info.iFails[5], info.iFails[6]);
+	memset(c, 32, sizeof(c)), c[71]=0;
+	TUint32 mask=0;
+	switch(info.iTestType)
+		{
+		case SReaderInfo::ETimeslice:			mask = 0x040000ff; break;
+		case SReaderInfo::ESuspend:				mask = 0x0400ff00; break;
+		case SReaderInfo::EKill:				mask = 0x0400ff00; break;
+		case SReaderInfo::EMigrate:				mask = 0x04ff0000; break;
+		case SReaderInfo::EInterlockedSuspend:	mask = 0x0400ff00; break;
+		case SReaderInfo::EInterlockedKill:		mask = 0x0100ff00; break;
+		case SReaderInfo::EInterlockedMigrate:	mask = 0x02ff0000; break;
+		}
+	TUint32 limit = info.iReads/10;
+	TInt fail=0;
+	for(i=0; i<7; ++i)
+		{
+		TBool bad = FALSE;
+		if (state[i] & mask)
+			bad = (info.iFails[i] > 0);
+		else
+			bad = (info.iFails[i] < limit);
+		if (bad)
+			{
+			++fail;
+			char* p = c+i*10+3;
+			*p++ = '-';
+			*p++ = '-';
+			*p++ = '-';
+			*p++ = '-';
+			*p++ = '-';
+			*p++ = '-';
+			}
+		}
+	if (fail)
+		{
+		c[0] = 'E';
+		c[1] = 'R';
+		c[2] = 'R';
+		TEST_PRINT1("%s",c);
+		TEST_RESULT(0,"FAILED");
+		}
+	}
+
+struct SGroupThreadInfo
+	{
+	TUint32 iInSeq;
+	TUint32 iRun;
+	};
+
+void GroupThread(TAny* a)
+	{
+	SGroupThreadInfo& info = *(SGroupThreadInfo*)a;
+	TInt i, frz;
+	NFastMutex mutex;
+	for (i = 0; i<3; ++i)
+		{
+		// Find the first nibble that asks for a group option
+		// and do what it asks for.
+		switch ((info.iInSeq>>(i*4))&0xf)
+			{
+			case 7:
+				frz = NKern::FreezeCpu();
+				NKern::WaitForAnyRequest();
+				NKern::EndFreezeCpu(frz);
+				return;
+			case 8:
+				NKern::FMWait(&mutex);
+				while (__e32_atomic_load_acq32(&info.iRun))
+					nfcfspin(10);
+				NKern::FMSignal(&mutex);
+				return;
+			}
+		}
+	// We weren't needed, but we have to wait to die anyway to avoid lifetime issues
+	NKern::WaitForAnyRequest();
+	}
+
+void DoRWTest(TInt aTestType, TUint32 aReadLimit, TUint32 aInSeq, TUint32 aOutSeq, TInt aRWCpu, TInt aICpu)
+	{
+	NFastMutex mutex;
+	SWriterInfo* winfo = new SWriterInfo;
+	TEST_OOM(winfo);
+	memclr(winfo, sizeof(SWriterInfo));
+	SReaderInfo* info = new SReaderInfo;
+	TEST_OOM(info);
+	memclr(info, sizeof(SReaderInfo));
+	SGroupThreadInfo* gtinfo = new SGroupThreadInfo;
+	TEST_OOM(gtinfo);
+	memclr(gtinfo, sizeof(SGroupThreadInfo));
+	TUint32 bufwords = 256;
+	TUint32* buf = (TUint32*)malloc(6 * bufwords * sizeof(TUint32));
+	TEST_OOM(buf);
+	memclr(buf, 6 * bufwords * sizeof(TUint32));
+	TInt i;
+	for (i=0; i<6; ++i)
+		{
+		info->iBuf[i] = buf + i * bufwords;
+		winfo->iBuf[i] = buf + i * bufwords;
+		}
+	winfo->iWords = bufwords;
+	winfo->iM = &mutex;
+	winfo->iInSeq = aInSeq;
+	winfo->iOutSeq = aOutSeq;
+	winfo->iPriority = 11;
+	winfo->iTimeslice = __microseconds_to_timeslice_ticks(10000);
+	winfo->iCpu = aRWCpu;
+
+	NFastSemaphore localExit(0);
+
+#ifdef __SMP__
+	NThreadGroup group;
+	SNThreadGroupCreateInfo ginfo;
+	ginfo.iCpuAffinity = aRWCpu;
+	TInt r = NKern::GroupCreate(&group, ginfo);
+	TEST_RESULT(r==KErrNone, "");
+	winfo->iGroup = &group;
+	gtinfo->iRun = 1;
+	gtinfo->iInSeq = aInSeq;
+	NThread* groupThrd = CreateThreadSignalOnExit("GroupThrd", &GroupThread, 1, gtinfo, 0, KSmallTimeslice, &localExit, aRWCpu, &group);
+	TEST_OOM(groupThrd);
+	info->iGroupThrd = groupThrd;
+	NKern::Sleep(100);
+#endif
+
+	info->iWords = bufwords;
+	info->iReadLimit = aReadLimit;
+	info->iWriterInfo = winfo;
+	info->iTestType = aTestType;
+
+	TInt rpri = (aTestType == SReaderInfo::ETimeslice) ? 11 : 10;
+	NThread* reader = CreateThreadSignalOnExit("Reader", &ReaderThread, rpri, info, 0, -1, &info->iExitSem, aRWCpu);
+	TEST_OOM(reader);
+	info->iReader = reader;
+	NKern::Sleep(10);
+	NThread* ivt = CreateThreadSignalOnExit("Intervention", &InterventionThread, 12, info, 0, KSmallTimeslice, &localExit, aICpu);
+	TEST_OOM(ivt);
+	info->iIvThrd = ivt;
+
+	NKern::FSWait(&localExit);
+#ifdef __SMP__
+	NKern::ThreadRequestSignal(groupThrd);
+#endif
+	__e32_atomic_store_rel32(&gtinfo->iRun, 0);
+	NKern::FSWait(&localExit);
+
+#ifdef __SMP__
+	NKern::GroupDestroy(&group);
+#endif
+
+	free(buf);
+
+	TEST_PRINT6("Type %d RL %d ISEQ %03x OSEQ %03x RWCPU %d ICPU %d", aTestType, aReadLimit, aInSeq, aOutSeq, aRWCpu, aICpu);
+	CheckResults(*info);
+
+	free(info);
+	free(winfo);
+	free(gtinfo);
+	}
+
+
+void TestFastMutex()
+	{
+	TEST_PRINT("Testing Fast Mutexes...");
+
+	FMTest0();
+	FMTest1();
+	FMTest1P();
+	FMTest2();
+	}
+
+void TestSuspendKillMigrate()
+	{
+	TEST_PRINT("Testing Suspend/Kill/Migrate...");
+
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x000, 0x000, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x010, 0x100, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x040, 0x400, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x132, 0x231, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x432, 0x234, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x310, 0x310, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x340, 0x340, 0, 1);
+
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x000, 0x000, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x010, 0x100, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x040, 0x400, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x132, 0x231, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x432, 0x234, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x310, 0x310, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x340, 0x340, 0, 1);
+
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x000, 0x000, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x010, 0x100, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x040, 0x400, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x132, 0x231, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x432, 0x234, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x310, 0x310, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x340, 0x340, 0, 1);
+
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x000, 0x000, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x132, 0x231, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x432, 0x234, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x020, 0x200, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x010, 0x100, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x040, 0x400, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x030, 0x300, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x432, 0x234, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x040, 0x400, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x010, 0x100, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x340, 0x340, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x310, 0x310, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x132, 0x231, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x040, 0x400, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x010, 0x100, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x432, 0x234, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x340, 0x340, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x132, 0x231, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x310, 0x310, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x040, 0x400, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x010, 0x100, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x310, 0x310, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x340, 0x340, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x132, 0x231, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x432, 0x234, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x120, 0x210, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x420, 0x240, 0, 1);
+
+#ifdef __SMP__
+	// Tests from above that involve freezing, except by joining a frozen group instead
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x137, 0x731, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x437, 0x734, 0, 1);
+
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x137, 0x731, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x437, 0x734, 0, 1);
+
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x137, 0x731, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x437, 0x734, 0, 1);
+
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x137, 0x731, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x437, 0x734, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x070, 0x700, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x437, 0x734, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x137, 0x731, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x437, 0x734, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x137, 0x731, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x137, 0x731, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x437, 0x734, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x170, 0x710, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x470, 0x740, 0, 1);
+
+	// Tests from above that involve freezing, except by joining a group with a mutex-holder instead
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x138, 0x831, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x438, 0x834, 0, 1);
+
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x138, 0x831, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x438, 0x834, 0, 1);
+
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x138, 0x831, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x438, 0x834, 0, 1);
+
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x138, 0x831, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x438, 0x834, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x080, 0x800, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x438, 0x834, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x138, 0x831, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x438, 0x834, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x138, 0x831, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x138, 0x831, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x438, 0x834, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x180, 0x810, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x480, 0x840, 0, 1);
+
+	// Tests from above that have a noop, except join a group that's doing nothing instead
+	// Most of these do "join group, other op, leave group, undo other op" - this is
+	// supposed to work, even though you can't *join* a group while frozen or holding a mutex
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x090, 0x900, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x019, 0x190, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x049, 0x490, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x319, 0x319, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x349, 0x349, 0, 1);
+
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x090, 0x900, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x019, 0x190, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x049, 0x490, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x319, 0x319, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x349, 0x349, 0, 1);
+
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x090, 0x900, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x019, 0x190, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x049, 0x490, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x319, 0x319, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x349, 0x349, 0, 1);
+
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x090, 0x900, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x029, 0x290, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x019, 0x190, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x049, 0x490, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x039, 0x390, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x049, 0x490, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x019, 0x190, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x349, 0x349, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x319, 0x319, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x049, 0x490, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x019, 0x190, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x349, 0x349, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x319, 0x319, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x049, 0x490, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x019, 0x190, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x319, 0x319, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x349, 0x349, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x129, 0x219, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x429, 0x249, 0, 1);
+
+	// Test freezing or acquiring a mutex while in a group that also does one of those things
+	// and then leave the group.
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x017, 0x170, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x018, 0x180, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x027, 0x270, 0, 1);
+	DoRWTest(SReaderInfo::ETimeslice,			KReadCount, 0x028, 0x280, 0, 1);
+
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x017, 0x170, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x018, 0x180, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x027, 0x270, 0, 1);
+	DoRWTest(SReaderInfo::ESuspend,				KReadCount, 0x028, 0x280, 0, 1);
+
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x017, 0x170, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x018, 0x180, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x027, 0x270, 0, 1);
+	DoRWTest(SReaderInfo::EKill,				KReadCount, 0x028, 0x280, 0, 1);
+
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x017, 0x170, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x018, 0x180, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x027, 0x270, 0, 1);
+	DoRWTest(SReaderInfo::EMigrate,				KReadCount, 0x028, 0x280, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x017, 0x170, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedSuspend,	KReadCount, 0x018, 0x180, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x017, 0x170, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedKill,		KReadCount, 0x018, 0x180, 0, 1);
+
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x017, 0x170, 0, 1);
+	DoRWTest(SReaderInfo::EInterlockedMigrate,	KReadCount, 0x018, 0x180, 0, 1);
+#endif
+}