Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2166 lines
67 KiB

/*++
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 <scsi.h> // for SetKnownGoodDrive()
#include <stdio.h> // 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;i<DeviceExtension->WmiData.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; i<DeviceExtension->WmiData.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;i<DeviceExtension->WmiData.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;i<numBufs;i++) {
context->DeviceExtension = 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;
}