xbox-kernel/private/ntos/xapi/k32/mmtimer.c
2020-09-30 17:17:25 +02:00

443 lines
13 KiB
C

/*++
Copyright (c) 1985-2000 Microsoft Corporation
Module Name:
mmtimer.c
Abstract:
This module implements the Win32 multimedia timer services.
--*/
#include "basedll.h"
//
// Define the maximum number of multimedia timers allowed.
//
#define MAXIMUM_MMTIMERS (MAXIMUM_WAIT_OBJECTS - 1)
//
// Structure used to bind the parameters from timeSetEvent to its corresponding
// kernel timer object.
//
#include <pshpack4.h>
typedef struct _MMTIMER {
KTIMER KernelTimer;
LPTIMECALLBACK lpTimeCallback;
DWORD dwTimeCallbackData;
BOOLEAN Allocated;
UCHAR Flags;
USHORT SerialNumber;
LARGE_INTEGER FireTime;
LARGE_INTEGER Interval;
} MMTIMER, *PMMTIMER;
#include <poppack.h>
//
// Structure used to bind all of the timer state that is allocated on the timer
// thread's stack.
//
typedef struct _MMTIMER_THREAD_STATE {
MMTIMER Timers[MAXIMUM_MMTIMERS];
KEVENT ThreadEvent;
} MMTIMER_THREAD_STATE, *PMMTIMER_THREAD_STATE;
INITIALIZED_CRITICAL_SECTION(XapiMmTimerCriticalSection);
USHORT XapiMmTimerSerialNumber;
PMMTIMER_THREAD_STATE XapiMmTimerThreadState;
DWORD
WINAPI
XapiMmTimerThread(
LPVOID lpThreadParameter
)
{
ULONG Index;
MMTIMER_THREAD_STATE MmTimerThreadState;
KWAIT_BLOCK WaitBlocks[MAXIMUM_WAIT_OBJECTS];
PVOID WaitObjects[MAXIMUM_WAIT_OBJECTS];
USHORT SerialNumbers[MAXIMUM_WAIT_OBJECTS];
ULONG WaitObjectCount;
NTSTATUS status;
PMMTIMER MmTimer;
LPTIMECALLBACK lpTimeCallback;
DWORD dwTimeCallbackData;
UINT uTimerID;
UINT fuEvent;
LARGE_INTEGER DueTime;
//
// Initialize the event used to signal that this thread needs to rebuild its
// wait object array.
//
KeInitializeEvent(&MmTimerThreadState.ThreadEvent, SynchronizationEvent,
FALSE);
//
// Initialize the kernel timers.
//
for (Index = 0; Index < MAXIMUM_MMTIMERS; Index++) {
KeInitializeTimerEx(&MmTimerThreadState.Timers[Index].KernelTimer,
SynchronizationTimer);
MmTimerThreadState.Timers[Index].Allocated = FALSE;
}
//
// The thread's state is now initialized. Notify the thread that created us
// that we're ready.
//
XapiMmTimerThreadState = &MmTimerThreadState;
KeSetEvent((PKEVENT)lpThreadParameter, EVENT_INCREMENT, FALSE);
//
// Initially, we wait on only our thread event.
//
WaitObjects[0] = &MmTimerThreadState.ThreadEvent;
WaitObjectCount = 1;
for (;;) {
//
// Wait to be notified of the arrival or removal of a timer or for a
// timer to be signaled.
//
status = KeWaitForMultipleObjects(WaitObjectCount, WaitObjects, WaitAny,
UserRequest, UserMode, FALSE, NULL, WaitBlocks);
if (status == STATUS_WAIT_0) {
//
// We've been signaled due to an addition or removal of a timer
// from our thread state structure. Recompute the local wait object
// array.
//
EnterCriticalSection(&XapiMmTimerCriticalSection);
RecomputeWaitObjects:
WaitObjectCount = 1;
for (Index = 0; Index < MAXIMUM_WAIT_OBJECTS - 1; Index++) {
//
// If the timer has been allocated, add it to the wait object
// array and capture the timer's serial number. We need the
// serial number to deal with a race where this thread is woken
// in response to a signaled timer and another thread has
// already called timeKillEvent.
//
if (MmTimerThreadState.Timers[Index].Allocated) {
SerialNumbers[WaitObjectCount] =
MmTimerThreadState.Timers[Index].SerialNumber;
WaitObjects[WaitObjectCount] =
&MmTimerThreadState.Timers[Index].KernelTimer;
WaitObjectCount++;
}
}
LeaveCriticalSection(&XapiMmTimerCriticalSection);
} else if (status >= STATUS_WAIT_1 && status < (STATUS_WAIT_1 +
MAXIMUM_MMTIMERS)) {
//
// We've been signaled due to a timer firing. Verify that the timer
// is still allocated and that the serial number matches what we
// think it should be.
//
EnterCriticalSection(&XapiMmTimerCriticalSection);
MmTimer = CONTAINING_RECORD(WaitObjects[status], MMTIMER, KernelTimer);
if (MmTimer->Allocated &&
(MmTimer->SerialNumber == SerialNumbers[status])) {
//
// The timer is still allocated. Depending on the timer flags,
// either set an event, pulse an event, or invoke a callback.
//
fuEvent = (UINT)MmTimer->Flags;
if ((fuEvent & TIME_CALLBACK_EVENT_SET) != 0) {
NtSetEvent((HANDLE)MmTimer->lpTimeCallback, NULL);
} else if ((fuEvent & TIME_CALLBACK_EVENT_PULSE) != 0) {
NtPulseEvent((HANDLE)MmTimer->lpTimeCallback, NULL);
} else {
//
// Capture the timer data to the stack so that we can invoke
// the callback outside of the critical section.
//
lpTimeCallback = MmTimer->lpTimeCallback;
dwTimeCallbackData = MmTimer->dwTimeCallbackData;
Index = MmTimer - &MmTimerThreadState.Timers[0];
uTimerID = MAKELONG(Index + 1, MmTimer->SerialNumber);
LeaveCriticalSection(&XapiMmTimerCriticalSection);
//
// Invoke the callback. The callback or another thread may
// end up calling timeKillEvent for our timer ID so we can't
// touch the MMTIMER structure until we validate it again.
//
lpTimeCallback(uTimerID, 0, dwTimeCallbackData, 0, 0);
//
// Reenter the critical section and verify that the timer
// hasn't been killed.
//
EnterCriticalSection(&XapiMmTimerCriticalSection);
if (!MmTimer->Allocated ||
(MmTimer->SerialNumber != SerialNumbers[status])) {
LeaveCriticalSection(&XapiMmTimerCriticalSection);
continue;
}
}
if ((fuEvent & TIME_PERIODIC) != 0) {
//
// Initialize the timer with its new due time. If the timer
// has already expired, then set the due time to zero so
// that we can immediately wake up and process the timer
// again.
//
MmTimer->FireTime.QuadPart -= MmTimer->Interval.QuadPart;
DueTime.QuadPart = KeQueryInterruptTime() -
MmTimer->FireTime.QuadPart;
if (DueTime.QuadPart > 0) {
DueTime.QuadPart = 0;
}
KeSetTimerEx(&MmTimer->KernelTimer, DueTime, 0, NULL);
} else {
//
// Mark the timer as unallocated.
//
MmTimer->Allocated = FALSE;
//
// Jump into the code to recompute the wait objects array.
//
goto RecomputeWaitObjects;
}
}
LeaveCriticalSection(&XapiMmTimerCriticalSection);
} else {
XDBGERR("XAPI", "XapiMmTimerThread woke for unexpected reason: %08x\n", status);
}
}
}
UINT
WINAPI
timeSetEvent(
UINT uDelay,
UINT uResolution,
LPTIMECALLBACK lpTimeCallback,
DWORD dwTimeCallbackData,
UINT fuEvent
)
{
KEVENT ThreadReadyEvent;
HANDLE hThread;
ULONG Index;
UINT uTimerID;
PMMTIMER MmTimer;
USHORT SerialNumber;
EnterCriticalSection(&XapiMmTimerCriticalSection);
//
// Check if the multimedia timer thread has been started yet.
//
if (XapiMmTimerThreadState == NULL) {
//
// Create an event that the multimedia timer thread will signal when it
// has finished initializating itself.
//
KeInitializeEvent(&ThreadReadyEvent, NotificationEvent, FALSE);
//
// Create the multimedia timer thread with the default stack size for
// the title.
//
hThread = CreateThread(NULL, 0, XapiMmTimerThread, &ThreadReadyEvent, 0,
NULL);
if (hThread == NULL) {
XDBGERR("XAPI", "XapiMmTimerThread cannot be created\n");
LeaveCriticalSection(&XapiMmTimerCriticalSection);
return 0;
}
CloseHandle(hThread);
//
// Wait for the multimedia timer thread to initialize itself.
//
KeWaitForSingleObject(&ThreadReadyEvent, Executive, UserMode, FALSE,
NULL);
//
// At this point, the global timer thread state should have been filled
// in.
//
ASSERT(XapiMmTimerThreadState != NULL);
}
uTimerID = 0;
for (Index = 0; Index < MAXIMUM_WAIT_OBJECTS - 1; Index++) {
if (!XapiMmTimerThreadState->Timers[Index].Allocated) {
SerialNumber = XapiMmTimerSerialNumber++;
MmTimer = &XapiMmTimerThreadState->Timers[Index];
MmTimer->Allocated = TRUE;
MmTimer->Flags = (UCHAR)fuEvent;
MmTimer->SerialNumber = SerialNumber;
MmTimer->lpTimeCallback = lpTimeCallback;
MmTimer->dwTimeCallbackData = dwTimeCallbackData;
//
// Compute the initial due time for the timer and start the timer.
//
// We track the fire time and period interval ourselves instead of
// using the period functionality of the kernel timer to avoid
// timer drift that can occur.
//
MmTimer->Interval.QuadPart = -10000 * (LONGLONG)uDelay;
MmTimer->FireTime.QuadPart = KeQueryInterruptTime() -
MmTimer->Interval.QuadPart;
KeSetTimerEx(&MmTimer->KernelTimer, MmTimer->Interval, 0, NULL);
//
// Signal the multimedia timer thread that it needs to add a timer
// to its wait list.
//
KeSetEvent(&XapiMmTimerThreadState->ThreadEvent, EVENT_INCREMENT,
FALSE);
uTimerID = MAKELONG(Index + 1, SerialNumber);
break;
}
}
LeaveCriticalSection(&XapiMmTimerCriticalSection);
return uTimerID;
}
MMRESULT
WINAPI
timeKillEvent(
UINT uTimerID
)
{
MMRESULT mmresult;
ULONG Index;
PMMTIMER MmTimer;
EnterCriticalSection(&XapiMmTimerCriticalSection);
Index = LOWORD(uTimerID) - 1;
//
// Validate that this is a legal timer identifier. The timer thread must be
// already running in order to have obtained a valid identifier from
// timeSetEvent. The timer identifier must be in legal range and the timer
// must be marked as having been allocated.
//
if ((XapiMmTimerThreadState != NULL) && (Index < MAXIMUM_MMTIMERS) &&
XapiMmTimerThreadState->Timers[Index].Allocated &&
XapiMmTimerThreadState->Timers[Index].SerialNumber == HIWORD(uTimerID)) {
//
// Cancel the timer and mark the timer as unallocated.
//
MmTimer = &XapiMmTimerThreadState->Timers[Index];
KeCancelTimer(&MmTimer->KernelTimer);
MmTimer->Allocated = FALSE;
//
// Signal the multimedia timer thread that it needs to remove a timer
// from its wait list.
//
KeSetEvent(&XapiMmTimerThreadState->ThreadEvent, EVENT_INCREMENT, FALSE);
mmresult = MMSYSERR_NOERROR;
} else {
mmresult = TIMERR_NOCANDO;
}
LeaveCriticalSection(&XapiMmTimerCriticalSection);
return mmresult;
}
MMRESULT
WINAPI
timeGetSystemTime(
LPMMTIME lpTime,
UINT wSize
)
{
lpTime->u.ms = NtGetTickCount();
lpTime->wType = TIME_MS;
return TIMERR_NOERROR;
}