windows-nt/Source/XPSP1/NT/drivers/wdm/audio/legacy/wdmaud.sys/wave.c
2020-09-26 16:20:57 +08:00

2217 lines
66 KiB
C

/****************************************************************************
*
* wave.c
*
* Wave routines for wdmaud.sys
*
* Copyright (C) Microsoft Corporation, 1997 - 1999 All Rights Reserved.
*
* History
* S.Mohanraj (MohanS)
* M.McLaughlin (MikeM)
* 5-19-97 - Noel Cross (NoelC)
*
***************************************************************************/
#include "wdmsys.h"
//
// This is just a scratch location that is never used for anything
// but a parameter to core functions.
//
IO_STATUS_BLOCK gIoStatusBlock ;
VOID
SetVolumeDpc(
IN PKDPC pDpc,
IN PVOID DefferedContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
);
VOID
SetVolumeWorker(
IN PWAVEDEVICE pDevice,
IN PVOID pNotUsed
);
VOID
WaitForOutStandingIo(
IN PWAVEDEVICE pWaveDevice,
IN PWAVE_PIN_INSTANCE pCurWavePin
);
//
// Check whether the waveformat is supported by kmixer
// purpose of this is to decide whether to use WaveQueued
// OR Standard Streaming
//
BOOL
PcmWaveFormat(
LPWAVEFORMATEX lpFormat
)
{
PWAVEFORMATEXTENSIBLE pWaveExtended;
WORD wFormatTag;
PAGED_CODE();
if (lpFormat->wFormatTag == WAVE_FORMAT_PCM) {
return (TRUE);
}
if (lpFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
return (TRUE);
}
if (lpFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
pWaveExtended = (PWAVEFORMATEXTENSIBLE) lpFormat;
if (IS_VALID_WAVEFORMATEX_GUID(&pWaveExtended->SubFormat)) {
wFormatTag = EXTRACT_WAVEFORMATEX_ID(&pWaveExtended->SubFormat);
if (wFormatTag == WAVE_FORMAT_PCM) {
return (TRUE);
}
if (wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
return (TRUE);
}
}
}
return (FALSE);
}
BOOL
IsValidFormatTag(
PKSDATARANGE_AUDIO pDataRange,
LPWAVEFORMATEX lpFormat
)
{
PAGED_CODE();
//
// See if we have a majorformat and subformat that
// we want
//
if ( IsEqualGUID( &KSDATAFORMAT_TYPE_AUDIO,
&pDataRange->DataRange.MajorFormat) )
{
if (WAVE_FORMAT_EXTENSIBLE == lpFormat->wFormatTag)
{
PWAVEFORMATEXTENSIBLE lpFormatExtensible;
lpFormatExtensible = (PWAVEFORMATEXTENSIBLE)lpFormat;
if ( IsEqualGUID( &pDataRange->DataRange.SubFormat,
&lpFormatExtensible->SubFormat) )
{
return TRUE;
}
}
else
{
if ( (EXTRACT_WAVEFORMATEX_ID(&pDataRange->DataRange.SubFormat) ==
lpFormat->wFormatTag) )
{
return TRUE;
}
}
}
DPF(DL_TRACE|FA_WAVE,("Invalid Format Tag") );
return FALSE;
}
BOOL
IsValidSampleFrequency(
PKSDATARANGE_AUDIO pDataRange,
DWORD nSamplesPerSec
)
{
PAGED_CODE();
//
// See if this datarange support the requested frequency
//
if (pDataRange->MinimumSampleFrequency <= nSamplesPerSec &&
pDataRange->MaximumSampleFrequency >= nSamplesPerSec)
{
return TRUE;
}
else
{
DPF(DL_MAX|FA_WAVE,("Invalid Sample Frequency") );
return FALSE;
}
}
BOOL
IsValidBitsPerSample(
PKSDATARANGE_AUDIO pDataRange,
LPWAVEFORMATEX lpFormat
)
{
PAGED_CODE();
//
// See if this datarange support the requested frequency
//
if (pDataRange->MinimumBitsPerSample <= lpFormat->wBitsPerSample &&
pDataRange->MaximumBitsPerSample >= lpFormat->wBitsPerSample)
{
if ( (lpFormat->wFormatTag == WAVE_FORMAT_PCM) &&
(lpFormat->wBitsPerSample > 32) )
{
DPF(DL_TRACE|FA_WAVE,("Invalid BitsPerSample") );
return FALSE;
}
else if ( (lpFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) &&
(lpFormat->wBitsPerSample != 32) )
{
DPF(DL_TRACE|FA_WAVE,("Invalid BitsPerSample") );
return FALSE;
}
return TRUE;
}
else
{
DPF(DL_TRACE|FA_WAVE,("Invalid BitsPerSample") );
return FALSE;
}
}
BOOL
IsValidChannels(
PKSDATARANGE_AUDIO pDataRange,
LPWAVEFORMATEX lpFormat
)
{
PAGED_CODE();
//
// See if this datarange support the requested frequency
//
if (pDataRange->MaximumChannels >= lpFormat->nChannels)
{
if ( ( (lpFormat->wFormatTag == WAVE_FORMAT_PCM) ||
(lpFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) ) &&
(lpFormat->nChannels > 2) )
{
DPF(DL_TRACE|FA_WAVE,("Invalid Channel") );
return FALSE;
}
return TRUE;
}
else
{
DPF(DL_TRACE|FA_WAVE,("Invalid Channel") );
return FALSE;
}
}
NTSTATUS
OpenWavePin(
PWDMACONTEXT pWdmaContext,
ULONG DeviceNumber,
LPWAVEFORMATEX lpFormat,
HANDLE32 DeviceHandle,
DWORD dwFlags,
ULONG DataFlow // DataFlow is either in or out.
)
{
PWAVE_PIN_INSTANCE pNewWavePin = NULL;
PWAVE_PIN_INSTANCE pCurWavePin;
PKSPIN_CONNECT pConnect = NULL;
PKSDATAFORMAT_WAVEFORMATEX pWaveDataFormat;
ULONG RegionSize;
PCONTROLS_LIST pControlList = NULL;
ULONG Device;
ULONG PinId;
NTSTATUS Status = STATUS_INVALID_DEVICE_REQUEST;
PAGED_CODE();
//
// Let's do this quickly and get out of here
//
if (WAVE_FORMAT_QUERY & dwFlags)
{
PDATARANGES AudioDataRanges;
PKSDATARANGE_AUDIO pDataRange;
ULONG d;
//
// WaveOut call? If so, use waveout info
//
if( KSPIN_DATAFLOW_IN == DataFlow )
AudioDataRanges = pWdmaContext->WaveOutDevs[DeviceNumber].AudioDataRanges;
else
AudioDataRanges = pWdmaContext->WaveInDevs[DeviceNumber].AudioDataRanges;
pDataRange = (PKSDATARANGE_AUDIO)&AudioDataRanges->aDataRanges[0];
for(d = 0; d < AudioDataRanges->Count; d++)
{
if ( (IsValidFormatTag(pDataRange,lpFormat)) &&
(IsValidSampleFrequency(pDataRange,lpFormat->nSamplesPerSec)) &&
(IsValidBitsPerSample(pDataRange,lpFormat)) &&
(IsValidChannels(pDataRange,lpFormat)) )
{
//
// Found a good data range, successful query
//
Status = STATUS_SUCCESS;
break;
}
// Get the pointer to the next data range
(PUCHAR)pDataRange += ((pDataRange->DataRange.FormatSize +
FILE_QUAD_ALIGNMENT) & ~FILE_QUAD_ALIGNMENT);
}
goto exit;
}
//
// Need to allocate a pin instance for multiple wave
// opens on the same device
//
Status = AudioAllocateMemory_Fixed(sizeof(WAVE_PIN_INSTANCE),
TAG_Audi_PIN,
ZERO_FILL_MEMORY,
&pNewWavePin);
if(!NT_SUCCESS(Status))
{
goto exit;
}
//
// Copy the application supplied waveformat so we can
// use in the worker thread context. Don't need to zero
// memory because we copy into the structure below.
//
Status = AudioAllocateMemory_Fixed((lpFormat->wFormatTag == WAVE_FORMAT_PCM) ?
sizeof( PCMWAVEFORMAT ) :
sizeof( WAVEFORMATEX ) + lpFormat->cbSize,
TAG_AudF_FORMAT,
DEFAULT_MEMORY,
&pNewWavePin->lpFormat);
if(!NT_SUCCESS(Status))
{
AudioFreeMemory(sizeof(WAVE_PIN_INSTANCE),&pNewWavePin);
goto exit;
}
RtlCopyMemory( pNewWavePin->lpFormat,
lpFormat,
(lpFormat->wFormatTag == WAVE_FORMAT_PCM) ?
sizeof( PCMWAVEFORMAT ) :
sizeof( WAVEFORMATEX ) + lpFormat->cbSize);
pNewWavePin->DataFlow = DataFlow;
pNewWavePin->dwFlags = dwFlags;
pNewWavePin->DeviceNumber = DeviceNumber;
pNewWavePin->WaveHandle = DeviceHandle;
pNewWavePin->Next = NULL;
pNewWavePin->NumPendingIos = 0;
pNewWavePin->StoppingSource = FALSE;
pNewWavePin->PausingSource = FALSE;
pNewWavePin->dwSig = WAVE_PIN_INSTANCE_SIGNATURE;
if( KSPIN_DATAFLOW_IN == DataFlow )
pNewWavePin->pWaveDevice = &pWdmaContext->WaveOutDevs[DeviceNumber];
else
pNewWavePin->pWaveDevice = &pWdmaContext->WaveInDevs[DeviceNumber];
KeInitializeEvent ( &pNewWavePin->StopEvent,
SynchronizationEvent,
FALSE ) ;
KeInitializeEvent ( &pNewWavePin->PauseEvent,
SynchronizationEvent,
FALSE ) ;
KeInitializeSpinLock(&pNewWavePin->WavePinSpinLock);
if( KSPIN_DATAFLOW_IN == DataFlow )
{
if (NULL == pWdmaContext->WaveOutDevs[DeviceNumber].pWavePin)
{
pWdmaContext->WaveOutDevs[DeviceNumber].pWavePin = pNewWavePin;
} else {
for (pCurWavePin = pWdmaContext->WaveOutDevs[DeviceNumber].pWavePin;
pCurWavePin->Next != NULL; )
{
pCurWavePin = pCurWavePin->Next;
}
pCurWavePin->Next = pNewWavePin;
DPF(DL_TRACE|FA_WAVE, ("Opening another waveout pin"));
}
} else {
if (NULL == pWdmaContext->WaveInDevs[DeviceNumber].pWavePin)
{
pWdmaContext->WaveInDevs[DeviceNumber].pWavePin = pNewWavePin;
} else {
for (pCurWavePin = pWdmaContext->WaveInDevs[DeviceNumber].pWavePin;
pCurWavePin->Next != NULL; )
{
pCurWavePin = pCurWavePin->Next;
}
pCurWavePin->Next = pNewWavePin;
DPF(DL_TRACE|FA_WAVE, ("Opening another wavein pin"));
}
}
//
// We only support one client at a time.
//
ASSERT( !pNewWavePin->fGraphRunning );
//
// We need to allocate enough memory to handle the
// extended waveformat structure
//
if (WAVE_FORMAT_PCM == lpFormat->wFormatTag)
{
RegionSize = sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT_WAVEFORMATEX);
}
else
{
RegionSize = sizeof(KSPIN_CONNECT) +
sizeof(KSDATAFORMAT_WAVEFORMATEX) +
lpFormat->cbSize;
}
Status = AudioAllocateMemory_Fixed(RegionSize,
TAG_Audt_CONNECT,
ZERO_FILL_MEMORY,
&pConnect);
if(!NT_SUCCESS(Status))
{
DPF(DL_WARNING|FA_WAVE, ("pConnect not valid"));
goto exit;
}
pWaveDataFormat = (PKSDATAFORMAT_WAVEFORMATEX)(pConnect + 1);
//
// Use WAVE_QUEUED for PCM waveOut and Standard Streaming for WaveIn
// and non-PCM waveOut
//
if ( pNewWavePin->DataFlow == KSPIN_DATAFLOW_IN )
{
if (PcmWaveFormat(lpFormat)) { // if it is KMIXER supported waveformat
pConnect->Interface.Set = KSINTERFACESETID_Media;
pConnect->Interface.Id = KSINTERFACE_MEDIA_WAVE_QUEUED;
pNewWavePin->fWaveQueued = TRUE;
} else {
pConnect->Interface.Set = KSINTERFACESETID_Standard;
pConnect->Interface.Id = KSINTERFACE_STANDARD_STREAMING;
pNewWavePin->fWaveQueued = FALSE;
}
pConnect->Interface.Flags = 0;
PinId = pNewWavePin->pWaveDevice->PinId;
Device = pNewWavePin->pWaveDevice->Device;
} else {
pConnect->Interface.Set = KSINTERFACESETID_Standard;
pConnect->Interface.Id = KSINTERFACE_STANDARD_STREAMING;
pConnect->Interface.Flags = 0;
PinId = pNewWavePin->pWaveDevice->PinId;
Device = pNewWavePin->pWaveDevice->Device;
}
pConnect->Medium.Set = KSMEDIUMSETID_Standard;
pConnect->Medium.Id = KSMEDIUM_STANDARD_DEVIO;
pConnect->Medium.Flags = 0 ;
pConnect->Priority.PriorityClass = KSPRIORITY_NORMAL;
pConnect->Priority.PrioritySubClass = 1;
pWaveDataFormat->DataFormat.MajorFormat = KSDATAFORMAT_TYPE_AUDIO;
if (WAVE_FORMAT_EXTENSIBLE == lpFormat->wFormatTag)
{
PWAVEFORMATEXTENSIBLE lpFormatExtensible;
lpFormatExtensible = (PWAVEFORMATEXTENSIBLE)lpFormat;
pWaveDataFormat->DataFormat.SubFormat = lpFormatExtensible->SubFormat;
}
else
{
INIT_WAVEFORMATEX_GUID( &pWaveDataFormat->DataFormat.SubFormat,
lpFormat->wFormatTag );
}
pWaveDataFormat->DataFormat.Specifier = KSDATAFORMAT_SPECIFIER_WAVEFORMATEX;
pWaveDataFormat->DataFormat.Flags = 0 ;
pWaveDataFormat->DataFormat.FormatSize = RegionSize - sizeof(KSPIN_CONNECT);
pWaveDataFormat->DataFormat.SampleSize = lpFormat->nBlockAlign ;
pWaveDataFormat->DataFormat.Reserved = 0 ;
//
// Copy over the whole waveformat structure
//
RtlCopyMemory( &pWaveDataFormat->WaveFormatEx,
lpFormat,
(lpFormat->wFormatTag == WAVE_FORMAT_PCM) ?
sizeof( PCMWAVEFORMAT ) :
sizeof( WAVEFORMATEX ) + lpFormat->cbSize);
Status = AudioAllocateMemory_Fixed(( sizeof(CONTROLS_LIST)+
((MAX_WAVE_CONTROLS-1)*sizeof(CONTROL_NODE)) ),
TAG_AudC_CONTROL,
ZERO_FILL_MEMORY,
&pControlList) ;
if(!NT_SUCCESS(Status))
{
AudioFreeMemory_Unknown( &pConnect );
DPF(DL_WARNING|FA_WAVE, ("Could not allocate ControlList"));
goto exit;
}
pControlList->Count = MAX_WAVE_CONTROLS ;
pControlList->Controls[WAVE_CONTROL_VOLUME].Control = KSNODETYPE_VOLUME ;
pControlList->Controls[WAVE_CONTROL_RATE].Control = KSNODETYPE_SRC ;
pControlList->Controls[WAVE_CONTROL_QUALITY].Control = KSNODETYPE_SRC ;
pNewWavePin->pControlList = pControlList ;
//
// Open a pin
//
Status = OpenSysAudioPin(Device,
PinId,
pNewWavePin->DataFlow,
pConnect,
&pNewWavePin->pFileObject,
&pNewWavePin->pDeviceObject,
pNewWavePin->pControlList);
AudioFreeMemory_Unknown( &pConnect );
if(!NT_SUCCESS(Status))
{
CloseTheWavePin(pNewWavePin->pWaveDevice, pNewWavePin->WaveHandle);
goto exit;
}
if ( pNewWavePin->DataFlow == KSPIN_DATAFLOW_IN ) {
Status = AttachVirtualSource(pNewWavePin->pFileObject, pNewWavePin->pWaveDevice->pWdmaContext->VirtualWavePinId);
if (!NT_SUCCESS(Status))
{
CloseTheWavePin(pNewWavePin->pWaveDevice, pNewWavePin->WaveHandle);
goto exit;
}
}
//
// Now we've gotten through everything so we can mark this one as running.
// We do it here because of the close path. In that path fGraphRunning gets
// decremented and the assert fires in the checked build.
//
pNewWavePin->fGraphRunning=TRUE;
//
// Why do we set this to KSSTATE_STOP and then change it to KSSTATE_PAUSE? If
// StatePin is able to successfully change the state to KSSTATE_PAUSE, the
// PinState will get updated to KSSTATE_PAUSE.
//
pNewWavePin->PinState = KSSTATE_STOP;
StatePin(pNewWavePin->pFileObject, KSSTATE_PAUSE, &pNewWavePin->PinState);
exit:
RETURN( Status );
}
void
CloseTheWavePin(
PWAVEDEVICE pWaveDevice,
HANDLE32 DeviceHandle
)
{
PWAVE_PIN_INSTANCE *ppCur;
PWAVE_PIN_INSTANCE pCurFree;
PAGED_CODE();
//
// Remove from device chain. Notice that ppCur gets the address of the
// location of pWaveDevice->pWavePin. Thus, the *ppCur = (*ppCur)->Next
// assignment below updates the pWaveDevice->pWavePin location if we
// close the first pin.
//
for (ppCur = &pWaveDevice->pWavePin;
*ppCur != NULL;
ppCur = &(*ppCur)->Next)
{
if ( NULL == DeviceHandle || (*ppCur)->WaveHandle == DeviceHandle )
{
//
// Note that if there is outstanding Io we can not call CloseWavePin
// until it's all come back. Thus, we'll need to tell the device
// to stop and then wait for the Io here.
//
if( (*ppCur)->pFileObject )
{
//
// We will never have outstanding Io if we don't have a file object
// to send it too.
//
WaitForOutStandingIo(pWaveDevice,*ppCur);
}
CloseWavePin ( *ppCur );
pCurFree = *ppCur;
*ppCur = (*ppCur)->Next;
AudioFreeMemory( sizeof(WAVE_PIN_INSTANCE), &pCurFree );
break;
}
}
}
//
// This routine can not fail!
//
VOID
CloseWavePin(
PWAVE_PIN_INSTANCE pWavePin
)
{
ASSERT(pWavePin->NumPendingIos==0);
PAGED_CODE();
//
// This routine can get called on the error path thus fGraphRunning may be FALSE.
// In either case, we will need to close sysaudio and free memory.
//
pWavePin->fGraphRunning = FALSE;
// Close the file object (pFileObject, if it exists)
if(pWavePin->pFileObject)
{
CloseSysAudio(pWavePin->pWaveDevice->pWdmaContext, pWavePin->pFileObject);
pWavePin->pFileObject = NULL;
}
//
// AudioFreeMemory_Unknown NULLs out this location after freeing the memory.
//
AudioFreeMemory_Unknown ( &pWavePin->lpFormat );
AudioFreeMemory_Unknown ( &pWavePin->pControlList ) ;
//
// Caller needs to free pWavePin if it wants to.
//
}
#pragma LOCKED_CODE
#pragma LOCKED_DATA
//
// This routine is used rather then an InterlockedIncrement and InterlockedDecrement
// because the routine that needs to determine what to do based on this information
// needs to perform multiple checks on different variables to determine exactly what
// to do. Thus, we need a "critical section" for NumPendingIos. Also, SpinLocks
// must be called from locked code. :)
//
void
LockedWaveIoCount(
PWAVE_PIN_INSTANCE pCurWavePin,
BOOL bIncrease
)
{
KIRQL OldIrql;
KeAcquireSpinLock(&pCurWavePin->WavePinSpinLock,&OldIrql);
if( bIncrease )
pCurWavePin->NumPendingIos++;
else
pCurWavePin->NumPendingIos--;
KeReleaseSpinLock(&pCurWavePin->WavePinSpinLock, OldIrql);
}
void
CompleteNumPendingIos(
PWAVE_PIN_INSTANCE pCurWavePin
)
{
KIRQL OldIrql;
if( pCurWavePin )
{
KeAcquireSpinLock(&pCurWavePin->WavePinSpinLock,&OldIrql);
//
// We always decrement NumPendingIos and then perform the comparisons.
// If the count goes to zero, we're the last IRP so we need to check
// to see if we need to signal any waiting thread.
//
if( ( --pCurWavePin->NumPendingIos == 0 ) && pCurWavePin->StoppingSource )
{
//
// If this Io is the last one to come through, and we're currently
// sitting waiting for the reset to finish, then we signal it here.
//
KeSetEvent ( &pCurWavePin->StopEvent, 0, FALSE ) ;
}
//
// Upon leaving this spinlock, pCurWavePin can be freed by the close
// routine if NumPendingIos went to zero!
//
KeReleaseSpinLock(&pCurWavePin->WavePinSpinLock, OldIrql);
}
//
// Must not touch pCurWavePin after this!
//
}
void
UnmapWriteContext(
PWRITE_CONTEXT pWriteContext
)
{
wdmaudUnmapBuffer(pWriteContext->pBufferMdl);
AudioFreeMemory_Unknown(&pWriteContext->pCapturedWaveHdr);
AudioFreeMemory(sizeof(WRITE_CONTEXT),&pWriteContext);
}
void
FreeWriteContext(
PWRITE_CONTEXT pWriteContext,
NTSTATUS IrpStatus
)
{
PIRP UserIrp;
PWDMAPENDINGIRP_CONTEXT pPendingIrpContext;
//
// grab the parent IRP from the reserved field
//
UserIrp = (PIRP)pWriteContext->whInstance.wh.reserved;
pPendingIrpContext = pWriteContext->pPendingIrpContext;
UnmapWriteContext( pWriteContext );
if( UserIrp )
wdmaudUnprepareIrp( UserIrp,IrpStatus,0,pPendingIrpContext);
}
//
// This is the Irp completion routine.
//
NTSTATUS
wqWriteWaveCallBack(
PDEVICE_OBJECT pDeviceObject,
PIRP pIrp,
IN PWAVEHDR pWriteData
)
{
PWAVE_PIN_INSTANCE pCurWavePin;
PMDL Mdl;
PMDL nextMdl;
PIRP UserIrp;
PWDMAPENDINGIRP_CONTEXT pPendingIrpContext;
PWRITE_CONTEXT pWriteContext = (PWRITE_CONTEXT)pWriteData;
ASSERT(pWriteData);
pCurWavePin = pWriteContext->whInstance.pWaveInstance;
DPF(DL_TRACE|FA_WAVE, ("R%d: 0x%08x", pCurWavePin->NumPendingIos,pIrp));
//
// After we get our pCurWavePin, we don't need the write context any longer.
//
FreeWriteContext(pWriteContext, pIrp->IoStatus.Status);
//
// Consider putting this in a routine.
//
if (pIrp->MdlAddress != NULL)
{
//
// Unlock any pages that may be described by MDLs.
//
Mdl = pIrp->MdlAddress;
while (Mdl != NULL)
{
MmUnlockPages( Mdl );
Mdl = Mdl->Next;
}
}
if (pIrp->MdlAddress != NULL)
{
for (Mdl = pIrp->MdlAddress; Mdl != (PMDL) NULL; Mdl = nextMdl)
{
nextMdl = Mdl->Next;
if (Mdl->MdlFlags & MDL_PARTIAL_HAS_BEEN_MAPPED)
{
ASSERT( Mdl->MdlFlags & MDL_PARTIAL );
MmUnmapLockedPages( Mdl->MappedSystemVa, Mdl );
}
else if (!(Mdl->MdlFlags & MDL_PARTIAL))
{
ASSERT(!(Mdl->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA ));
}
AudioFreeMemory_Unknown( &Mdl );
}
}
IoFreeIrp( pIrp );
CompleteNumPendingIos( pCurWavePin );
return ( STATUS_MORE_PROCESSING_REQUIRED );
}
//
// Consider combining ssWriteWaveCallback and wqWriteWaveCallBack. They look
// like the same routine!
//
NTSTATUS
ssWriteWaveCallBack(
PDEVICE_OBJECT pDeviceObject,
PIRP pIrp,
IN PSTREAM_HEADER_EX pStreamHeader
)
{
PWAVE_PIN_INSTANCE pCurWavePin;
PIRP UserIrp;
PWDMAPENDINGIRP_CONTEXT pPendingIrpContext;
PWRITE_CONTEXT pWriteContext = (PWRITE_CONTEXT)pStreamHeader->pWaveHdr;
ASSERT(pWriteContext);
pCurWavePin = pWriteContext->whInstance.pWaveInstance;
DPF(DL_TRACE|FA_WAVE, ("R%d: 0x%08x", pCurWavePin->NumPendingIos,pIrp));
FreeWriteContext( pWriteContext, pIrp->IoStatus.Status );
CompleteNumPendingIos( pCurWavePin );
return STATUS_SUCCESS;
}
#pragma PAGEABLE_CODE
#pragma PAGEABLE_DATA
//
// Walk the list and if we find a matching pin, write it back for the caller.
//
NTSTATUS
FindRunningPin(
IN PWAVEDEVICE pWaveDevice,
IN HANDLE32 DeviceHandle,
OUT PWAVE_PIN_INSTANCE* ppCurWavePin
)
{
PWAVE_PIN_INSTANCE pCurWavePin;
NTSTATUS Status = STATUS_INVALID_DEVICE_REQUEST;
//
// Prepare for the error condition.
//
*ppCurWavePin = NULL;
//
// find the right pin based off of the wave handle
//
for (pCurWavePin = pWaveDevice->pWavePin;
pCurWavePin != NULL;
pCurWavePin = pCurWavePin->Next)
{
if (pCurWavePin->WaveHandle == DeviceHandle)
{
if (pCurWavePin->fGraphRunning)
{
//
// Write back the pointer and return success
//
*ppCurWavePin = pCurWavePin;
Status = STATUS_SUCCESS;
} else {
DPF(DL_WARNING|FA_WAVE,("Invalid fGraphRunning") );
Status = STATUS_DEVICE_NOT_READY;
}
return Status;
}
}
return Status;
}
//
// WriteWaveOutPin walks the device list like the other routines.
//
// pUserIrp is the Irp on which this call from user mode was made. It's
// always going to be valid. We don't need to check it.
//
// This routine needs to set pCompletedIrp to either TRUE or FALSE. If
// TRUE, the Irp was successfully marked STATUS_PENDING and it will get
// completed later. If FALSE, there was some type of error that prevented
// us from submitting the Irp. The caller to this routine will need to
// handle freeing the Irp.
//
//
// This routine should be the one storing the user's irp in the reserved field.
// Not the caller.
// pWriteContext->whInstance.wh.reserved = (DWORD_PTR)pIrp; // store to complete later
//
NTSTATUS
WriteWaveOutPin(
PWAVEDEVICE pWaveOutDevice,
HANDLE32 DeviceHandle,
LPWAVEHDR pWriteData,
PSTREAM_HEADER_EX pStreamHeader,
PIRP pUserIrp,
PWDMACONTEXT pContext,
BOOL *pCompletedIrp
)
{
PWAVE_PIN_INSTANCE pCurWavePin;
PWRITE_CONTEXT pWriteContext = (PWRITE_CONTEXT)pWriteData;
PWDMAPENDINGIRP_CONTEXT pPendingIrpContext;
NTSTATUS Status;
PAGED_CODE();
//
// We assumee that pCompletedIrp is FALSE on entry.
//
ASSERT( *pCompletedIrp == FALSE );
Status = FindRunningPin(pWaveOutDevice,DeviceHandle,&pCurWavePin);
if( NT_SUCCESS(Status) )
{
if (pCurWavePin->fWaveQueued)
{
PIO_STACK_LOCATION pIrpStack;
LARGE_INTEGER StartingOffset;
PIRP pIrp = NULL;
//
// Can't use KsStreamIo because these are not
// true stream headers. Sending down headers
// using the WAVE_QUEUED interface
//
StartingOffset.QuadPart = 0;
pIrp = IoBuildAsynchronousFsdRequest(IRP_MJ_WRITE,
pCurWavePin->pDeviceObject,
(PVOID)pWriteContext,
sizeof(WAVEHDR),
&StartingOffset,
&gIoStatusBlock);
if( pIrp )
{
Status = wdmaudPrepareIrp( pUserIrp,
WaveOutDevice,
pContext,
&pPendingIrpContext );
if( NT_SUCCESS(Status) )
{
//
// The Irp was successfully marked STATUS_PENDING and put in
// our queue. Now let's send it.
//
pWriteContext->whInstance.pWaveInstance = pCurWavePin;
pWriteContext->pPendingIrpContext = pPendingIrpContext;
pIrp->RequestorMode = KernelMode;
pIrp->Tail.Overlay.OriginalFileObject = pCurWavePin->pFileObject;
pIrpStack = IoGetNextIrpStackLocation(pIrp);
pIrpStack->FileObject = pCurWavePin->pFileObject;
IoSetCompletionRoutine(pIrp,
wqWriteWaveCallBack,
pWriteData,
TRUE,TRUE,TRUE);
//
// one more IRP pending
//
LockedWaveIoCount(pCurWavePin,INCREASE);
DPF(DL_TRACE|FA_WAVE, ("A%d", pCurWavePin->NumPendingIos));
//
// We don't need to check the return code because the
// completion routine will ALWAYS be called. See
// IoSetCompletionRoutine(...TRUE,TRUE,TRUE).
//
IofCallDriver( pCurWavePin->pDeviceObject, pIrp );
//
// At this point, the Irp may have been completed and our
// callback routine will have been called. We can not touch
// the irp after this call. The Callback routine Completes
// the Irp and unprepares the user's Irp.
//
*pCompletedIrp = TRUE;
//
// In wdmaudPrepareIrp we call IoCsqInsertIrp which calls
// IoMarkIrpPending, thus we must always return STATUS_PENDING.
//
return STATUS_PENDING;
} else {
//
// We where not successful at putting the Irp in the queue.
// cleanup and indicated that we did not complete the Irp.
// The status will have been set by wdmaudPrepareIrp.
DPF(DL_WARNING|FA_WAVE,("wdmaudPrepareIrp failed Status=%X",Status) );
}
} else {
//
// Could not create a Irp to send down - error out!
//
DPF(DL_WARNING|FA_WAVE,("IoBuildAsynchronousFsdRequest failed") );
Status = STATUS_UNSUCCESSFUL;
//
// We can't get an Irp to schedule. Cleanup memory
// and return. The caller will complete the Irp.
//
}
} else {
//
// If it's not wave queued we need to make sure that it's a PCM
// looped call.
//
if ( (pWriteData->dwFlags & (WHDR_BEGINLOOP|WHDR_ENDLOOP)) )
{
//
// Error out non-PCM looped calls
//
Status = STATUS_NOT_IMPLEMENTED;
} else {
//
// The graph is running so we can use it. Proceed.
//
Status = wdmaudPrepareIrp( pUserIrp, WaveOutDevice, pContext, &pPendingIrpContext );
if( NT_SUCCESS(Status) )
{
//
// The Irp was successfully marked STATUS_PENDING and put in
// our queue. Now let's send it.
//
pWriteContext->whInstance.pWaveInstance = pCurWavePin;
pWriteContext->pPendingIrpContext = pPendingIrpContext;
//
// one more IRP pending
//
LockedWaveIoCount(pCurWavePin,INCREASE);
DPF(DL_TRACE|FA_WAVE, ("A%d", pCurWavePin->NumPendingIos));
pStreamHeader->pWavePin = pCurWavePin;
pStreamHeader->Header.FrameExtent = pWriteData->dwBufferLength ;
pStreamHeader->Header.DataUsed = pWriteData->dwBufferLength;
pStreamHeader->Header.OptionsFlags = 0 ;
pStreamHeader->Header.Size = sizeof( KSSTREAM_HEADER );
pStreamHeader->Header.TypeSpecificFlags = 0;
pStreamHeader->pWaveHdr = pWriteData; // store so we can use later
Status = KsStreamIo(pCurWavePin->pFileObject,
NULL, // Event
NULL, // PortContext
ssWriteWaveCallBack,
pStreamHeader, // CompletionContext
KsInvokeOnSuccess | KsInvokeOnCancel | KsInvokeOnError,
&gIoStatusBlock,
&pStreamHeader->Header,
sizeof( KSSTREAM_HEADER ),
KSSTREAM_WRITE,
KernelMode );
//
// At this point, the Irp may have been completed and our
// callback routine will have been called. We can not touch
// the irp after this call. The Callback routine Completes
// the Irp and unprepares the user's Irp.
//
*pCompletedIrp = TRUE;
//
// In wdmaudPrepareIrp we call IoCsqInsertIrp which calls
// IoMarkIrpPending, thus we must always return STATUS_PENDING.
// also, we don't want to clean up anything.... just return.
//
return STATUS_PENDING;
//
// Warning: If, for any reason, the completion routine is not called
// for this Irp, wdmaud.sys will hang. It's been discovered that
// KsStreamIo may error out in low memory conditions. There is an
// outstanding bug to address this.
//
} else {
//
// We where not successful at putting the Irp in the queue.
// cleanup and indicated that we did not complete the Irp.
// The Status was set by wdmaudPrepareIrp.
DPF(DL_WARNING|FA_WAVE,("wdmaudPrepareIrp failed Status=%X",Status) );
}
}
}
}
//
// All error paths end up here. All error paths should cleanup the
// memory so we don't leak.
//
UnmapWriteContext( pWriteContext );
RETURN( Status );
}
//
// These next three routines all perform the same type of walk and checks.
// They should be combined into one walk routine and a callback.
//
NTSTATUS
PosWavePin(
PWAVEDEVICE pWaveDevice,
HANDLE32 DeviceHandle,
PWAVEPOSITION pWavePos
)
{
PWAVE_PIN_INSTANCE pCurWavePin;
KSAUDIO_POSITION AudioPosition;
NTSTATUS Status;
PAGED_CODE();
Status = FindRunningPin(pWaveDevice,DeviceHandle,&pCurWavePin);
if( NT_SUCCESS(Status) )
{
if ( pWavePos->Operation == KSPROPERTY_TYPE_SET )
{
AudioPosition.WriteOffset = pWavePos->BytePos;
}
Status = PinProperty(pCurWavePin->pFileObject,
&KSPROPSETID_Audio,
KSPROPERTY_AUDIO_POSITION,
pWavePos->Operation,
sizeof(AudioPosition),
&AudioPosition);
if (NT_SUCCESS(Status))
{
pWavePos->BytePos = (DWORD)AudioPosition.PlayOffset;
}
}
RETURN( Status );
}
NTSTATUS
BreakLoopWaveOutPin(
PWAVEDEVICE pWaveOutDevice,
HANDLE32 DeviceHandle
)
{
PWAVE_PIN_INSTANCE pCurWavePin;
NTSTATUS Status;
PAGED_CODE();
Status = FindRunningPin(pWaveOutDevice,DeviceHandle,&pCurWavePin);
if( NT_SUCCESS(Status) )
{
if (pCurWavePin->fWaveQueued) {
Status = PinMethod ( pCurWavePin->pFileObject,
&KSMETHODSETID_Wave_Queued,
KSMETHOD_WAVE_QUEUED_BREAKLOOP,
KSMETHOD_TYPE_WRITE, // TODO :: change to TYPE_NONE
0,
NULL ) ;
}
else {
//
// Error out non-pcm loop related commands
//
Status = STATUS_NOT_IMPLEMENTED;
}
}
RETURN( Status );
}
NTSTATUS
ResetWaveOutPin(
PWAVEDEVICE pWaveOutDevice,
HANDLE32 DeviceHandle
)
{
PWAVE_PIN_INSTANCE pCurWavePin;
NTSTATUS Status;
KSRESET ResetValue ;
PAGED_CODE();
Status = FindRunningPin(pWaveOutDevice,DeviceHandle,&pCurWavePin);
if( NT_SUCCESS(Status))
{
pCurWavePin->StoppingSource = TRUE ;
ResetValue = KSRESET_BEGIN ;
Status = ResetWavePin(pCurWavePin, &ResetValue) ;
//
// If the driver fails to reset will will not wait for the
// Irps to complete. But, that would be bad in the
// CleanupWavePins case because we're going to free
// the memory when we return from this call. Thus,
// will choose a hang over a bugcheck and wait for
// the Irps to complete.
//
if ( pCurWavePin->NumPendingIos )
{
DPF(DL_TRACE|FA_WAVE, ("Start waiting for stop to complete"));
KeWaitForSingleObject ( &pCurWavePin->StopEvent,
Executive,
KernelMode,
FALSE,
NULL ) ;
}
DPF(DL_TRACE|FA_WAVE, ("Done waiting for stop to complete"));
ResetValue = KSRESET_END ;
ResetWavePin(pCurWavePin, &ResetValue) ;
//
// Why do we have this KeClearEvent ???
//
KeClearEvent ( &pCurWavePin->StopEvent );
pCurWavePin->StoppingSource = FALSE ;
}
RETURN( Status );
}
//
// The only difference between this and StatePin is KSPROPERTY_CONNECTION_STATE
// and IOCTL_KS_RESET_STATE. Consider using StatePin if possible.
//
NTSTATUS
ResetWavePin(
PWAVE_PIN_INSTANCE pWavePin,
KSRESET *pResetValue
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG BytesReturned ;
PAGED_CODE();
if (!pWavePin->fGraphRunning)
{
DPF(DL_WARNING|FA_WAVE,("Invalid fGraphRunning") );
RETURN( STATUS_INVALID_DEVICE_REQUEST );
}
DPF(DL_TRACE|FA_SYSAUDIO,("IOCTL_KS_RESET_STATE pResetValue=%X",pResetValue) );
Status = KsSynchronousIoControlDevice(pWavePin->pFileObject,
KernelMode,
IOCTL_KS_RESET_STATE,
pResetValue,
sizeof(KSRESET),
NULL,
0,
&BytesReturned);
DPF(DL_TRACE|FA_SYSAUDIO,("IOCTL_KS_RESET_STATE result=%X",Status) );
RETURN( Status );
}
//
// Looks the same, different flavor.
//
NTSTATUS
StateWavePin(
PWAVEDEVICE pWaveInDevice,
HANDLE32 DeviceHandle,
KSSTATE State
)
{
PWAVE_PIN_INSTANCE pCurWavePin;
NTSTATUS Status;
PAGED_CODE();
Status = FindRunningPin(pWaveInDevice,DeviceHandle,&pCurWavePin);
if( NT_SUCCESS(Status) )
{
if( pCurWavePin->DataFlow == KSPIN_DATAFLOW_OUT )
{
//
// We have an In pin.
//
//
// On a waveInStop, one more buffer needs to make
// it up to the application before the device can
// stop. The caveat is that if the buffer is
// large it might take awhile for the stop to happen.
//
// Don't return let this extra buffer complete if the
// device is already in a paused state.
//
if( (KSSTATE_PAUSE == State) &&
(KSSTATE_PAUSE != pCurWavePin->PinState) )
{
pCurWavePin->PausingSource = TRUE ;
if ( pCurWavePin->NumPendingIos )
{
DPF(DL_TRACE|FA_WAVE, ("Waiting for PauseEvent..."));
KeWaitForSingleObject ( &pCurWavePin->PauseEvent,
Executive,
KernelMode,
FALSE,
NULL ) ;
DPF(DL_TRACE|FA_WAVE, ("...Done waiting for PauseEvent"));
}
KeClearEvent ( &pCurWavePin->PauseEvent );
pCurWavePin->PausingSource = FALSE ;
}
Status = StatePin ( pCurWavePin->pFileObject, State, &pCurWavePin->PinState ) ;
if ( NT_SUCCESS(Status) )
{
ASSERT(pCurWavePin->PinState == State);
if ( KSSTATE_STOP == State )
{
Status = StatePin( pCurWavePin->pFileObject,KSSTATE_PAUSE,&pCurWavePin->PinState);
}
}
} else {
//
// We have an out pin.
//
Status = StatePin ( pCurWavePin->pFileObject, State, &pCurWavePin->PinState ) ;
}
}
RETURN( Status );
}
#pragma LOCKED_CODE
#pragma LOCKED_DATA
void
UnmapStreamHeader(
PSTREAM_HEADER_EX pStreamHeader
)
{
wdmaudUnmapBuffer(pStreamHeader->pBufferMdl);
wdmaudUnmapBuffer(pStreamHeader->pHeaderMdl);
AudioFreeMemory_Unknown(&pStreamHeader->pWaveHdrAligned);
AudioFreeMemory(sizeof(STREAM_HEADER_EX),&pStreamHeader);
}
void
FreeStreamHeader(
PSTREAM_HEADER_EX pStreamHeader,
NTSTATUS IrpStatus
)
{
PIRP UserIrp;
PWDMAPENDINGIRP_CONTEXT pPendingIrpContext;
UserIrp = pStreamHeader->pIrp;
ASSERT(UserIrp);
pPendingIrpContext = pStreamHeader->pPendingIrpContext;
UnmapStreamHeader( pStreamHeader );
wdmaudUnprepareIrp( UserIrp,IrpStatus,sizeof(DEVICEINFO),pPendingIrpContext );
}
//
// This is the read Irp completion routine.
//
NTSTATUS
ReadWaveCallBack(
PDEVICE_OBJECT pDeviceObject,
PIRP pIrp,
IN PSTREAM_HEADER_EX pStreamHeader
)
{
// cast the reserved field to the parent IRP that we stored in here
PWAVE_PIN_INSTANCE pCurWavePin;
NTSTATUS Status;
KIRQL OldIrql;
//
// Must get the current pin before we free the stream header structure.
//
pCurWavePin = pStreamHeader->pWavePin;
//
// Get the dataused and fill the bytes recorded field
//
if (pIrp->IoStatus.Status == STATUS_CANCELLED)
pStreamHeader->pWaveHdrAligned->dwBytesRecorded = 0L;
else
pStreamHeader->pWaveHdrAligned->dwBytesRecorded = pStreamHeader->Header.DataUsed;
//
// Copy back the contents of the captured buffer
//
try
{
RtlCopyMemory( pStreamHeader->pdwBytesRecorded,
&pStreamHeader->pWaveHdrAligned->dwBytesRecorded,
sizeof(DWORD));
}
except (EXCEPTION_EXECUTE_HANDLER)
{
DPF(DL_WARNING|FA_WAVE, ("Couldn't copy waveheader (0x%08x)", GetExceptionCode()) );
}
FreeStreamHeader( pStreamHeader, pIrp->IoStatus.Status );
if ( pCurWavePin )
{
//
// Need to lock this code so we can decrement and check and set an event
// with no preemption windows.
//
KeAcquireSpinLock(&pCurWavePin->WavePinSpinLock, &OldIrql);
//
// We always decrement NumPendingIos before doing any comparisons. This
// is so that we're consistant.
//
pCurWavePin->NumPendingIos--;
if( pCurWavePin->PausingSource )
{
//
// Let this I/O squeeze out of the queue on a waveInStop
//
KeSetEvent ( &pCurWavePin->PauseEvent, 0, FALSE ) ;
}
//
// If the count went to zero, we're the last IRP so we need to check
// to see if we need to signal any waiting thread.
//
if( (pCurWavePin->NumPendingIos == 0) && pCurWavePin->StoppingSource )
{
//
// Because we do not block (FALSE), we can call KeSetEvent in this
// Lock.
//
KeSetEvent ( &pCurWavePin->StopEvent, 0, FALSE ) ;
}
//
// Upon leaving this spinlock, pCurWavePin can be freed by the close
// routine if NumPendingIos went to zero!
//
KeReleaseSpinLock(&pCurWavePin->WavePinSpinLock, OldIrql);
}
return STATUS_SUCCESS;
}
#pragma PAGEABLE_CODE
#pragma PAGEABLE_DATA
//
// pUserIrp will always be valid when this call is made. It is the Irp
// that we got for the user mode request.
//
// pStreamHeader is alway going to be valid.
//
NTSTATUS
ReadWaveInPin(
PWAVEDEVICE pWaveInDevice,
HANDLE32 DeviceHandle,
PSTREAM_HEADER_EX pStreamHeader,
PIRP pUserIrp,
PWDMACONTEXT pContext,
BOOL *pCompletedIrp
)
{
PWAVE_PIN_INSTANCE pCurWavePin;
PWDMAPENDINGIRP_CONTEXT pPendingIrpContext;
NTSTATUS Status;
PAGED_CODE();
//
// We assumee that pCompletedIrp is FALSE on entry.
//
ASSERT( *pCompletedIrp == FALSE );
Status = FindRunningPin(pWaveInDevice,DeviceHandle,&pCurWavePin);
if( NT_SUCCESS(Status) )
{
Status = wdmaudPrepareIrp( pUserIrp, WaveInDevice, pContext, &pPendingIrpContext );
if( NT_SUCCESS(Status) )
{
pStreamHeader->pWavePin = pCurWavePin;
pStreamHeader->pPendingIrpContext = pPendingIrpContext;
ASSERT(pPendingIrpContext);
pStreamHeader->Header.OptionsFlags = 0 ;
pStreamHeader->Header.Size = sizeof( KSSTREAM_HEADER );
pStreamHeader->Header.TypeSpecificFlags = 0;
LockedWaveIoCount(pCurWavePin,INCREASE);
DPF(DL_TRACE|FA_WAVE, ("A%d: 0x%08x", pCurWavePin->NumPendingIos,
pStreamHeader));
Status = KsStreamIo(pCurWavePin->pFileObject,
NULL, // Event
NULL, // PortContext
ReadWaveCallBack,
pStreamHeader, // CompletionContext
KsInvokeOnSuccess | KsInvokeOnCancel | KsInvokeOnError,
&gIoStatusBlock,
&pStreamHeader->Header,
sizeof( KSSTREAM_HEADER ),
KSSTREAM_READ,
KernelMode );
//
// In wdmaudPrepareIrp we call IoCsqInsertIrp which calls
// IoMarkIrpPending, thus we must always return STATUS_PENDING.
// And we completed the Irp.
//
*pCompletedIrp = TRUE;
return STATUS_PENDING;
//
// Warning: If, for any reason, the completion routine is not called
// for this Irp, wdmaud.sys will hang. It's been discovered that
// KsStreamIo may error out in low memory conditions. There is an
// outstanding bug to address this.
//
} else {
//
// wdmaudPrepareIrp would have set Status for this error path
//
DPF(DL_WARNING|FA_WAVE,("wdmaudPrepareIrp failed Status=%X",Status) );
}
}
//
// All error paths lead here.
//
UnmapStreamHeader( pStreamHeader );
RETURN( Status );
}
NTSTATUS
FindVolumeControl(
IN PWDMACONTEXT pWdmaContext,
IN PCWSTR DeviceInterface,
IN DWORD DeviceType
)
{
PCOMMONDEVICE *papCommonDevice;
PWAVEDEVICE paWaveOutDevs;
PMIDIDEVICE paMidiOutDevs;
PAUXDEVICE paAuxDevs;
ULONG DeviceNumber;
ULONG MixerIndex;
NTSTATUS Status;
PAGED_CODE();
papCommonDevice = &pWdmaContext->apCommonDevice[DeviceType][0];
paWaveOutDevs = pWdmaContext->WaveOutDevs;
paMidiOutDevs = pWdmaContext->MidiOutDevs;
paAuxDevs = pWdmaContext->AuxDevs;
for( DeviceNumber = 0; DeviceNumber < MAXNUMDEVS; DeviceNumber++ ) {
if(papCommonDevice[DeviceNumber]->Device == UNUSED_DEVICE) {
continue;
}
if(MyWcsicmp(papCommonDevice[DeviceNumber]->DeviceInterface, DeviceInterface)) {
continue;
}
MixerIndex = FindMixerForDevNode(pWdmaContext->MixerDevs, DeviceInterface);
if ( (MixerIndex == UNUSED_DEVICE) || (pWdmaContext->MixerDevs[MixerIndex].pwstrName == NULL) ) {
continue;
}
switch(DeviceType) {
case WaveOutDevice:
Status = IsVolumeControl(
pWdmaContext,
DeviceInterface,
MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT,
&paWaveOutDevs[ DeviceNumber ].dwVolumeID,
&paWaveOutDevs[ DeviceNumber ].cChannels);
if(!NT_SUCCESS(Status)) {
break;
}
if( paWaveOutDevs[ DeviceNumber ].pTimer == NULL ) {
Status = AudioAllocateMemory_Fixed(sizeof(KTIMER),
TAG_AudT_TIMER,
ZERO_FILL_MEMORY,
&paWaveOutDevs[ DeviceNumber ].pTimer);
if(!NT_SUCCESS(Status)) {
return Status;
}
KeInitializeTimerEx( paWaveOutDevs[ DeviceNumber ].pTimer,
NotificationTimer
);
}
if( paWaveOutDevs[ DeviceNumber ].pDpc == NULL ) {
Status = AudioAllocateMemory_Fixed(sizeof(KDPC),
TAG_AudE_EVENT,
ZERO_FILL_MEMORY,
&paWaveOutDevs[ DeviceNumber ].pDpc);
if(!NT_SUCCESS(Status)) {
return Status;
}
KeInitializeDpc( paWaveOutDevs[ DeviceNumber ].pDpc,
SetVolumeDpc,
&paWaveOutDevs[ DeviceNumber ]
);
// Initialize the left and right channels to goofy values.
// This signifies that the cache is invalid
paWaveOutDevs[ DeviceNumber ].LeftVolume = 0x4321;
paWaveOutDevs[ DeviceNumber ].RightVolume = 0x6789;
paWaveOutDevs[ DeviceNumber ].fNeedToSetVol = FALSE;
}
break;
case MidiOutDevice:
Status = IsVolumeControl(
pWdmaContext,
DeviceInterface,
MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER,
&paMidiOutDevs[ DeviceNumber ].dwVolumeID,
&paMidiOutDevs[ DeviceNumber ].cChannels);
break;
case AuxDevice:
Status = IsVolumeControl(
pWdmaContext,
DeviceInterface,
MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC,
&paAuxDevs[ DeviceNumber ].dwVolumeID,
&paAuxDevs[ DeviceNumber ].cChannels);
break;
}
} // while
return( STATUS_SUCCESS );
}
NTSTATUS
IsVolumeControl(
IN PWDMACONTEXT pWdmaContext,
IN PCWSTR DeviceInterface,
IN DWORD dwComponentType,
IN PDWORD pdwControlID,
IN PDWORD pcChannels
)
{
MIXERLINE ml;
MIXERLINECONTROLS mlc;
MIXERCONTROL mc;
MMRESULT mmr;
PAGED_CODE();
ml.dwComponentType = dwComponentType;
ml.cbStruct = sizeof( MIXERLINE );
mmr = kmxlGetLineInfo( pWdmaContext,
DeviceInterface,
&ml,
MIXER_GETLINEINFOF_COMPONENTTYPE
);
if( mmr != MMSYSERR_NOERROR ) {
DPF(DL_WARNING|FA_WAVE,("kmxlGetLineInfo failed mmr=%X",mmr) );
RETURN( STATUS_UNSUCCESSFUL );
}
mlc.cbStruct = sizeof( MIXERLINECONTROLS );
mlc.dwLineID = ml.dwLineID;
mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
mlc.cControls = 1;
mlc.cbmxctrl = sizeof( MIXERCONTROL );
mlc.pamxctrl = &mc;
mmr = kmxlGetLineControls(
pWdmaContext,
DeviceInterface,
&mlc,
MIXER_GETLINECONTROLSF_ONEBYTYPE
);
if( mmr != MMSYSERR_NOERROR ) {
DPF(DL_WARNING|FA_WAVE,( "kmxlGetLineControls failed mmr=%x!", mmr ) );
return( STATUS_UNSUCCESSFUL );
}
*pdwControlID = mc.dwControlID;
*pcChannels = ml.cChannels;
return( STATUS_SUCCESS );
}
#pragma LOCKED_CODE
NTSTATUS
MapMmSysError(
IN MMRESULT mmr
)
{
if ( (mmr == MMSYSERR_INVALPARAM) ||
(mmr == MIXERR_INVALCONTROL) ) {
return (STATUS_INVALID_PARAMETER);
}
if (mmr == MMSYSERR_NOTSUPPORTED) {
return (STATUS_NOT_SUPPORTED);
}
if (mmr == MMSYSERR_NOMEM) {
return(STATUS_INSUFFICIENT_RESOURCES);
}
if (mmr == MMSYSERR_NOERROR) {
return(STATUS_SUCCESS);
}
return(STATUS_UNSUCCESSFUL);
}
NTSTATUS
SetVolume(
PWDMACONTEXT pWdmaContext,
IN DWORD DeviceNumber,
IN DWORD DeviceType,
IN DWORD LeftChannel,
IN DWORD RightChannel
)
{
MIXERCONTROLDETAILS mcd;
MIXERCONTROLDETAILS_UNSIGNED mcd_u[ 2 ];
LARGE_INTEGER li;
if( DeviceNumber == (ULONG) -1 ) {
RETURN( STATUS_INVALID_PARAMETER );
}
if( DeviceType == WaveOutDevice ) {
PWAVEDEVICE paWaveOutDevs = pWdmaContext->WaveOutDevs;
mcd_u[ 0 ].dwValue = LeftChannel;
mcd_u[ 1 ].dwValue = RightChannel;
mcd.cbStruct = sizeof( MIXERCONTROLDETAILS );
mcd.dwControlID = paWaveOutDevs[ DeviceNumber ].dwVolumeID;
mcd.cChannels = paWaveOutDevs[ DeviceNumber ].cChannels;
mcd.cMultipleItems = 0;
mcd.cbDetails = mcd.cChannels *
sizeof( MIXERCONTROLDETAILS_UNSIGNED );
mcd.paDetails = &mcd_u[0];
return( MapMmSysError(kmxlSetControlDetails( pWdmaContext,
paWaveOutDevs[ DeviceNumber ].DeviceInterface,
&mcd,
0
))
);
}
if( DeviceType == MidiOutDevice ) {
PMIDIDEVICE paMidiOutDevs = pWdmaContext->MidiOutDevs;
//
// We don't support volume changes on a MIDIPORT
//
if ( paMidiOutDevs[ DeviceNumber ].MusicDataRanges ) {
WORD wTechnology;
wTechnology = GetMidiTechnology( (PKSDATARANGE_MUSIC)
&paMidiOutDevs[ DeviceNumber ].MusicDataRanges->aDataRanges[0] );
if (wTechnology == MOD_MIDIPORT) {
RETURN( STATUS_INVALID_DEVICE_REQUEST );
}
}
mcd_u[ 0 ].dwValue = LeftChannel;
mcd_u[ 1 ].dwValue = RightChannel;
mcd.cbStruct = sizeof( MIXERCONTROLDETAILS );
mcd.dwControlID = paMidiOutDevs[ DeviceNumber ].dwVolumeID;
mcd.cChannels = paMidiOutDevs[ DeviceNumber ].cChannels;
mcd.cMultipleItems = 0;
mcd.cbDetails = mcd.cChannels *
sizeof( MIXERCONTROLDETAILS_UNSIGNED );
mcd.paDetails = &mcd_u[0];
return( MapMmSysError(kmxlSetControlDetails( pWdmaContext,
paMidiOutDevs[ DeviceNumber ].DeviceInterface,
&mcd,
0
))
);
}
if( DeviceType == AuxDevice ) {
PAUXDEVICE paAuxDevs = pWdmaContext->AuxDevs;
mcd_u[ 0 ].dwValue = LeftChannel;
mcd_u[ 1 ].dwValue = RightChannel;
mcd.cbStruct = sizeof( MIXERCONTROLDETAILS );
mcd.dwControlID = paAuxDevs[ DeviceNumber ].dwVolumeID;
mcd.cChannels = paAuxDevs[ DeviceNumber ].cChannels;
mcd.cMultipleItems = 0;
mcd.cbDetails = mcd.cChannels *
sizeof( MIXERCONTROLDETAILS_UNSIGNED );
mcd.paDetails = &mcd_u[0];
return( MapMmSysError(kmxlSetControlDetails( pWdmaContext,
paAuxDevs[ DeviceNumber ].DeviceInterface,
&mcd,
0
))
);
}
return( STATUS_SUCCESS );
}
VOID
SetVolumeDpc(
IN PKDPC pDpc,
IN PWAVEDEVICE pWaveDevice,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
QueueWorkList(pWaveDevice->pWdmaContext, SetVolumeWorker, pWaveDevice, NULL ) ;
}
VOID
SetVolumeWorker(
IN PWAVEDEVICE pDevice,
IN PVOID pNotUsed
)
{
MIXERCONTROLDETAILS mcd;
MIXERCONTROLDETAILS_UNSIGNED mcd_u[ 2 ];
DPF(DL_TRACE|FA_WAVE,( "Left %X Right %X",
pDevice->LeftVolume,
pDevice->RightVolume ) );
pDevice->fNeedToSetVol = FALSE;
mcd_u[ 0 ].dwValue = pDevice->LeftVolume;
mcd_u[ 1 ].dwValue = pDevice->RightVolume;
mcd.cbStruct = sizeof( MIXERCONTROLDETAILS );
mcd.dwControlID = pDevice->dwVolumeID;
mcd.cChannels = pDevice->cChannels;
mcd.cMultipleItems = 0;
mcd.cbDetails = mcd.cChannels * sizeof( MIXERCONTROLDETAILS_UNSIGNED );
mcd.paDetails = &mcd_u[0];
kmxlSetControlDetails( pDevice->pWdmaContext,
pDevice->DeviceInterface,
&mcd,
0
);
}
#pragma PAGEABLE_CODE
NTSTATUS
GetVolume(
IN PWDMACONTEXT pWdmaContext,
IN DWORD DeviceNumber,
IN DWORD DeviceType,
OUT PDWORD LeftChannel,
OUT PDWORD RightChannel
)
{
MIXERCONTROLDETAILS mcd;
MIXERCONTROLDETAILS_UNSIGNED mcd_u[ 2 ];
MMRESULT mmr;
PAGED_CODE();
if( DeviceType == WaveOutDevice ) {
PWAVEDEVICE pWaveOutDevice = &pWdmaContext->WaveOutDevs[DeviceNumber];
if( ( pWaveOutDevice->LeftVolume != 0x4321 ) &&
( pWaveOutDevice->RightVolume != 0x6789 ) ) {
*LeftChannel = pWaveOutDevice->LeftVolume;
*RightChannel = pWaveOutDevice->RightVolume;
return( STATUS_SUCCESS );
} else {
mcd.cbStruct = sizeof( MIXERCONTROLDETAILS );
mcd.dwControlID = pWaveOutDevice->dwVolumeID;
mcd.cChannels = pWaveOutDevice->cChannels;
mcd.cMultipleItems = 0;
mcd.cbDetails = mcd.cChannels *
sizeof( MIXERCONTROLDETAILS_UNSIGNED );
mcd.paDetails = &mcd_u[0];
mmr = kmxlGetControlDetails( pWdmaContext,
pWaveOutDevice->DeviceInterface,
&mcd,
0 );
if( mmr == MMSYSERR_NOERROR )
{
*LeftChannel = mcd_u[ 0 ].dwValue;
*RightChannel = mcd_u[ 1 ].dwValue;
}
return( MapMmSysError(mmr) );
}
}
if( DeviceType == MidiOutDevice ) {
PMIDIDEVICE pMidiOutDevice = &pWdmaContext->MidiOutDevs[DeviceNumber];
//
// We don't support volume changes on a MIDIPORT
//
if ( pMidiOutDevice->MusicDataRanges ) {
WORD wTechnology;
wTechnology = GetMidiTechnology( (PKSDATARANGE_MUSIC)
&pMidiOutDevice->MusicDataRanges->aDataRanges[0] );
if (wTechnology == MOD_MIDIPORT) {
RETURN( STATUS_INVALID_DEVICE_REQUEST );
}
}
mcd.cbStruct = sizeof( MIXERCONTROLDETAILS );
mcd.dwControlID = pMidiOutDevice->dwVolumeID;
mcd.cChannels = pMidiOutDevice->cChannels;
mcd.cMultipleItems = 0;
mcd.cbDetails = mcd.cChannels *
sizeof( MIXERCONTROLDETAILS_UNSIGNED );
mcd.paDetails = &mcd_u[0];
mmr = kmxlGetControlDetails( pWdmaContext,
pMidiOutDevice->DeviceInterface,
&mcd,
0 );
if( mmr == MMSYSERR_NOERROR )
{
*LeftChannel = mcd_u[ 0 ].dwValue;
*RightChannel = mcd_u[ 1 ].dwValue;
}
return( MapMmSysError(mmr) );
}
if( DeviceType == AuxDevice ) {
PAUXDEVICE pAuxDevice = &pWdmaContext->AuxDevs[DeviceNumber];
mcd.cbStruct = sizeof( MIXERCONTROLDETAILS );
mcd.dwControlID = pAuxDevice->dwVolumeID;
mcd.cChannels = pAuxDevice->cChannels;
mcd.cMultipleItems = 0;
mcd.cbDetails = mcd.cChannels *
sizeof( MIXERCONTROLDETAILS_UNSIGNED );
mcd.paDetails = &mcd_u[0];
mmr = kmxlGetControlDetails( pWdmaContext,
pAuxDevice->DeviceInterface,
&mcd,
0 );
if( mmr == MMSYSERR_NOERROR )
{
*LeftChannel = mcd_u[ 0 ].dwValue;
*RightChannel = mcd_u[ 1 ].dwValue;
}
return( MapMmSysError(mmr) );
}
RETURN( STATUS_INVALID_PARAMETER );
}
//
// This routine waits for the Io to complete on the device after telling
// the device to stop.
//
VOID
WaitForOutStandingIo(
IN PWAVEDEVICE pWaveDevice,
IN PWAVE_PIN_INSTANCE pCurWavePin
)
{
if( pCurWavePin->DataFlow == KSPIN_DATAFLOW_IN)
{
//
// We have a wave out pin to close. Force pending data
// to come back on running pins. Non-running pins are
// ignored on this call.
//
ResetWaveOutPin( pWaveDevice, pCurWavePin->WaveHandle);
} else {
//
// We have a wave in pin to close
//
pCurWavePin->StoppingSource = TRUE ;
//
// We can not fail on this path. Doesn't look like we need to make sure
// that we're running here.
//
StatePin ( pCurWavePin->pFileObject, KSSTATE_STOP, &pCurWavePin->PinState ) ;
//
// Regardless of the return code we're going to wait for all the
// irps to complete. If the driver do not successfuly complete
// the irps, we will hang waiting for them here.
//
if( pCurWavePin->NumPendingIos )
{
KeWaitForSingleObject ( &pCurWavePin->StopEvent,
Executive,
KernelMode,
FALSE,
NULL ) ;
}
//
// Why do we have this KeClearEvent???
//
KeClearEvent ( &pCurWavePin->StopEvent );
pCurWavePin->StoppingSource = FALSE ;
}
}
//
// Replaces CleanupWaveOutPins and CleanupWaveInPins.
//
VOID
CleanupWavePins(
IN PWAVEDEVICE pWaveDevice
)
{
PWAVE_PIN_INSTANCE pCurWavePin;
PWAVE_PIN_INSTANCE pFreeWavePin;
PAGED_CODE();
while (pCurWavePin = pWaveDevice->pWavePin)
{
DPF(DL_TRACE|FA_WAVE, ("0x%08x", pCurWavePin));
WaitForOutStandingIo(pWaveDevice,pCurWavePin);
CloseWavePin( pCurWavePin );
pFreeWavePin = pCurWavePin;
pWaveDevice->pWavePin = pCurWavePin->Next;
AudioFreeMemory( sizeof(WAVE_PIN_INSTANCE),&pFreeWavePin );
}
}
VOID
CleanupWaveDevices(
IN PWDMACONTEXT pWdmaContext
)
{
DWORD DeviceNumber;
PAGED_CODE();
for (DeviceNumber = 0; DeviceNumber < MAXNUMDEVS; DeviceNumber++)
{
//
// Handle waveout devices first...
//
if (pWdmaContext->apCommonDevice[WaveOutDevice][DeviceNumber]->Device != UNUSED_DEVICE)
{
if ( pWdmaContext->WaveOutDevs[DeviceNumber].pTimer != NULL)
KeCancelTimer(pWdmaContext->WaveOutDevs[DeviceNumber].pTimer);
CleanupWavePins(&pWdmaContext->WaveOutDevs[DeviceNumber]);
//
// Sense we have removed it from the list, the other routine that would do
// the same thing (RemoveDevNode) will also attempt to remove it from the
// the list because the value it non-null. Thus, the only safe thing that
// we can do here is free the memory.
//
// Note: This routine will only get called when the handle to the driver is
// closed.
//
AudioFreeMemory_Unknown(&pWdmaContext->WaveOutDevs[DeviceNumber].pTimer);
}
//
// ...then handle wavein devices
//
if (pWdmaContext->apCommonDevice[WaveInDevice][DeviceNumber]->Device != UNUSED_DEVICE)
{
CleanupWavePins(&pWdmaContext->WaveInDevs[DeviceNumber]);
}
}
}
#pragma LOCKED_CODE
#pragma LOCKED_DATA
//
// --------------------------------------------------------------------------------
//
// The following routines are used by more then just wave.c
//
// --------------------------------------------------------------------------------
//
NTSTATUS
wdmaudPrepareIrp(
PIRP pIrp,
ULONG IrpDeviceType,
PWDMACONTEXT pContext,
PWDMAPENDINGIRP_CONTEXT *ppPendingIrpContext
)
{
return AddIrpToPendingList( pIrp,
IrpDeviceType,
pContext,
ppPendingIrpContext );
}
NTSTATUS
wdmaudUnprepareIrp(
PIRP pIrp,
NTSTATUS IrpStatus,
ULONG_PTR Information,
PWDMAPENDINGIRP_CONTEXT pPendingIrpContext
)
{
NTSTATUS Status;
// Note that the IrpContext may have been zero'ed out already because the cancel
// routine has already been called. The cancel safe queue API zeroes out the Irp
// field in the context when it performs a cancel.
Status = RemoveIrpFromPendingList( pPendingIrpContext );
if (NT_SUCCESS(Status)) {
pIrp->IoStatus.Status = IrpStatus;
if (Information) {
pIrp->IoStatus.Information = Information;
}
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
}
RETURN( Status );
}
#pragma PAGEABLE_CODE
#pragma PAGEABLE_DATA
//
// StatePin - This is used by both Midi and Wave functionality.
//
// On success State will get updated to the new state. Must make sure that
// fGraphRunning is TRUE before calling this routine.
//
// call like:
// if( pWavePin->fGraphRunning )
// StatePin(pWavePin->pFileObject, KSSTATE_PAUSE, &pWavePin->State);
//
NTSTATUS
StatePin(
IN PFILE_OBJECT pFileObject,
IN KSSTATE State,
OUT PKSSTATE pResultingState
)
{
NTSTATUS Status;
PAGED_CODE();
Status = PinProperty(pFileObject,
&KSPROPSETID_Connection,
KSPROPERTY_CONNECTION_STATE,
KSPROPERTY_TYPE_SET,
sizeof(State),
&State);
if(NT_SUCCESS(Status))
{
*pResultingState = State;
}
RETURN( Status );
}