/*++ Copyright (C) Microsoft Corporation, 1998 - 1999 Module Name: thread.c Abstract: Author: Environment: kernel mode only Notes: Revision History: --*/ #include "redbook.h" #include "ntddredb.h" #include "proto.h" #include // for SetKnownGoodDrive() #include // vsprintf() #include "thread.tmh" // // this is how many seconds until resources are free'd from // a play, and also how long (in seconds) before a frozen state // is detected (and a fix attempted). // #define REDBOOK_THREAD_FIXUP_SECONDS 10 #define REDBOOK_THREAD_SYSAUDIO_CACHE_SECONDS 2 #define REDBOOK_PERFORM_STUTTER_CONTROL 0 #if DBG // // allows me to send silence to ks as needed // ULONG RedBookForceSilence = FALSE; #endif #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, RedBookAllocatePlayResources ) #pragma alloc_text(PAGE, RedBookCacheToc ) #pragma alloc_text(PAGE, RedBookDeallocatePlayResources ) #pragma alloc_text(PAGERW, RedBookReadRaw ) #pragma alloc_text(PAGERW, RedBookStream ) #pragma alloc_text(PAGE, RedBookSystemThread ) #pragma alloc_text(PAGE, RedBookCheckForAudioDeviceRemoval ) #pragma alloc_text(PAGE, RedBookThreadDigitalHandler ) /* but last two CANNOT be unlocked when playing, so they are (temporarily) commented out. eventually will only have them locked when playing #pragma alloc_text(PAGERW, RedBookReadRawCompletion ) #pragma alloc_text(PAGERW, RedBookStreamCompletion ) */ #endif ALLOC_PRAGMA VOID RedBookSystemThread( PVOID Context ) /*++ Routine Description: This system thread will wait on events, sending buffers to Kernel Streaming as they become available. Arguments: Context - deviceExtension Return Value: status --*/ { PREDBOOK_DEVICE_EXTENSION deviceExtension = Context; LARGE_INTEGER timeout; NTSTATUS waitStatus; NTSTATUS status; ULONG i; ULONG timeouts; LARGE_INTEGER stopTime; // // some per-thread state // BOOLEAN killed = FALSE; PAGED_CODE(); deviceExtension->Thread.SelfPointer = PsGetCurrentThread(); // // perf fix -- run at low realtime priority // KeSetPriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY); timeouts = 0; stopTime.QuadPart = 0; KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Thread => UpdateMixerPin at %p\n", &deviceExtension->Stream.UpdateMixerPin)); // // Just loop waiting for events. // //waitForNextEvent: while ( 1 ) { // // if are killed, just wait on singular event--EVENT_DIGITAL until // can finish processing. there should not be anything left in the // IOCTL_LIST (since the kill calls RemoveLockAndWait() first) // // this also implies that a kill will never occur while ioctls are // still being processed. this does not guarantee the state will // be STOPPED, just that no IO will be occurring. // // // nanosecond is 10^-9, units is 100 nanoseconds // so seconds is 10,000,000 units // must wait in relative time, which requires negative numbers // timeout.QuadPart = (LONGLONG)(-1 * 10 * 1000 * (LONGLONG)1000); // // note: we are using a timeout mechanism mostly to catch bugs // where the state would lock up. we also "auto adjust" // our internal state if things get too wierd, basically // auto-fixing ourselves. note that this does cause the // thread's stack to be swapped in, so this shouldn't // be done when we're 100% stopped. // if (deviceExtension->Thread.IoctlCurrent == NULL) { // // wait on an ioctl, but not the ioctl completion event // ULONG state = GetCdromState(deviceExtension); if ((state == CD_STOPPED) && (!RedBookArePlayResourcesAllocated(deviceExtension)) ) { // // if we've got no current ioctl and we haven't allocated // any resources, there're no need to timeout. // this will prevent this stack from getting swapped in // needlessly, reducing effective footprint a bit // stopTime.QuadPart = 0; waitStatus = KeWaitForMultipleObjects(EVENT_MAXIMUM - 1, (PVOID)(&deviceExtension->Thread.Events[0]), WaitAny, Executive, UserMode, FALSE, // Alertable NULL, deviceExtension->Thread.EventBlock ); } else { // // we've got no current ioctl, but we're also not stopped. // it is also possible to be waiting to cleanup resources here. // even if we're paused, we want to keep track of what's // going on, since it's possible for the state to get // messed up here. // waitStatus = KeWaitForMultipleObjects(EVENT_MAXIMUM - 1, (PVOID)(&deviceExtension->Thread.Events[0]), WaitAny, Executive, UserMode, FALSE, // Alertable &timeout, deviceExtension->Thread.EventBlock ); } } else { // // wait on the ioctl completion, but not the ioctl event // waitStatus = KeWaitForMultipleObjects(EVENT_MAXIMUM - 1, (PVOID)(&deviceExtension->Thread.Events[1]), WaitAny, Executive, UserMode, FALSE, // Alertable &timeout, deviceExtension->Thread.EventBlock ); if (waitStatus != STATUS_TIMEOUT) { waitStatus ++; // to account for offset } } // // need to check if we are stopped for too long -- if so, free // the resources. // { ULONG state = GetCdromState(deviceExtension); if (!TEST_FLAG(state, CD_PLAYING) && !TEST_FLAG(state, CD_PAUSED)) { LARGE_INTEGER now; // not playing, if (stopTime.QuadPart == 0) { LONGLONG offset; KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "StopTime => Determining when to dealloc\n")); // query the time KeQueryTickCount( &stopTime ); // add appropriate offset // nanosecond is 10^-9, units is 100 nanoseconds // so seconds is 10,000,000 units // offset = REDBOOK_THREAD_SYSAUDIO_CACHE_SECONDS; offset *= (LONGLONG)(10 * 1000 * (LONGLONG)1000); // divide offset by time increment offset /= (LONGLONG)KeQueryTimeIncrement(); // add those ticks to store when we should release // our resources stopTime.QuadPart += offset; } KeQueryTickCount(&now); KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "StopTime => Is %I64x >= %I64x?\n", now.QuadPart, stopTime.QuadPart )); if (now.QuadPart >= stopTime.QuadPart) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "StopTime => Deallocating resources\n")); RedBookDeallocatePlayResources(deviceExtension); } } else { stopTime.QuadPart = 0; } } RedBookCheckForAudioDeviceRemoval(deviceExtension); // // To enable a single thread for multiple cdroms, just set events // at offset of (DEVICE_ID * EVENT_MAX) + EVENT_TO_SET // set deviceExtension = waitStatus / EVENT_MAX // switch ( waitStatus % EVENT_MAX ) // if (waitStatus == EVENT_DIGITAL) { timeouts = 0; } switch ( waitStatus ) { case EVENT_IOCTL: { PLIST_ENTRY listEntry; while ((listEntry = ExInterlockedRemoveHeadList( &deviceExtension->Thread.IoctlList, &deviceExtension->Thread.IoctlLock)) != NULL) { RedBookThreadIoctlHandler(deviceExtension, listEntry); if (deviceExtension->Thread.IoctlCurrent) { // special case break; } } break; } case EVENT_COMPLETE: { RedBookThreadIoctlCompletionHandler(deviceExtension); break; } case EVENT_WMI: { PLIST_ENTRY listEntry; while ((listEntry = ExInterlockedRemoveHeadList( &deviceExtension->Thread.WmiList, &deviceExtension->Thread.WmiLock)) != NULL) { RedBookThreadWmiHandler(deviceExtension, listEntry); } break; } case EVENT_DIGITAL: { PLIST_ENTRY listEntry; while ((listEntry = ExInterlockedRemoveHeadList( &deviceExtension->Thread.DigitalList, &deviceExtension->Thread.DigitalLock)) != NULL) { RedBookThreadDigitalHandler(deviceExtension, listEntry); } break; } case EVENT_KILL_THREAD: { ULONG state = GetCdromState(deviceExtension); killed = TRUE; // // i don't think this can occur with outstanding io, since // the remove lock is taken first. nonetheless, it's better // to be safe than sorry. // if (!TEST_FLAG(state, CD_STOPPED)) { state = SetCdromState(deviceExtension, state, CD_STOPPING); break; } RedBookDeallocatePlayResources(deviceExtension); // // else we can safely terminate. // KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "STExit => Thread was killed\n")); ASSERT(deviceExtension->Thread.PendingRead == 0); ASSERT(deviceExtension->Thread.PendingStream == 0); PsTerminateSystemThread(STATUS_SUCCESS); ASSERT(!"[redbook] Thread should never reach past self-terminate code"); break; } case STATUS_TIMEOUT: { ULONG state = GetCdromState(deviceExtension); timeouts++; if (timeouts < REDBOOK_THREAD_FIXUP_SECONDS) { break; } else { timeouts = 0; } // // these tests all occur once every ten seconds. // the most basic case is where we want to deallocate // our cached TOC, but we also perform lots of // sanity testing here -- ASSERTing on CHK builds and // trying to fix ourselves up when possible. // if (!TEST_FLAG(state, CD_PLAYING) && !TEST_FLAG(state, CD_PAUSED)) { // !handling ioctls // not playing, so free resources RedBookDeallocatePlayResources(deviceExtension); } else if (TEST_FLAG(state, CD_STOPPING)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugError, "[redbook] " "STTime !! %x seconds inactivity\n", REDBOOK_THREAD_FIXUP_SECONDS)); if (IsListEmpty(&deviceExtension->Thread.DigitalList)) { if ((deviceExtension->Thread.PendingRead == 0) && (deviceExtension->Thread.PendingStream == 0) && (deviceExtension->Thread.IoctlCurrent != NULL)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugError, "[redbook] " "STTime !! No Reads, No Streams, In a temp " "state (%x) and have STOP irp %p " "pending?!\n", state, ((PREDBOOK_THREAD_IOCTL_DATA)deviceExtension->Thread.IoctlCurrent)->Irp )); ASSERT(!"STTime !! CD_STOPPING Fixup with no reads nor streams but STOP pending\n"); SetCdromState(deviceExtension, state, CD_STOPPED); KeSetEvent(deviceExtension->Thread.Events[EVENT_COMPLETE], IO_NO_INCREMENT, FALSE); } else { ASSERT(!"STTime !! CD_STOPPING Fixup with empty list and no pending ioctl?\n"); } } else { ASSERT(!"STTime !! CD_STOPPING Fixup with list items\n"); KeSetEvent(deviceExtension->Thread.Events[EVENT_DIGITAL], IO_NO_INCREMENT, FALSE); } } else if (TEST_FLAG(state, CD_PAUSING)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugError, "[redbook] " "STTime !! %x seconds inactivity\n", REDBOOK_THREAD_FIXUP_SECONDS)); if (IsListEmpty(&deviceExtension->Thread.DigitalList)) { if ((deviceExtension->Thread.PendingRead == 0) && (deviceExtension->Thread.PendingStream == 0) && (deviceExtension->Thread.IoctlCurrent != NULL)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugError, "[redbook] " "STTime !! No Reads, No Streams, In a temp " "state (%x) and have PAUSE irp %p " "pending?!\n", state, ((PREDBOOK_THREAD_IOCTL_DATA)deviceExtension->Thread.IoctlCurrent)->Irp )); ASSERT(!"STTime !! CD_PAUSING Fixup with no reads nor streams but PAUSE pending\n"); SetCdromState(deviceExtension, state, CD_PAUSED); KeSetEvent(deviceExtension->Thread.Events[EVENT_COMPLETE], IO_NO_INCREMENT, FALSE); } else { ASSERT(!"STTime !! CD_PAUSING Fixup with empty list and no pending ioctl?\n"); } } else { ASSERT(!"STTime !! CD_PAUSING Fixup with list items\n"); KeSetEvent(deviceExtension->Thread.Events[EVENT_DIGITAL], IO_NO_INCREMENT, FALSE); } } else if (TEST_FLAG(state, CD_PAUSED)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "STTime => Still paused\n")); } else { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "STTime !! %x seconds inactivity\n", REDBOOK_THREAD_FIXUP_SECONDS)); if (IsListEmpty(&deviceExtension->Thread.DigitalList)) { ASSERT(!"STTime !! CD_PLAYING Fixup with empty list\n"); } else { ASSERT(!"STTime !! CD_PLAYING Fixup with list items\n"); KeSetEvent(deviceExtension->Thread.Events[EVENT_DIGITAL], IO_NO_INCREMENT, FALSE); } } break; } default: { if (waitStatus > 0 && waitStatus < EVENT_MAXIMUM) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugError, "[redbook] " "ST !! Unhandled event: %lx\n", waitStatus)); } else { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugError, "[redbook] " "ST !! event too large/small: %lx\n", waitStatus)); } ASSERT(!"[redbook] ST !! Unhandled event"); break; } } // end of the huge case statement. // // check if ioctl waiting or killed. if so, and state is no longer // in an intermediate state, set the appropriate event. // ordered to short-curcuit quickly. // { ULONG state = GetCdromState(deviceExtension); if (killed && deviceExtension->Thread.PendingRead == 0 && deviceExtension->Thread.PendingStream == 0) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "ST => Killing Thread %p\n", deviceExtension->Thread.SelfPointer)); ASSERT(!TEST_FLAG(state, CD_MASK_TEMP)); state = SetCdromState(deviceExtension, state, CD_STOPPED); KeSetEvent(deviceExtension->Thread.Events[EVENT_KILL_THREAD], IO_NO_INCREMENT, FALSE); } } continue; } // while(1) loop ASSERT(!"[redbook] ST !! somehow broke out of while(1) loop?"); } VOID RedBookReadRaw( PREDBOOK_DEVICE_EXTENSION DeviceExtension, PREDBOOK_COMPLETION_CONTEXT Context ) /*++ Routine Description: Reads raw audio data off the cdrom. Must either reinsert Context into queue and set an event or set a completion routine which will do so. Arguments: DeviceObject - CDROM class driver object or lower level filter Return Value: status --*/ { NTSTATUS status; PIO_STACK_LOCATION nextIrpStack; PRAW_READ_INFO readInfo; PAGED_CODE(); VerifyCalledByThread(DeviceExtension); KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugDigitalR, "[redbook] " "ReadRaw => Entering\n")); status = IoAcquireRemoveLock(&DeviceExtension->RemoveLock, Context->Irp); if (!NT_SUCCESS(status)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugDigitalR, "[redbook] " "ReadRaw !! RemoveLock failed %lx\n", status)); // end of thread loop will check for no outstanding io // and on too many errors set stopping // don't forget perf info Context->TimeReadSent.QuadPart = 0; // special value Context->Irp->IoStatus.Status = status; Context->Reason = REDBOOK_CC_READ_COMPLETE; // // put it on the queue and set the event // ExInterlockedInsertTailList(&DeviceExtension->Thread.DigitalList, &Context->ListEntry, &DeviceExtension->Thread.DigitalLock); KeSetEvent(DeviceExtension->Thread.Events[EVENT_DIGITAL], IO_CD_ROM_INCREMENT, FALSE); return; } KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugDigitalR, "[redbook] " "ReadRaw => Index %x sending Irp %p\n", Context->Index, Context->Irp)); // // (no failure from this point forward) // IoReuseIrp(Context->Irp, STATUS_UNSUCCESSFUL); Context->Irp->MdlAddress = Context->Mdl; // // irp is from kernel mode // Context->Irp->AssociatedIrp.SystemBuffer = NULL; // // fill in the completion context // ASSERT(Context->DeviceExtension == DeviceExtension); // // setup the irpstack for the raw read // nextIrpStack = IoGetNextIrpStackLocation(Context->Irp); SET_FLAG(nextIrpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME); nextIrpStack->MajorFunction = IRP_MJ_DEVICE_CONTROL; nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_CDROM_RAW_READ; nextIrpStack->Parameters.DeviceIoControl.Type3InputBuffer = Context->Buffer; nextIrpStack->Parameters.DeviceIoControl.InputBufferLength = sizeof(RAW_READ_INFO); nextIrpStack->Parameters.DeviceIoControl.OutputBufferLength = (RAW_SECTOR_SIZE * DeviceExtension->WmiData.SectorsPerRead); // // setup the read info (uses same buffer) // readInfo = (PRAW_READ_INFO)(Context->Buffer); readInfo->DiskOffset.QuadPart = (ULONGLONG)(DeviceExtension->CDRom.NextToRead)*COOKED_SECTOR_SIZE; readInfo->SectorCount = DeviceExtension->WmiData.SectorsPerRead; readInfo->TrackMode = CDDA; // // send it. // IoSetCompletionRoutine(Context->Irp, RedBookReadRawCompletion, Context, TRUE, TRUE, TRUE); KeQueryTickCount(&Context->TimeReadSent); IoCallDriver(DeviceExtension->TargetDeviceObject, Context->Irp); return; } NTSTATUS RedBookReadRawCompletion( PVOID UnusableParameter, PIRP Irp, PREDBOOK_COMPLETION_CONTEXT Context ) /*++ Routine Description: When a read completes, use zero'd buffer if error occurred. Make buffer available to ks, then set ks event. Arguments: DeviceObject - NULL, due to being originator of IRP Irp - pointer to buffer to send to KS must check error to increment/clear error count Context - REDBOOK_COMPLETION_CONTEXT Return Value: STATUS_MORE_PROCESSING_REQUIRED --*/ { PREDBOOK_DEVICE_EXTENSION deviceExtension = Context->DeviceExtension; KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugDigitalR, "[redbook] " "ReadRaw => Completed Irp %p\n", Irp)); KeQueryTickCount(&Context->TimeStreamReady); Context->Reason = REDBOOK_CC_READ_COMPLETE; ExInterlockedInsertTailList(&deviceExtension->Thread.DigitalList, &Context->ListEntry, &deviceExtension->Thread.DigitalLock); KeSetEvent(deviceExtension->Thread.Events[EVENT_DIGITAL], IO_CD_ROM_INCREMENT, FALSE); // // safe to release it since we wait for thread termination // IoReleaseRemoveLock(&deviceExtension->RemoveLock, Context->Irp); return STATUS_MORE_PROCESSING_REQUIRED; } VOID RedBookStream( PREDBOOK_DEVICE_EXTENSION DeviceExtension, PREDBOOK_COMPLETION_CONTEXT Context ) /*++ Routine Description: Send buffer to KS. Must either reinsert Context into queue and set an event or set a completion routine which will do so. Arguments: Context - DeviceExtension Return Value: status --*/ { NTSTATUS status; PIO_STACK_LOCATION nextIrpStack; PUCHAR buffer; PKSSTREAM_HEADER header; ULONG bufferSize; PULONG streamOk; PAGED_CODE(); VerifyCalledByThread(DeviceExtension); KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugDigitalS, "[redbook] " "Stream => Entering\n")); bufferSize = DeviceExtension->WmiData.SectorsPerRead * RAW_SECTOR_SIZE; status = IoAcquireRemoveLock(&DeviceExtension->RemoveLock, Context->Irp); if (!NT_SUCCESS(status)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugDigitalS, "[redbook] " "Stream !! RemoveLock failed %lx\n", status)); // end of thread loop will check for no outstanding io // and on too many errors set CD_STOPPING // don't forget perf info Context->TimeReadSent.QuadPart = 0; // special value Context->Irp->IoStatus.Status = status; Context->Reason = REDBOOK_CC_STREAM_COMPLETE; // // put it on the queue and set the event // ExInterlockedInsertTailList(&DeviceExtension->Thread.DigitalList, &Context->ListEntry, &DeviceExtension->Thread.DigitalLock); KeSetEvent(DeviceExtension->Thread.Events[EVENT_DIGITAL], IO_CD_ROM_INCREMENT, FALSE); return; } KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugDigitalS, "[redbook] " "Stream => Index %x sending Irp %p\n", Context->Index, Context->Irp)); // // CONSIDER - how does STOPPED occur? // SUGGEST - out of loop, if no error and none pending, set to STOPPED? // // // (no failure from this point forward) // // // use a zero'd buffer if an error occurred during the read // if (NT_SUCCESS(Context->Irp->IoStatus.Status)) { IoReuseIrp(Context->Irp, STATUS_SUCCESS); buffer = Context->Buffer; // good data Context->Irp->MdlAddress = Context->Mdl; } else { IoReuseIrp(Context->Irp, STATUS_SUCCESS); buffer = DeviceExtension->Buffer.SilentBuffer; // zero'd data Context->Irp->MdlAddress = DeviceExtension->Buffer.SilentMdl; } #if DBG if (RedBookForceSilence) { buffer = DeviceExtension->Buffer.SilentBuffer; // zero'd data Context->Irp->MdlAddress = DeviceExtension->Buffer.SilentMdl; } #endif // RedBookUseSilence nextIrpStack = IoGetNextIrpStackLocation(Context->Irp); // // get and fill in the context // ASSERT(Context->DeviceExtension == DeviceExtension); // // setup the irpstack for streaming the buffer // nextIrpStack->MajorFunction = IRP_MJ_DEVICE_CONTROL; nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_KS_WRITE_STREAM; nextIrpStack->Parameters.DeviceIoControl.OutputBufferLength = sizeof(KSSTREAM_HEADER); nextIrpStack->Parameters.DeviceIoControl.InputBufferLength = sizeof(KSSTREAM_HEADER); nextIrpStack->FileObject = DeviceExtension->Stream.PinFileObject; Context->Header.FrameExtent = bufferSize; Context->Header.DataUsed = bufferSize; Context->Header.Size = sizeof(KSSTREAM_HEADER); Context->Header.TypeSpecificFlags = 0; Context->Header.Data = buffer; Context->Header.OptionsFlags = 0; Context->Irp->AssociatedIrp.SystemBuffer = &Context->Header; #if REDBOOK_PERFORM_STUTTER_CONTROL #if REDBOOK_WMI_BUFFERS_MIN < 3 #error "The minimum number of buffers must be at least three due to the method used to prevent stuttering" #endif // REDBOOK_WMI_BUFFERS_MIN < 3 // // perform my own pausing to prevent stuttering // if (DeviceExtension->Thread.PendingStream <= 3 && DeviceExtension->Buffer.Paused == 0) { // // only one buffer (or less) was pending play, // so pause the output to prevent horrible // stuttering. // since this is serialized from a thread, // can set a simple boolean in the extension // KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugDigitalS, "[redbook] " "Stream => Pausing, few buffers pending\n")); if (DeviceExtension->Buffer.FirstPause == 0) { RedBookLogError(DeviceExtension, REDBOOK_ERR_INSUFFICIENT_DATA_STREAM_PAUSED, STATUS_SUCCESS ); InterlockedIncrement(&DeviceExtension->WmiPerf.StreamPausedCount); } else { DeviceExtension->Buffer.FirstPause = 0; } DeviceExtension->Buffer.Paused = 1; SetNextDeviceState(DeviceExtension, KSSTATE_PAUSE); } else if (DeviceExtension->Buffer.Paused == 1 && DeviceExtension->Thread.PendingStream == DeviceExtension->WmiData.NumberOfBuffers ) { ULONG i; // // are now using the maximum number of buffers, // all pending stream. this allows smooth play again. // KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugDigitalS, "[redbook] " "Stream => Resuming, %d buffers pending\n", DeviceExtension->WmiData.NumberOfBuffers)); DeviceExtension->Buffer.Paused = 0; // // prevent these statistics from being added. // for (i=0;iWmiData.NumberOfBuffers;i++) { (DeviceExtension->Buffer.Contexts + i)->TimeReadSent.QuadPart = 0; } // // let the irps go! // SetNextDeviceState(DeviceExtension, KSSTATE_RUN); } // end of stutter prevention #endif // REDBOOK_PERFORM_STUTTER_CONTROL // // get perf counters at last possible second // KeQueryTickCount(&Context->TimeStreamSent); IoSetCompletionRoutine(Context->Irp, RedBookStreamCompletion, Context, TRUE, TRUE, TRUE); IoCallDriver(DeviceExtension->Stream.PinDeviceObject, Context->Irp); return; } NTSTATUS RedBookStreamCompletion( PVOID UnusableParameter, PIRP Irp, PREDBOOK_COMPLETION_CONTEXT Context ) /*++ Routine Description: Arguments: DeviceObject - CDROM class driver object or lower level filter Irp - pointer to buffer to send to KS must check error to increment/clear error count Context - sector of disk (ordered number) Return Value: status --*/ { PREDBOOK_DEVICE_EXTENSION deviceExtension = Context->DeviceExtension; KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugDigitalS, "[redbook] " "Stream => Completed Irp %p\n", Irp)); KeQueryTickCount(&Context->TimeReadReady); Context->Reason = REDBOOK_CC_STREAM_COMPLETE; ExInterlockedInsertTailList(&deviceExtension->Thread.DigitalList, &Context->ListEntry, &deviceExtension->Thread.DigitalLock); KeSetEvent(deviceExtension->Thread.Events[EVENT_DIGITAL], IO_CD_ROM_INCREMENT, FALSE); // // safe to release it since we wait for thread termination // IoReleaseRemoveLock(&deviceExtension->RemoveLock, Context->Irp); return STATUS_MORE_PROCESSING_REQUIRED; } #if DBG VOID ValidateCdromState(ULONG State) { ULONG temp; if (State == 0) { ASSERT(!"Invalid Cdrom State"); } else if (TEST_FLAG(State, ~CD_MASK_ALL)) { ASSERT(!"Invalid Cdrom State"); } temp = State & CD_MASK_TEMP; if (temp & (temp - 1)) { // see if zero or one bits are set ASSERT(!"Invalid Cdrom State"); } temp = State & CD_MASK_STATE; if (temp == 0) { // dis-allow zero bits for STATE ASSERT(!"Invalid Cdrom State"); } else if (temp & (temp - 1)) { // see if zero or one bits are set ASSERT(!"Invalid Cdrom State"); } return; } #else VOID ValidateCdromState(ULONG State) {return;} #endif ULONG GetCdromState( PREDBOOK_DEVICE_EXTENSION DeviceExtension ) { // // this routine may be called by anyone, whether in the thread's // context or not. setting the state is restricted, however. // ULONG state; state = InterlockedCompareExchange(&DeviceExtension->CDRom.StateNow,0,0); ValidateCdromState(state); return state; } LONG SetCdromState( IN PREDBOOK_DEVICE_EXTENSION DeviceExtension, IN LONG ExpectedOldState, IN LONG NewState ) { LONG trueOldState; PAGED_CODE(); VerifyCalledByThread(DeviceExtension); // ensure when set to: also setting: // CD_PAUSING CD_PLAYING // CD_STOPPING CD_PLAYING if (TEST_FLAG(NewState, CD_PAUSING)) { SET_FLAG(NewState, CD_PLAYING); } if (TEST_FLAG(NewState, CD_STOPPING)) { SET_FLAG(NewState, CD_PLAYING); } ValidateCdromState(ExpectedOldState); ValidateCdromState(NewState); //attempt to change it trueOldState = InterlockedCompareExchange( &DeviceExtension->CDRom.StateNow, NewState, ExpectedOldState ); ASSERTMSG("State set outside of thread", trueOldState == ExpectedOldState); // // see if an event should be fired, volume set, and/or // stream state set // if (ExpectedOldState == NewState) { // // if state is not changing, don't do anything // KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Setting state to same as expected?! %x == %x\n", ExpectedOldState, NewState)); } else if (TEST_FLAG(ExpectedOldState, CD_MASK_TEMP)) { // // should not go from temp state to temp state // ASSERT(!TEST_FLAG(NewState, CD_MASK_TEMP)); // // ioctl is being processed, and state is no longer // in a temp state, so should process the ioctl again // KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "SetState => EVENT_COMPLETE should be set soon " "for %p\n", DeviceExtension->Thread.IoctlCurrent)); } else if (TEST_FLAG(NewState, CD_MASK_TEMP)) { // // going to either pausing or stopping, both of which must // be specially handled by stopping the KS stream also. // KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "SetState => %s, setting device state " "to KSSTATE_STOP\n", (TEST_FLAG(NewState, CD_STOPPING) ? "STOP" : "PAUSE"))); SetNextDeviceState(DeviceExtension, KSSTATE_STOP); } else if (TEST_FLAG(NewState, CD_PAUSED)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "SetState => Finishing a PAUSE operation\n")); } else if (TEST_FLAG(NewState, CD_PLAYING)) { ULONG i; PREDBOOK_COMPLETION_CONTEXT context; ASSERT(NewState == CD_PLAYING); KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "SetState => Starting a PLAY operation\n")); // // not same state, not from temp state, // so must either be paused or stopped. // ASSERT(TEST_FLAG(ExpectedOldState,CD_STOPPED) || TEST_FLAG(ExpectedOldState,CD_PAUSED)); // // set some deviceextension stuff // RtlZeroMemory(&DeviceExtension->WmiPerf, sizeof(REDBOOK_WMI_PERF_DATA)); DeviceExtension->CDRom.ReadErrors = 0; DeviceExtension->CDRom.StreamErrors = 0; DeviceExtension->Buffer.Paused = 0; DeviceExtension->Buffer.FirstPause = 1; DeviceExtension->Buffer.IndexToRead = 0; DeviceExtension->Buffer.IndexToStream = 0; // // reset the buffer state // ASSERT(DeviceExtension->Buffer.Contexts); context = DeviceExtension->Buffer.Contexts; for (i=0; iWmiData.NumberOfBuffers;i++) { *(DeviceExtension->Buffer.ReadOk_X + i) = 0; *(DeviceExtension->Buffer.StreamOk_X + i) = 0; context->Reason = REDBOOK_CC_READ; context->Irp->IoStatus.Status = STATUS_SUCCESS; ExInterlockedInsertTailList(&DeviceExtension->Thread.DigitalList, &context->ListEntry, &DeviceExtension->Thread.DigitalLock); context++; // pointer arithmetic } context = NULL; // // start the digital playback // SetNextDeviceState(DeviceExtension, KSSTATE_RUN); RedBookKsSetVolume(DeviceExtension); KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "SetState => Setting DIGITAL event\n")); KeSetEvent(DeviceExtension->Thread.Events[EVENT_DIGITAL], IO_CD_ROM_INCREMENT, FALSE); } else { // // ReadQ Channel or some such nonsense // } return GetCdromState(DeviceExtension); } VOID RedBookDeallocatePlayResources( PREDBOOK_DEVICE_EXTENSION DeviceExtension ) { PREDBOOK_COMPLETION_CONTEXT context; ULONG i; BOOLEAN freedSomething = FALSE; PAGED_CODE(); VerifyCalledByThread(DeviceExtension); #if DBG { ULONG state = GetCdromState(DeviceExtension); ASSERT(!TEST_FLAG(state, CD_PLAYING) && !TEST_FLAG(state, CD_PAUSED)); } #endif // // free all resources // if (DeviceExtension->Buffer.StreamOk_X) { freedSomething = TRUE; ExFreePool(DeviceExtension->Buffer.StreamOk_X); DeviceExtension->Buffer.StreamOk_X = NULL; } if (DeviceExtension->Buffer.ReadOk_X) { freedSomething = TRUE; ExFreePool(DeviceExtension->Buffer.ReadOk_X); DeviceExtension->Buffer.ReadOk_X = NULL; } if (DeviceExtension->Buffer.Contexts) { context = DeviceExtension->Buffer.Contexts; for (i=0;iWmiData.NumberOfBuffers;i++) { if (context->Irp) { IoFreeIrp(context->Irp); } if (context->Mdl) { IoFreeMdl(context->Mdl); } context++; // pointer arithmetic } context = NULL; freedSomething = TRUE; ExFreePool(DeviceExtension->Buffer.Contexts); DeviceExtension->Buffer.Contexts = NULL; } if (DeviceExtension->Buffer.SilentMdl) { freedSomething = TRUE; IoFreeMdl(DeviceExtension->Buffer.SilentMdl); DeviceExtension->Buffer.SilentMdl = NULL; } if (DeviceExtension->Buffer.SkipBuffer) { freedSomething = TRUE; ExFreePool(DeviceExtension->Buffer.SkipBuffer); DeviceExtension->Buffer.SkipBuffer = NULL; } if (DeviceExtension->Thread.CheckVerifyIrp) { PIRP irp = DeviceExtension->Thread.CheckVerifyIrp; freedSomething = TRUE; if (irp->MdlAddress) { IoFreeMdl(irp->MdlAddress); } if (irp->AssociatedIrp.SystemBuffer) { ExFreePool(irp->AssociatedIrp.SystemBuffer); } IoFreeIrp(DeviceExtension->Thread.CheckVerifyIrp); DeviceExtension->Thread.CheckVerifyIrp = NULL; } if (DeviceExtension->Stream.PinFileObject) { freedSomething = TRUE; CloseSysAudio(DeviceExtension); } if (DeviceExtension->CDRom.Toc) { freedSomething = TRUE; ExFreePool(DeviceExtension->CDRom.Toc); DeviceExtension->CDRom.Toc = NULL; } if (freedSomething) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "DeallocatePlay => Deallocated play resources\n")); } return; } BOOLEAN RedBookArePlayResourcesAllocated( PREDBOOK_DEVICE_EXTENSION DeviceExtension ) // // just choose one, since it's all done in a batch in // one thread context it's always safe. // { PAGED_CODE(); VerifyCalledByThread(DeviceExtension); return (DeviceExtension->Stream.PinFileObject != NULL); } NTSTATUS RedBookAllocatePlayResources( PREDBOOK_DEVICE_EXTENSION DeviceExtension ) // // allocate resources if they are not already allocated // { PREDBOOK_COMPLETION_CONTEXT context; NTSTATUS status; KEVENT event; ULONG numBufs; ULONG numSectors; ULONG bufSize; ULONG i; CCHAR maxStack; BOOLEAN sysAudioOpened = FALSE; PAGED_CODE(); VerifyCalledByThread(DeviceExtension); // // NOTE: // The call to update the mixer Id may de-allocate all play // resources, since the stack sizes may change. it must // therefore be the first call within this routine. // if (DeviceExtension->Stream.MixerPinId == -1) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => No mixer set?\n")); return STATUS_UNSUCCESSFUL; } if (DeviceExtension->Buffer.Contexts != NULL) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => Using existing resources\n")); return STATUS_SUCCESS; } #if DBG { ULONG state = GetCdromState(DeviceExtension); ASSERT(!TEST_FLAG(state, CD_PLAYING) && !TEST_FLAG(state, CD_PAUSED)); } #endif ASSERT(DeviceExtension->Buffer.Contexts == NULL); ASSERT(DeviceExtension->Buffer.SkipBuffer == NULL); numBufs = DeviceExtension->WmiData.NumberOfBuffers; numSectors = DeviceExtension->WmiData.SectorsPerRead; bufSize = RAW_SECTOR_SIZE * numSectors; TRY { ASSERT(DeviceExtension->Stream.MixerPinId != -1); // // may need to allocate the CheckVerifyIrp // { PIO_STACK_LOCATION irpStack; PIRP irp; irp = DeviceExtension->Thread.CheckVerifyIrp; if (irp == NULL) { irp = IoAllocateIrp( (CCHAR)(DeviceExtension->SelfDeviceObject->StackSize+1), FALSE); } if (irp == NULL) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => No CheckVerifyIrp\n")); status = STATUS_NO_MEMORY; LEAVE; } irp->AssociatedIrp.SystemBuffer = irp->MdlAddress = NULL; irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPoolCacheAligned, sizeof(ULONG), TAG_CV_BUFFER); if (irp->AssociatedIrp.SystemBuffer == NULL) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => No CheckVerify Buffer\n")); status = STATUS_NO_MEMORY; LEAVE; } irp->MdlAddress = IoAllocateMdl(irp->AssociatedIrp.SystemBuffer, sizeof(ULONG), FALSE, FALSE, NULL); if (irp->MdlAddress == NULL) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => No CheckVerify Mdl\n")); status = STATUS_NO_MEMORY; LEAVE; } MmBuildMdlForNonPagedPool(irp->MdlAddress); IoSetNextIrpStackLocation(irp); irpStack = IoGetCurrentIrpStackLocation(irp); irpStack->MajorFunction = IRP_MJ_DEVICE_CONTROL; irpStack->Parameters.DeviceIoControl.InputBufferLength = 0; irpStack->Parameters.DeviceIoControl.OutputBufferLength = sizeof(ULONG); irpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_CDROM_CHECK_VERIFY; DeviceExtension->Thread.CheckVerifyIrp = irp; } // // connect to sysaudio // { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => Preparing to open sysaudio\n")); ASSERT(DeviceExtension->Stream.MixerPinId != MAXULONG); status = OpenSysAudio(DeviceExtension); if ( !NT_SUCCESS(status) ) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugWarning, "[redbook] " "AllocatePlay !! Unable to open sysaudio %lx\n", status)); LEAVE; } // else the pin is open sysAudioOpened = TRUE; } maxStack = MAX(DeviceExtension->TargetDeviceObject->StackSize, DeviceExtension->Stream.PinDeviceObject->StackSize); DeviceExtension->Buffer.MaxIrpStack = maxStack; KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocateePlay => Stacks: Cdrom %x Stream %x\n", DeviceExtension->TargetDeviceObject->StackSize, DeviceExtension->Stream.PinDeviceObject->StackSize)); KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocateePlay => Allocating %x stacks per irp\n", maxStack)); DeviceExtension->Buffer.SkipBuffer = ExAllocatePoolWithTag(NonPagedPoolCacheAligned, bufSize * (numBufs + 1), TAG_BUFFER); if (DeviceExtension->Buffer.SkipBuffer == NULL) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => No Skipbuffer\n")); status = STATUS_NO_MEMORY; LEAVE; } RtlZeroMemory(DeviceExtension->Buffer.SkipBuffer, bufSize * (numBufs + 1)); DeviceExtension->Buffer.Contexts = ExAllocatePoolWithTag(NonPagedPool, sizeof(REDBOOK_COMPLETION_CONTEXT) * numBufs, TAG_CC); if (DeviceExtension->Buffer.Contexts == NULL) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => No Contexts\n")); status = STATUS_NO_MEMORY; LEAVE; } RtlZeroMemory(DeviceExtension->Buffer.Contexts, sizeof(REDBOOK_COMPLETION_CONTEXT) * numBufs); context = DeviceExtension->Buffer.Contexts; for (i=0;iDeviceExtension = DeviceExtension; context->Reason = REDBOOK_CC_READ; context->Index = i; context->Buffer = DeviceExtension->Buffer.SkipBuffer + (bufSize * i); // pointer arithmetic of UCHARS // // allocate irp, mdl // context->Irp = IoAllocateIrp(maxStack, FALSE); context->Mdl = IoAllocateMdl(context->Buffer, bufSize, FALSE, FALSE, NULL); if (context->Irp == NULL || context->Mdl == NULL) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => Irp/Mdl %x failed\n", i)); status = STATUS_NO_MEMORY; LEAVE; } MmBuildMdlForNonPagedPool(context->Mdl); context++; // pointer arithmetic of CONTEXTS } context = NULL; // safety // // allocated above as part of SkipBuffer // DeviceExtension->Buffer.SilentBuffer = DeviceExtension->Buffer.SkipBuffer + (bufSize * numBufs); DeviceExtension->Buffer.SilentMdl = IoAllocateMdl(DeviceExtension->Buffer.SkipBuffer, bufSize, FALSE, FALSE, NULL); if (DeviceExtension->Buffer.SilentMdl == NULL) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => Silent Mdl failed\n")); status = STATUS_NO_MEMORY; LEAVE; } DeviceExtension->Buffer.ReadOk_X = ExAllocatePoolWithTag(NonPagedPoolCacheAligned, sizeof(ULONG) * numBufs, TAG_READX); if (DeviceExtension->Buffer.ReadOk_X == NULL) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => ReadOk_X failed\n")); status = STATUS_NO_MEMORY; LEAVE; } RtlZeroMemory(DeviceExtension->Buffer.ReadOk_X, sizeof(ULONG) * numBufs); DeviceExtension->Buffer.StreamOk_X = ExAllocatePoolWithTag(NonPagedPoolCacheAligned, sizeof(ULONG) * numBufs, TAG_STREAMX); if (DeviceExtension->Buffer.StreamOk_X == NULL) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => ReadOk_X failed\n")); status = STATUS_NO_MEMORY; LEAVE; } RtlZeroMemory(DeviceExtension->Buffer.StreamOk_X, sizeof(ULONG) * numBufs); MmBuildMdlForNonPagedPool(DeviceExtension->Buffer.SilentMdl); KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "AllocatePlay => Allocated All Resources\n")); status = STATUS_SUCCESS; } FINALLY { if (!NT_SUCCESS(status)) { RedBookDeallocatePlayResources(DeviceExtension); return status; } } // // else all resources allocated // return STATUS_SUCCESS; } NTSTATUS RedBookCacheToc( PREDBOOK_DEVICE_EXTENSION DeviceExtension ) { PCDROM_TOC newToc; PIRP irp; ULONG mediaChangeCount; IO_STATUS_BLOCK ioStatus; KEVENT event; NTSTATUS status; PAGED_CODE(); VerifyCalledByThread(DeviceExtension); // // cache the number of times the media has changed // use this to prevent redundant reads of the toc // and to return Q channel info during playback // KeInitializeEvent(&event, SynchronizationEvent, FALSE); // // first get the mediaChangeCount to see if we've already // cached this toc // irp = IoBuildDeviceIoControlRequest(IOCTL_CDROM_CHECK_VERIFY, DeviceExtension->TargetDeviceObject, NULL, 0, &mediaChangeCount, sizeof(ULONG), FALSE, &event, &ioStatus); if (irp == NULL) { return STATUS_NO_MEMORY; } SET_FLAG(IoGetNextIrpStackLocation(irp)->Flags, SL_OVERRIDE_VERIFY_VOLUME); status = IoCallDriver(DeviceExtension->TargetDeviceObject, irp); if (status == STATUS_PENDING) { KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); status = ioStatus.Status; } if (!NT_SUCCESS(status)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "CacheToc !! CheckVerify failed %lx\n", status)); return status; } // // read TOC only we don't have the correct copy cached // if (DeviceExtension->CDRom.Toc != NULL && DeviceExtension->CDRom.CheckVerify == mediaChangeCount) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "CacheToc => Using cached toc\n")); return STATUS_SUCCESS; } // // Allocate for the cached TOC // newToc = ExAllocatePoolWithTag(NonPagedPoolCacheAligned, sizeof(CDROM_TOC), TAG_TOC); if (newToc == NULL) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "CacheToc !! Unable to allocate new TOC\n")); return STATUS_NO_MEMORY; } KeClearEvent(&event); irp = IoBuildDeviceIoControlRequest(IOCTL_CDROM_READ_TOC, DeviceExtension->TargetDeviceObject, NULL, 0, newToc, sizeof(CDROM_TOC), FALSE, &event, &ioStatus); if (irp == NULL) { ExFreePool(newToc); newToc = NULL; return STATUS_NO_MEMORY; } SET_FLAG(IoGetNextIrpStackLocation(irp)->Flags, SL_OVERRIDE_VERIFY_VOLUME); status = IoCallDriver(DeviceExtension->TargetDeviceObject, irp); if (status == STATUS_PENDING) { KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); status = ioStatus.Status; } // // set the new toc, or if error free it // return the status // if (!NT_SUCCESS(status)) { ExFreePool(newToc); newToc = NULL; KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "CacheToc !! Failed to get TOC %lx\n", status)); } else { if (DeviceExtension->CDRom.Toc) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugAllocPlay, "[redbook] " "CacheToc => Freeing old toc %p\n", DeviceExtension->CDRom.Toc)); ExFreePool(DeviceExtension->CDRom.Toc); } DeviceExtension->CDRom.Toc = newToc; DeviceExtension->CDRom.CheckVerify = mediaChangeCount; } return status; } VOID RedBookThreadDigitalHandler( IN PREDBOOK_DEVICE_EXTENSION DeviceExtension, IN PLIST_ENTRY ListEntry ) // // DECREMENT StreamPending/ReadPending if it's a completion // SET stopped, error, etc. states // INCREMENT StreamPending/ReadPending if it's to be sent again // { PREDBOOK_COMPLETION_CONTEXT Context; ULONG index; ULONG mod; ULONG state; PAGED_CODE(); VerifyCalledByThread(DeviceExtension); ASSERT(DeviceExtension->WmiData.NumberOfBuffers); ASSERT(DeviceExtension->Buffer.SkipBuffer); // // Increment/Decrement PendingRead/PendingStream // Context = CONTAINING_RECORD(ListEntry, REDBOOK_COMPLETION_CONTEXT, ListEntry); index = Context->Index; mod = DeviceExtension->WmiData.NumberOfBuffers; state = GetCdromState(DeviceExtension); // // decrement the number reading/streaming if needed // if (Context->Reason == REDBOOK_CC_READ_COMPLETE) { if (!NT_SUCCESS(Context->Irp->IoStatus.Status)) { if (IoIsErrorUserInduced(Context->Irp->IoStatus.Status)) { DeviceExtension->CDRom.ReadErrors = REDBOOK_MAX_CONSECUTIVE_ERRORS; } else { DeviceExtension->CDRom.ReadErrors++; } } else { DeviceExtension->CDRom.ReadErrors = 0; } DeviceExtension->Thread.PendingRead--; Context->Reason = REDBOOK_CC_STREAM; } else if (Context->Reason == REDBOOK_CC_STREAM_COMPLETE) { if (!NT_SUCCESS(Context->Irp->IoStatus.Status)) { DeviceExtension->CDRom.StreamErrors++; } else { DeviceExtension->CDRom.StreamErrors = 0; } // // if stream succeeded OR we are _NOT_ stopping audio, // increment FinishedStreaming and save wmi stats // if (NT_SUCCESS(Context->Irp->IoStatus.Status) || !TEST_FLAG(state, CD_MASK_TEMP)) { DeviceExtension->CDRom.FinishedStreaming += DeviceExtension->WmiData.SectorsPerRead; AddWmiStats(DeviceExtension, Context); } DeviceExtension->Thread.PendingStream--; Context->Reason = REDBOOK_CC_READ; } if (DeviceExtension->CDRom.StreamErrors >= REDBOOK_MAX_CONSECUTIVE_ERRORS && !TEST_FLAG(state, CD_MASK_TEMP)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Digital => Too many stream errors, beginning STOP\n")); ASSERT(!TEST_FLAG(state, CD_STOPPED)); ASSERT(!TEST_FLAG(state, CD_PAUSED)); state = SetCdromState(DeviceExtension, state, CD_STOPPING); } if (DeviceExtension->CDRom.ReadErrors >= REDBOOK_MAX_CONSECUTIVE_ERRORS && !TEST_FLAG(state, CD_MASK_TEMP)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Digital => Too many read errors, beginning STOP\n")); state = SetCdromState(DeviceExtension, state, CD_STOPPING); } // // if stopping/pausing/etc, and no reads/streams are pending, // set the new state and return. // the while() loop in the thread will do the right thing // when there is no more outstanding io--it will call the ioctl // completion handler to do whatever post-processing is needed. // if (TEST_FLAG(state, CD_MASK_TEMP)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Digital => Temp state %x, not continuing (%d, %d)\n", state, DeviceExtension->Thread.PendingRead, DeviceExtension->Thread.PendingStream )); if (DeviceExtension->Thread.PendingRead == 0 && DeviceExtension->Thread.PendingStream == 0) { // // Set NextToRead and NextToStream to FinishedStreaming // DeviceExtension->CDRom.NextToRead = DeviceExtension->CDRom.NextToStream = DeviceExtension->CDRom.FinishedStreaming; if (TEST_FLAG(state, CD_PAUSING)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Digital => completing PAUSED\n")); state = SetCdromState(DeviceExtension, state, CD_PAUSED); } else if (TEST_FLAG(state, CD_STOPPING)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Digital => completing STOPPED\n")); state = SetCdromState(DeviceExtension, state, CD_STOPPED); } else { ASSERT(!"Unknown state?"); } if (DeviceExtension->Thread.IoctlCurrent) { KeSetEvent(DeviceExtension->Thread.Events[EVENT_COMPLETE], IO_CD_ROM_INCREMENT, FALSE); } } return; } if (DeviceExtension->CDRom.NextToRead >= DeviceExtension->CDRom.EndPlay && Context->Reason == REDBOOK_CC_READ) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Digital => End play, ignoring READ\n")); if (DeviceExtension->Thread.PendingRead == 0 && DeviceExtension->Thread.PendingStream == 0) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Digital => All IO done, setting STOPPED\n")); state = SetCdromState(DeviceExtension, state, CD_STOPPED); } return; } if (DeviceExtension->CDRom.NextToStream >= DeviceExtension->CDRom.EndPlay && Context->Reason == REDBOOK_CC_STREAM) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Digital => End play, ignoring STREAM\n")); if (DeviceExtension->Thread.PendingRead == 0 && DeviceExtension->Thread.PendingStream == 0) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Digital => All IO done, setting STOPPED\n")); state = SetCdromState(DeviceExtension, state, CD_STOPPED); } return; } switch(Context->Reason) { case REDBOOK_CC_READ: { // mark this buffer as off the queue/usable ASSERT(DeviceExtension->Buffer.ReadOk_X[index] == 0); DeviceExtension->Buffer.ReadOk_X[index] = 1; if (index != DeviceExtension->Buffer.IndexToRead) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Digital => Delaying read, index %x\n", index)); return; } if (DeviceExtension->CDRom.NextToRead > DeviceExtension->CDRom.EndPlay) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Digital => End of Play\n")); return; } for (index = Context->Index; DeviceExtension->Buffer.ReadOk_X[index] != 0; index = (index + 1) % mod) { // mark this buffer as in use BEFORE attempting to read DeviceExtension->Buffer.ReadOk_X[index] = 0; DeviceExtension->Thread.PendingRead++; RedBookReadRaw(DeviceExtension, &DeviceExtension->Buffer.Contexts[index]); // increment where reading from AFTER attempting to read DeviceExtension->CDRom.NextToRead += DeviceExtension->WmiData.SectorsPerRead; // inc/mod the index AFTER attempting to read DeviceExtension->Buffer.IndexToRead++; DeviceExtension->Buffer.IndexToRead %= mod; } break; } case REDBOOK_CC_STREAM: { // mark this buffer as off the queue/usable ASSERT(DeviceExtension->Buffer.StreamOk_X[index] == 0); DeviceExtension->Buffer.StreamOk_X[index] = 1; if (index != DeviceExtension->Buffer.IndexToStream) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugThread, "[redbook] " "Delaying stream of index %x\n", index)); return; } for (index = Context->Index; DeviceExtension->Buffer.StreamOk_X[index] != 0; index = (index + 1) % mod) { // mark this buffer as in use BEFORE attempting to read DeviceExtension->Buffer.StreamOk_X[index] = 0; DeviceExtension->Thread.PendingStream++; RedBookStream(DeviceExtension, &DeviceExtension->Buffer.Contexts[index]); // increment where reading from AFTER attempting to read DeviceExtension->CDRom.NextToStream += DeviceExtension->WmiData.SectorsPerRead; // inc/mod the index AFTER attempting to read DeviceExtension->Buffer.IndexToStream++; DeviceExtension->Buffer.IndexToStream %= mod; } break; } default: { ASSERT(!"Unhandled Context->Reason\n"); break; } } // end switch (Context->Reason) return; } VOID AddWmiStats( PREDBOOK_DEVICE_EXTENSION DeviceExtension, PREDBOOK_COMPLETION_CONTEXT Context ) { KIRQL oldIrql; ULONG timeIncrement; if (Context->TimeReadSent.QuadPart == 0) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugWmi, "[redbook] " "Not Saving WMI Stats for REASON:\n")); KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugWmi, "[redbook] " "(ReadError, StreamError, Paused?)\n")); return; } timeIncrement = KeQueryTimeIncrement(); // amount of time for each tick KeAcquireSpinLock(&DeviceExtension->WmiPerfLock, &oldIrql); DeviceExtension->WmiPerf.TimeReadDelay += (Context->TimeReadSent.QuadPart - Context->TimeReadReady.QuadPart ) * timeIncrement; DeviceExtension->WmiPerf.TimeReading += (Context->TimeStreamReady.QuadPart - Context->TimeReadSent.QuadPart ) * timeIncrement; DeviceExtension->WmiPerf.TimeStreamDelay += (Context->TimeStreamSent.QuadPart - Context->TimeStreamReady.QuadPart ) * timeIncrement; DeviceExtension->WmiPerf.TimeStreaming += (Context->TimeReadReady.QuadPart - Context->TimeStreamSent.QuadPart ) * timeIncrement; DeviceExtension->WmiPerf.DataProcessed += DeviceExtension->WmiData.SectorsPerRead * RAW_SECTOR_SIZE; KeReleaseSpinLock( &DeviceExtension->WmiPerfLock, oldIrql ); return; } //////////////////////////////////////////////////////////////////////////////// VOID RedBookCheckForAudioDeviceRemoval( PREDBOOK_DEVICE_EXTENSION DeviceExtension ) { ULONG state = GetCdromState(DeviceExtension); PAGED_CODE(); VerifyCalledByThread(DeviceExtension); KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugSysaudio, "[redbook] " "STCheckForRemoval => Checking if audio device changed\n")); if (TEST_FLAG(state, CD_MASK_TEMP)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugSysaudio, "[redbook] " "STCheckForRemoval => delaying -- temp state\n")); return; } if (DeviceExtension->Stream.UpdateMixerPin == 0) { return; } KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugSysaudio, "[redbook] " "STCheckForRemoval => Audio Device may have changed\n")); if (TEST_FLAG(state, CD_PLAYING)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugSysaudio, "[redbook] " "STCheckForRemoval => playing, so stopping\n")); state = SetCdromState(DeviceExtension, state, CD_STOPPING); return; } if (TEST_FLAG(state, CD_STOPPED)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugSysaudio, "[redbook] " "STCheckForRemoval => stopped, updating\n")); } else if (TEST_FLAG(state, CD_PAUSED)) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugSysaudio, "[redbook] " "STCheckForRemoval => paused, updating\n")); // // ISSUE-2000/5/24-henrygab - may not need to stop // unless mixer becomes -1, // since we could then send // to the new audio device. // state = SetCdromState(DeviceExtension, state, CD_STOPPED); } ASSERT(TEST_FLAG(GetCdromState(DeviceExtension), CD_STOPPED)); // // set the value to zero (iff the value was one) // check if the value was one, and if so, update the mixerpin // if (InterlockedCompareExchange(&DeviceExtension->Stream.UpdateMixerPin, 0, 1) == 1) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugSysaudio, "[redbook] " "STCheckForRemoval => Updating MixerPin\n")); // // free any in-use play resources // RedBookDeallocatePlayResources(DeviceExtension); if (DeviceExtension->Stream.MixerPinId != -1) { UninitializeVirtualSource(DeviceExtension); } InitializeVirtualSource(DeviceExtension); if (DeviceExtension->Stream.MixerPinId == -1) { KdPrintEx((DPFLTR_REDBOOK_ID, RedbookDebugSysaudio, "[redbook] " "STCheckForRemoval => Update of mixerpin " "failed -- will retry later\n")); InterlockedExchange(&DeviceExtension->Stream.UpdateMixerPin, 1); return; } } return; }