diff -r 000000000000 -r 96e5fb8b040d kerneltest/e32test/benchmark/bm_ldd.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kerneltest/e32test/benchmark/bm_ldd.cpp Thu Dec 17 09:24:54 2009 +0200 @@ -0,0 +1,704 @@ +// Copyright (c) 2002-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 "k32bm.h" + +const TUint8 KMutexOrder = 0xf0; + +class DBMLDevice : public DLogicalDevice + { +public: + DBMLDevice(); + virtual TInt Install(); + virtual void GetCaps(TDes8& aDes) const; + virtual TInt Create(DLogicalChannelBase*& aChannel); + }; + +class DBMLChannel : public DLogicalChannelBase, public MBMIsr, public MBMInterruptLatencyIsr + { +public: + DBMLChannel(); + ~DBMLChannel(); + + virtual TInt DoCreate(TInt aUnit, const TDesC8* anInfo, const TVersion& aVer); + virtual TInt Request(TInt aFunction, TAny* a1, TAny* a2); + + DBMPChannel* PChannel() { return (DBMPChannel*) iPdd; } + +private: + static const TInt KBMDfcQThreadPriority; + static const TInt KBMKernelThreadPriority; + + static void Dfc(TAny*); + + virtual void Isr(TBMTicks now); + + TInt (DBMLChannel::*iRequestInterrupt)(); // Measurement specific RBMChannel::RequestInterrupt() implmentation + TInt RequestInterrupt(); // Default iRequestInterrupt() implementation + + TBMTicks (DBMLChannel::*iResult)(); // Measurement specific RBMChannel::Result() implmentation + TBMTicks Result(); // Default iResult() implementation + + TInt Start(RBMChannel::TMode); + + TInt StartInterruptLatency(); + virtual void InterruptLatencyIsr(TBMTicks latency); + + TInt StartKernelPreemptionLatency(); + static TInt KernelPreemptionLatencyThreadEntry(TAny* ptr); + void KernelPreemptionLatencyThread(); + + TInt StartUserPreemptionLatency(); + TBMTicks UserPreemptionLatencyResult(); // iResult() implementation + + TInt StartNTimerJitter(); + TInt RequestNTimerJitterInterrupt(); // iRequestInterrupt() implementation + static void NTimerJitterCallBack(TAny*); + + TInt StartTimerStampOverhead(); + TInt RequestTimerStampOverhead(); // iRequestInterrupt() implementation + + TInt SetAbsPrioirty(TInt aThreadHandle, TInt aNewPrio, TInt* aOldPrio); + + DMutex* iLock; // Shall be acquired by anyone who access the object's writable state. + + TBool iStarted; // ETrue when a particular sequence of measurements has been started + TBool iPendingInterruptRequest; // ETrue when an interrupt has been requested + + TDynamicDfcQue* iDfcQ; + TDfc iDfc; + + DThread* iKernelThread; // the kernel thread created by some benchmarks + DThread* iUserThread; // the user-side thread + DThread* iInterruptThread; // the thread signaled by DFC; if non-NULL either iKernelThread or iUserThread + + NTimer iNTimer; // the timer used in "NTimer jitter" benchmark + TBMTicks iOneNTimerTick; // number of high-resolution timer ticks in one NKern tick. + TInt iNTimerShotCount; // used in "NTimer jitter" to distinguish between the first and the second shots + + TBMTicks iTime; + TBMTicks iTimerPeriod; // period of high-resolution timer in ticks + + NFastSemaphore* iKernelThreadExitSemaphore; + + void Lock() + { + NKern::ThreadEnterCS(); + Kern::MutexWait(*iLock); + } + void Unlock() + { + Kern::MutexSignal(*iLock); + NKern::ThreadLeaveCS(); + } + + TBMTicks Delta(TBMTicks aT0, TBMTicks aT1) + { + return (aT0 <= aT1) ? (aT1 - aT0) : iTimerPeriod - (aT0 - aT1); + } + }; + +_LIT(KBMLChannelLit, "BMLChannel"); + +const TInt DBMLChannel::KBMDfcQThreadPriority = KBMLDDHighPriority; +const TInt DBMLChannel::KBMKernelThreadPriority = KBMLDDMidPriority; + + + +DECLARE_STANDARD_LDD() +// +// Create a new device +// + { + __ASSERT_CRITICAL; + return new DBMLDevice; + } + +DBMLDevice::DBMLDevice() +// +// Constructor +// + { + //iUnitsMask=0; + iVersion = TVersion(1,0,1); + iParseMask = KDeviceAllowPhysicalDevice; + } + +TInt DBMLDevice::Install() +// +// Install the device driver. +// + { + TInt r = SetName(&KBMLdName); + return r; + } + +void DBMLDevice::GetCaps(TDes8&) const +// +// Return the Comm capabilities. +// + { + } + +TInt DBMLDevice::Create(DLogicalChannelBase*& aChannel) +// +// Create a channel on the device. +// + { + __ASSERT_CRITICAL; + aChannel = new DBMLChannel; + return aChannel ? KErrNone : KErrNoMemory; + } + +DBMLChannel::DBMLChannel() : + iDfc(0, this, 0, 0), + iNTimer(NULL, this) + { + // iDfcQueue = NULL; + // iStarted = EFalse; + // iPendingInterruptRequest = EFalse; + // iKernelThread = NULL; + } + +TInt DBMLChannel::DoCreate(TInt /*aUnit*/, const TDesC8* /* aInfo*/ , const TVersion& aVer) +// +// Create the channel from the passed info. +// + { + __ASSERT_CRITICAL; + if (!Kern::QueryVersionSupported(TVersion(1,0,1),aVer)) + return KErrNotSupported; + TInt r = Kern::MutexCreate(iLock, KBMLChannelLit, KMutexOrder); + if (r != KErrNone) + { + return r; + } + iTimerPeriod = PChannel()->TimerPeriod(); + // Calculate the number of high-resolution timer ticks in one NKern tick + // deviding the number of high-resolution timer ticks in one second by the + // number of NKern ticks in one second. + // + iOneNTimerTick = PChannel()->TimerNsToTicks(BMSecondsToNs(1))/NKern::TimerTicks(1000); + return KErrNone; + } + +DBMLChannel::~DBMLChannel() +// Called on a channel close. Note that if the PDD channel create failed +// then the DoCreate() call will not have been made so don't assume anything +// about non-ctor initialisation of members. + { + if (iLock) + iLock->Close(0); + + if (iPendingInterruptRequest) + { + PChannel()->CancelInterrupt(); + iDfc.Cancel(); + } + + if (iDfcQ) + { + iDfcQ->Destroy(); + } + + if (iKernelThread) + { + NFastSemaphore exitSemaphore; + exitSemaphore.iOwningThread = NKern::CurrentThread(); + iKernelThreadExitSemaphore = &exitSemaphore; + NKern::ThreadRequestSignal(&iKernelThread->iNThread); + NKern::FSWait(&exitSemaphore); + } + } + +void DBMLChannel::Dfc(TAny* ptr) + { + DBMLChannel* lCh = (DBMLChannel*) ptr; + BM_ASSERT(lCh->iPendingInterruptRequest); + BM_ASSERT(lCh->iInterruptThread); + NKern::ThreadRequestSignal(&lCh->iInterruptThread->iNThread); + lCh->iPendingInterruptRequest = EFalse; + } + +// +// Default DBMLChannel::iRequestInterrupt implementation +// +TInt DBMLChannel::RequestInterrupt() + { + if (!iStarted) + { + return KErrNotReady; + } + if (iPendingInterruptRequest) + { + return KErrInUse; + } + iPendingInterruptRequest = ETrue; + PChannel()->RequestInterrupt(); + return KErrNone; + } + +// +// Default DBMLChannel::iResult implementation +// +TBMTicks DBMLChannel::Result() + { + return iTime; + } + +void DBMLChannel::Isr(TBMTicks aNow) + { + // + // Store the ISR entry time and queue a DFC. + // + iTime = aNow; + iDfc.Add(); + } + +// +// "INTERRUPT LATENCY" +// +// SCENARIO: +// +// A user thread requests an interrupt (RBMChannel::RequestInterrupt()) and waits at User::WaitForAnyRequest() +// (RBMChannel::Result()). +// When the interrupt occurs DBMLChannel::InterruptLatencyIsr() stores the interrupt latency +// provided by LDD, in DBMLChannel::iTime and queues a DFC (DBMLChannel::iDfc, DBMLChannel::Dfc()) +// which in its turn signals the user thread. +// + +TInt DBMLChannel::StartInterruptLatency() + { + if (iStarted) + { + return KErrInUse; + } + TInt r = PChannel()->BindInterrupt((MBMInterruptLatencyIsr*) this); + if (r != KErrNone) + { + return r; + } + // Use the default iRequestInterrupt() implmentation + iRequestInterrupt = &DBMLChannel::RequestInterrupt; + // Use the default iResult() implmentation + iResult = &DBMLChannel::Result; + iInterruptThread = &Kern::CurrentThread(); + iStarted = ETrue; + return KErrNone; + } + +void DBMLChannel::InterruptLatencyIsr(TBMTicks aLatency) + { + iTime = aLatency; + iDfc.Add(); + } + +// +// "KERNEL THREAD PREEMPTION LATENCY" +// +// SCENARIO: +// +// A new kernel thread is created at the beginning of a sequence of measurements +// (DBMLChannel::StartKernelPreemptionLatency()). The kernel thread waits at Kern::WaitForAnyRequest() +// (DBMLChannel::KernelPreemptionLatencyThread()). +// The user thread requests an interrupt (RBMChannel::RequestInterrupt()) and waits at User::WaitForAnyRequest() +// (RBMChannel::Result()). +// When the interrupt occurs DBMLChannel::Isr() stores the ISR entry time, provided by LDD +// in DBMLChannel::iTime and queues a DFC (DBMLChannel::iDfc, DBMLChannel::Dfc()) which, in its turn, +// signals the kernel thread. +// The kernel thread, when awaken, calculates the latency as the difference between the ISR entry time +// and the current time and finally signals the user thread. +// + +TInt DBMLChannel::StartKernelPreemptionLatency() + { + if (iStarted) + { + return KErrInUse; + } + TInt r = PChannel()->BindInterrupt((MBMIsr*) this); + if (r != KErrNone) + { + return r; + } + { + SThreadCreateInfo info; + info.iType = EThreadSupervisor; + info.iFunction = DBMLChannel::KernelPreemptionLatencyThreadEntry; + info.iPtr = this; + info.iSupervisorStack = NULL; + info.iSupervisorStackSize = 0; + info.iInitialThreadPriority = KBMKernelThreadPriority; + info.iName.Set(KBMLChannelLit); + info.iTotalSize = sizeof(info); + r = Kern::ThreadCreate(info); + if (r != KErrNone) + { + return r; + } + iKernelThread = (DThread*) info.iHandle; + } + + iUserThread = &Kern::CurrentThread(); + // Use the default iRequestInterrupt() implmentation + iRequestInterrupt = &DBMLChannel::RequestInterrupt; + // Use the default iResult() implmentation + iResult = &DBMLChannel::Result; + iInterruptThread = iKernelThread; + iStarted = ETrue; + + Kern::ThreadResume(*iKernelThread); + + return KErrNone; + } + +TInt DBMLChannel::KernelPreemptionLatencyThreadEntry(TAny* ptr) + { + DBMLChannel* lCh = (DBMLChannel*) ptr; + lCh->KernelPreemptionLatencyThread(); + BM_ASSERT(0); + return 0; + } + +void DBMLChannel::KernelPreemptionLatencyThread() + { + for(;;) + { + NKern::WaitForAnyRequest(); + + if(iKernelThreadExitSemaphore) + break; + + TBMTicks now = PChannel()->TimerStamp(); + iTime = Delta(iTime, now); + BM_ASSERT(iUserThread); + NKern::ThreadRequestSignal(&iUserThread->iNThread); + } + + NKern::FSSignal(iKernelThreadExitSemaphore); + Kern::Exit(0); + } + + +// +// "USER THREAD PREEMPTION LATENCY" +// +// SCENARIO: +// +// A user thread requests an interrupt (RBMChannel::RequestInterrupt()) and waits at User::WaitForAnyRequest() +// (RBMChannel::Result()). +// When the interrupt occurs DBMLChannel::Isr() stores the ISR entry time provided by LDD, +// in DBMLChannel::iTime and queues a DFC (DBMLChannel::iDfc, DBMLChannel::Dfc()) which in its turn +// signals the user thread. +// The user thread, when awaken, immediately re-enters in the LDD, and calculates the latency as +// the difference between the ISR entry time and the current time. +// + +TInt DBMLChannel::StartUserPreemptionLatency() + { + if (iStarted) + { + return KErrInUse; + } + TInt r = PChannel()->BindInterrupt((MBMIsr*) this); + if (r != KErrNone) + { + return r; + } + // Default iRequestInterrupt() implmentation + iRequestInterrupt = &DBMLChannel::RequestInterrupt; + iResult = &DBMLChannel::UserPreemptionLatencyResult; + iInterruptThread = &Kern::CurrentThread(); + iStarted = ETrue; + return KErrNone; + } + +TBMTicks DBMLChannel::UserPreemptionLatencyResult() + { + TBMTicks now = PChannel()->TimerStamp(); + return Delta(iTime, now); + } + +// +// "NTimer PERIOD JITTER" +// +// SCENARIO: +// +// One measuremnt is done by two consecutive NTimer callbacks. +// The first callback stores the current time and the second one calculate the actual period as +// the difference between its own current time and the time stored by the first callback. +// The difference between this actual period and the theoretical period is considered as the jitter. +// + +TInt DBMLChannel::StartNTimerJitter() + { + if (iStarted) + { + return KErrInUse; + } + new (&iNTimer) NTimer(&NTimerJitterCallBack, this); + iRequestInterrupt = &DBMLChannel::RequestNTimerJitterInterrupt; + // Use the default iResult() implmentation + iResult = &DBMLChannel::Result; + iInterruptThread = &Kern::CurrentThread(); + iStarted = ETrue; + return KErrNone; + } + +TInt DBMLChannel::RequestNTimerJitterInterrupt() + { + if (!iStarted) + { + return KErrNotReady; + } + if (iPendingInterruptRequest) + { + return KErrInUse; + } + iPendingInterruptRequest = ETrue; + iNTimerShotCount = 0; + iNTimer.OneShot(1); + return KErrNone; + } + + +void DBMLChannel::NTimerJitterCallBack(TAny* ptr) + { + DBMLChannel* lCh = (DBMLChannel*) ptr; + TBMTicks now = lCh->PChannel()->TimerStamp(); + if (lCh->iNTimerShotCount++ == 0) + { + // + // This is the first callback: store the time and request another one. + // + lCh->iTime = now; + lCh->iNTimer.Again(1); + } + else + { + // + // This is the second callback: measure the jitter and schedule a DFC + // which in its turn will signal the user thread. + // + lCh->iTime = lCh->Delta(lCh->iTime, now); + lCh->iDfc.Add(); + } + } + +// +// "TIMER OVERHEAD" +// +// SCENARIO: +// To measure the overhead of the high-precision timer read operation we get +// two consecutive timestamps through DBMPChannel::TimerStamp() interface. +// The difference beween this two values is considered as the measured overhead. +// + +TInt DBMLChannel::StartTimerStampOverhead() + { + if (iStarted) + { + return KErrInUse; + } + iRequestInterrupt = &DBMLChannel::RequestTimerStampOverhead; + // Use the default iResult() implmentation + iResult = &DBMLChannel::Result; + iInterruptThread = &Kern::CurrentThread(); + iStarted = ETrue; + return KErrNone; + } + +TInt DBMLChannel::RequestTimerStampOverhead() + { + TBMTicks t1 = PChannel()->TimerStamp(); + TBMTicks t2 = PChannel()->TimerStamp(); + iTime = Delta(t1, t2); + NKern::ThreadRequestSignal(&iInterruptThread->iNThread); + return KErrNone; + } +// +// END OF "GETTING TIMER OVERHEAD" +// + +// +// The implmentation of RBMDriver::SetAbsPrioirty() call. +// +TInt DBMLChannel::SetAbsPrioirty(TInt aThreadHandle, TInt aNewPrio, TInt* aOldPrio) + { + NKern::LockSystem(); + // + // Under the system lock find the DThread object and increment its ref-count (i.e Open()) + // + DThread* thr = (DThread*) Kern::ObjectFromHandle(&Kern::CurrentThread(), aThreadHandle, EThread); + TInt r; + if (!thr) + { + r = EBadHandle; + } + else + { + r = thr->Open(); + } + // + // Now it's safe to release the system lock and to work with the object. + // + NKern::ThreadEnterCS(); + NKern::UnlockSystem(); + if (r != KErrNone) + { + NKern::ThreadLeaveCS(); + return r; + } + *aOldPrio = thr->iDefaultPriority; + Kern::SetThreadPriority(aNewPrio, thr); + // + // Work is done - close the object. + // + thr->Close(NULL); + NKern::ThreadLeaveCS(); + return KErrNone; + } + +_LIT(KBmDfcQName, "BmDfcQ"); + +// +// Starts a new sequence of measurements. +// +// Only one sequence can be started for any particular DBMLChannel object during its life. +// If more than one sequence is required a new DBMLChannel object must be created. +// +TInt DBMLChannel::Start(RBMChannel::TMode aMode) + { + TInt r; + if (iDfcQ == NULL) + { + r = Kern::DynamicDfcQCreate(iDfcQ, KBMDfcQThreadPriority, KBmDfcQName); + if (r != KErrNone) + return r; + + iDfc.SetDfcQ(iDfcQ); + iDfc.SetFunction(Dfc); + } + + switch (aMode) + { + case RBMChannel::EInterruptLatency: + r = StartInterruptLatency(); + break; + case RBMChannel::EKernelPreemptionLatency: + r = StartKernelPreemptionLatency(); + break; + case RBMChannel::EUserPreemptionLatency: + r = StartUserPreemptionLatency(); + break; + case RBMChannel::ENTimerJitter: + r = StartNTimerJitter(); + break; + case RBMChannel::ETimerStampOverhead: + r = StartTimerStampOverhead(); + break; + default: + r = KErrNotSupported; + break; + } + + return r; + } + +// +// Client requests. +// +TInt DBMLChannel::Request(TInt aFunction, TAny* a1, TAny* a2) + { + TInt r = KErrNone; + switch (aFunction) + { + case RBMChannel::EStart: + { + RBMChannel::TMode mode = (RBMChannel::TMode) (TInt) a1; + Lock(); + r = Start(mode); + Unlock(); + break; + } + case RBMChannel::ERequestInterrupt: + { + Lock(); + r = (this->*iRequestInterrupt)(); + Unlock(); + break; + } + case RBMChannel::EResult: + { + // + // We don't acquire the lock because: + // (1) iResult() typically reads iTime which was written BEFORE to signal the current thread + // and therefore BEFORE the current thread comes here. + // (2) we really want if possible (i.e. correct!) to avoid the lock acquisition because it can + // increase the measurement overhead in the case when we are in a measured path (e.g. user + // preemption latency benchmark). + // + TBMTicks ticks = (this->*iResult)(); + umemput(a1, &ticks, sizeof(ticks)); + break; + } + // + // All below requests do not access writable DBMChannel state and therefore do not require the lock + // + case RBMChannel::ETimerStamp: + { + TBMTicks ticks = PChannel()->TimerStamp(); + umemput(a1, &ticks, sizeof(ticks)); + break; + } + case RBMChannel::ETimerPeriod: + { + TBMTicks ticks = iTimerPeriod; + umemput(a1, &ticks, sizeof(ticks)); + break; + } + case RBMChannel::ETimerTicksToNs: + { + TBMTicks ticks; + umemget(&ticks, a1, sizeof(ticks)); + TBMNs ns = PChannel()->TimerTicksToNs(ticks); + umemput(a2, &ns, sizeof(ns)); + break; + } + case RBMChannel::ETimerNsToTicks: + { + TBMNs ns; + umemget(&ns, a1, sizeof(ns)); + TBMTicks ticks = PChannel()->TimerNsToTicks(ns); + umemput(a2, &ticks, sizeof(ticks)); + break; + } + case RBMChannel::ESetAbsPriority: + { + TInt newPrio; + TInt oldPrio; + umemget(&newPrio, a2, sizeof(newPrio)); + r = SetAbsPrioirty((TInt) a1, newPrio, &oldPrio); + umemput(a2, &oldPrio, sizeof(oldPrio)); + break; + } + default: + r = KErrNotSupported; + break; + } + return r; + } + +