Examining Windows CE 3.0 Real-Time Capabilities 
by Bart Van Beneden 

Listing One
///////////////////////////////////////////////////////////////////////////// 
// PrioInversion.c  This file defines an entry point for the DLL application.
//       The test code creates a priority inversion situation.
//       The test code is part of a DLL and is loaded, locked, and called
//       from an external application to eliminate all paging overhead.
//////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "test.h"
#include "trace.h"

#define LOW_PRIORITY       10
#define MEDIUM_PRIORITY     5
#define HIGH_PRIORITY       0

BOOL    VirtualCopy( LPVOID, LPVOID, DWORD, DWORD );
int     MediumPriorityThread(LPVOID);
int     HighPriorityThread(LPVOID);

int             iTest           = 0;
unsigned long*  pulpPCIMemory   = NULL;
HANDLE          HandleMutex;
HANDLE          HandleSemA, HandleSemB;
HANDLE          HandleMediumPrioThread, HandleHighPrioThread;

////////////////////////////////////////////////////////////////////////////// 
// This is the start of the program, and is an exported function of the DLL 
// that is called by an external application.
// This is also the low-priority thread in the test; after it has set up 
// all the necessary items for the test, it will grab the mutex and release 
// higher priority threads.
///////////////////////////////////////////////////////////////////////////// 
TEST_API int Start(void)
{
    DWORD   dwThreadId;
    int     i;
    // Set the priority of this thread to a low value
    CeSetThreadPriority(GetCurrentThread(), LOW_PRIORITY);
    // Set the thread quantum to 0 i.e.; run to completion
    if (CeSetThreadQuantum(GetCurrentThread(), 0) == 0)
    {
        RETAILMSG( 1, (TEXT( "Failed to set thread quantum\r\n" )));
        return -1;
    }
    // Allocate virtual memory region and bind physical PCI memory to it
    pulpPCIMemory = 
         (unsigned long *) VirtualAlloc( 0, 0x4, MEM_RESERVE, PAGE_NOACCESS );
    if ( !pulpPCIMemory )
    {
        RETAILMSG( 1, (TEXT( 
                        "Failed to allocate virtual address space\r\n" )));
        return -1;
    }
    VirtualCopy((LPVOID)pulpPCIMemory, (LPVOID)(0xE1000000 / 256), 
                       0x40, PAGE_READWRITE | PAGE_NOCACHE | PAGE_PHYSICAL );
    // Create the mutex
    HandleMutex = CreateMutex(NULL, FALSE, NULL);
    if (HandleMutex == NULL) 
    {
        RETAILMSG( 1, (TEXT( "Failed to create mutex\r\n" )));
        return -1;
    }
    // Create the semaphores necessary to synchronize the threads
    HandleSemA = CreateSemaphore(NULL, 0, 1, NULL);
    HandleSemB = CreateSemaphore(NULL, 0, 1, NULL);
    if ((HandleSemA==NULL)||(HandleSemB==NULL)) 
    {
        RETAILMSG( 1, (TEXT( "Failed to create semaphores\r\n" )));
        return -1;
    }
    // Create the other threads
    HandleMediumPrioThread  = 
               CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) 
               MediumPriorityThread, NULL, 0, &dwThreadId);
    HandleHighPrioThread    = 
               CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) 
               HighPriorityThread, NULL, 0, &dwThreadId);
    if ((HandleMediumPrioThread==NULL)||(HandleHighPrioThread==NULL)) 
    {
        RETAILMSG( 1, (TEXT( "Failed to create threads\r\n" )));
        return -1;
    }
    // Let the other threads initialize properly
    Sleep(1);
    for (i=0; i<11000; i++)
    {
        // This variable set to 1 so medium priority thread has work to do.
       iTest = 1;
        // Acquire mutex so it is not available for high-priority thread
        WaitForSingleObject(HandleMutex, INFINITE);
        // Release semaphore B so medium priority thread can start running
        ReleaseSemaphore(HandleSemB, 1, NULL);
        // Write trace to check that this thread's priority is boosted when 
        // system attempts to resolve the priority inversion situation
        *pulpPCIMemory = TRC(0, 0, TRC_MT, TRC_RLS, 0, 0);
        // Release the mutex so the high-priority thread can grab it
        ReleaseMutex(HandleMutex);  
    }
    return 0;
}
///////////////////////////////////////////////////////////////////////////// 
// Medium-priority thread. Its main purpose is to keep the low-priority
// thread from running once it has grabbed the mutex.
///////////////////////////////////////////////////////////////////////////// 
int MediumPriorityThread(LPVOID pArg)
{
    // Set the priority of this thread to the medium value
    CeSetThreadPriority(GetCurrentThread(), MEDIUM_PRIORITY);
    // Set the thread quantum to 0 i.e.; run to completion
    if (CeSetThreadQuantum(GetCurrentThread(), 0) == 0)
    {
        RETAILMSG( 1, (TEXT( "Failed to set thread quantum\r\n" )));
        return -1;
   }
    for (;;)
    {
        // Wait here until we are sure low-priority thread has grabbed mutex
        WaitForSingleObject(HandleSemB, INFINITE);
        // Release semaphore A to unblock the high-priority thread
        ReleaseSemaphore(HandleSemA, 1, NULL);
        // While this variable is set, perform a busy loop. This is crucial: 
        // if system does not implement priority inheritance properly, this 
        // loop prevents low-priority thread from releasing mutex, resulting 
        // in deadlock
       while(iTest);
    }
    return 0;
}
///////////////////////////////////////////////////////////////////////////// 
// High-priority thread. It will try to grab a mutex was acquired
// by the low-priority thread earlier.
///////////////////////////////////////////////////////////////////////////// 
int HighPriorityThread(LPVOID pArg)
{
    // Set the priority of this thread to the highest value
    CeSetThreadPriority(GetCurrentThread(), HIGH_PRIORITY);
    // Set the thread quantum to 0 i.e.; run to completion
    if (CeSetThreadQuantum(GetCurrentThread(), 0) == 0)
    {
        RETAILMSG( 1, (TEXT( "Failed to set thread quantum\r\n" )));
        return -1;
    }
    for (;;)
    {
        // Wait here until you are sure medium-priority thread is set 
        // to execute its busy loop.
        WaitForSingleObject(HandleSemA, INFINITE);
        // Write trace before acquiring mutex
        *pulpPCIMemory = TRC(0, 2, TRC_MT, TRC_ACQ, 0, 0);
        // Acquire mutex (system will now detect priority inversion situation
        // and resolve it).
        WaitForSingleObject(HandleMutex, INFINITE);
        // Write trace after acquiring mutex (the difference between both 
        // traces is the time it took to recover from the deadlock caused 
        // by priority inversion).
        *pulpPCIMemory = TRC(0, 2, TRC_MT, TRC_ACQ, 0, 1);
        // This test cycle is finished ; let the medium-priority thread now to stop looping
        iTest = 0;
        // Release the mutex to start the next sequence
        ReleaseMutex(HandleMutex);
    }
    return 0;
}



3

