4419 lines
105 KiB
C
4419 lines
105 KiB
C
/*++
|
||
"@(#) NEC wave.c 1.3 95/05/23 11:15:58"
|
||
|
||
Copyright (c) 1995 NEC Corporation.
|
||
Copyright (c) 1992 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
wave.c
|
||
|
||
Abstract:
|
||
|
||
This module contains code for wave input and output which is non
|
||
hardware specific.
|
||
|
||
Environment:
|
||
|
||
Kernel mode
|
||
|
||
Revision History:
|
||
|
||
03-11-93 EPA
|
||
Added SoundGetDMABufferSize( IN OUT PWAVE_INFO WaveInfo )
|
||
|
||
Notes:
|
||
|
||
This component implements a wave type device with recording and
|
||
playing.
|
||
|
||
Dispatch routine :
|
||
Create device IRP_MJ_CREATE
|
||
Cleanup and close device IRP_MJ_CLEANUP and IRP_MJ_CLOSE
|
||
Read for recording IRP_MJ_READ
|
||
Write for playing IRP_MJ_WRITE
|
||
IO controls IRP_MJ_DEVICE_CONTROL
|
||
|
||
The device state is held mainly in a WAVE_INFO structure except
|
||
for actual state variable itself (playing, paused) which is in the
|
||
local device info.
|
||
|
||
The device is assume to use DMA. The design is always to copy the
|
||
DMA data into a designated buffer which divided into two halves :
|
||
|
||
The half that is playing
|
||
The half that is prepared for playing
|
||
|
||
DMA stops either because of a request to stop or because the data
|
||
runs out. The latter condition is detected rather lazily by waiting
|
||
until a half buffer completes and testing if there's anything in the
|
||
buffer prepared for playing. This could be improved by setting a
|
||
timer at the start of the 'last' buffer. We don't switch our method
|
||
of playing for the 'last' buffer because the application could
|
||
supply more data while this buffer is playing.
|
||
|
||
Once DMA is started a 'deferred procedure call' (Dpc) routine
|
||
is queued for every interrupt (the device specific code outside this
|
||
component must arrange for this by making SoundWaveDeferred) the
|
||
Dpc routine for the device object and calling IoRequestDpc from
|
||
its interrupt service routine ONLY IF the DMABusy flag is set.
|
||
|
||
The DMA buffer size is varied depending on the number of bits
|
||
per second.
|
||
|
||
Mutual exclusion is on 4 levels :
|
||
|
||
1. At the application request level by the application's exclusion
|
||
routine which is called back for every request (usually a MUTANT
|
||
or MUTEX - NOT a spin lock).
|
||
|
||
2. Between routines on the application thread and the Dpc routines
|
||
for variables owned by this component by a spin lock.
|
||
|
||
3. Between routines below device level and device level by the
|
||
interrupt object.
|
||
|
||
4. Exclusion implemented for device access (eg synch with ISR or
|
||
between multiple devices) by the hardware access callback routines.
|
||
|
||
Irp handling :
|
||
--------------
|
||
|
||
Both wave input and output have an input queue or Irps attached
|
||
to QueueHead in the WAVE_INFO structure. Irps on this queue
|
||
can be cancelled at any time and can only be accessed under
|
||
the Cancel spin lock.
|
||
|
||
Wave output has an additional queue of non-cancellable Irps
|
||
attached from ProgressQueue. These Irps have some or all of
|
||
their data actually in the DMA buffers. The head Irp in this
|
||
second queue has its IoStatus.Information field set to the position
|
||
where the first data byte in the DMA buffers was copied from. When
|
||
a DMA buffer completes Irps are completed for the bytes in the
|
||
buffer and a new IoStatus.Information field set.
|
||
|
||
If output is paused the ProgressQueue is moved back under QueueHead
|
||
and the Irps in it become cancellable. Restarting from Pause
|
||
takes note of the IoStatus.Information field for the first byte
|
||
to take from the first buffer (which may not be the same as that
|
||
when we were paused because in theory that one could have been
|
||
cancelled).
|
||
|
||
Timer monitoring of devices to make sure they aren't dead
|
||
---------------------------------------------------------
|
||
|
||
When DMA starts we start a timer Dpc with a timeout of 3 seconds.
|
||
|
||
Each time an IO completion Dpc runs we set a flag to say we've had
|
||
a Dpc routine (and hence an interrupt).
|
||
|
||
The timer Dpc routine checks that we've had a interrupt in the last
|
||
3 seconds and shuts down the device (forever - by setting DeviceBad
|
||
in the WAVE_INFO) if not. Otherwise it queues a clone of itself.
|
||
|
||
If we detect the bad state and set DeviceBad we stop the DMA so
|
||
as to release resources. Any new request to the driver will
|
||
receive STATUS_INVALID_DEVICE_REQUEST.
|
||
|
||
Shutting down the timer Dpc is rather complicated :
|
||
|
||
We aim to get to a state where either :
|
||
|
||
A. The Dpc will not run again (unless re-initiated) and is
|
||
not running.
|
||
|
||
B. We can wait for the Dpc routine to set an event.
|
||
|
||
And to know which of A or B we reached.
|
||
|
||
The timer logical thread can be in one of :
|
||
|
||
X. Really complete
|
||
|
||
Y. On timer queue
|
||
|
||
Z. On Dpc queue
|
||
|
||
W1. Dpc routine running before getting spin lock
|
||
|
||
W2. Dpc routine running after releasing spin lock.
|
||
This is just the tail of the routine which does nothing
|
||
so regard the timer as 'finished' (state X).
|
||
|
||
W3. Holding spin lock
|
||
|
||
Outside the device spin lock the TimerActive boolean is TRUE
|
||
in case X and FALSE in other cases (except when we're synchronously
|
||
starting the thing up when we set it prior to setting the
|
||
first timer).
|
||
|
||
|
||
The method of reaching a known state is :
|
||
|
||
Inside the device spin lock :
|
||
|
||
If TimerActive is FALSE do nothing
|
||
|
||
Cancel the timer
|
||
|
||
If the timer was set we were in state Y and we're done
|
||
|
||
Otherwise note that we cannot now enter state Y while
|
||
we have the spin lock here because we would have to
|
||
go through state W3 (because the Dpc routine is the one
|
||
which restarts the timer and it has the spin lock while it
|
||
does it).
|
||
|
||
Thus we know we're in state Z or W1 so now we just reset our
|
||
event and set TimerActive to be FALSE.
|
||
|
||
We then release the device spin lock and wait
|
||
for the event to be set by the timer Dpc routine. We
|
||
|
||
|
||
--*/
|
||
|
||
#include <stdlib.h> // For min, max
|
||
#include <string.h>
|
||
#include <soundlib.h>
|
||
#include <wave.h>
|
||
#include <hardware.h>
|
||
|
||
|
||
#define absval(x) ((x) > 0 ? (x) : -(x))
|
||
|
||
ULONG __inline PositionInBuffer(PSOUND_DOUBLE_BUFFER Db, ULONG Offset)
|
||
{
|
||
if (Db->StartOfData + Offset >= Db->BufferSize) {
|
||
return Db->StartOfData + Offset - Db->BufferSize;
|
||
} else {
|
||
return Db->StartOfData + Offset;
|
||
}
|
||
}
|
||
|
||
//
|
||
// BEWARE - this function returns 0 if the input
|
||
// position == the start position
|
||
//
|
||
ULONG __inline OffsetInBuffer(PSOUND_DOUBLE_BUFFER Db, ULONG Position)
|
||
{
|
||
if (Db->StartOfData > Position) {
|
||
return Db->BufferSize + Position - Db->StartOfData;
|
||
} else {
|
||
return Position - Db->StartOfData;
|
||
}
|
||
}
|
||
|
||
//
|
||
// This function assumes that if the offset is the buffer size then
|
||
// actually we haven't started
|
||
//
|
||
ULONG __inline BytesProcessedInBuffer(PSOUND_DOUBLE_BUFFER Db, ULONG Bytes)
|
||
{
|
||
if (Bytes > Db->nBytes) {
|
||
return Db->nBytes;
|
||
} else {
|
||
return Bytes;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Local definitions
|
||
//
|
||
|
||
VOID
|
||
SoundStartWaveRecord(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI
|
||
);
|
||
|
||
VOID
|
||
SoundStopWaveRecord(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI
|
||
);
|
||
VOID
|
||
SoundStartDMA(
|
||
IN PWAVE_INFO WaveInfo
|
||
);
|
||
IO_ALLOCATION_ACTION
|
||
SoundProgramDMA(
|
||
IN PDEVICE_OBJECT pDO,
|
||
IN PIRP pIrp,
|
||
IN PVOID pMRB,
|
||
IN PVOID Context
|
||
);
|
||
VOID
|
||
SoundTerminateDMA(
|
||
IN PWAVE_INFO WaveInfo,
|
||
IN BOOLEAN Pause
|
||
);
|
||
VOID
|
||
SoundStopDMA(
|
||
IN PWAVE_INFO WaveInfo,
|
||
IN BOOLEAN Pause
|
||
);
|
||
VOID
|
||
SoundResetOutput(
|
||
IN OUT PSOUND_BUFFER_QUEUE BufferQueue
|
||
);
|
||
NTSTATUS
|
||
SoundSetWaveInputState(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN ULONG State,
|
||
IN PFILE_OBJECT FileObject
|
||
);
|
||
NTSTATUS
|
||
SoundSetWaveOutputState(
|
||
PLOCAL_DEVICE_INFO pLDI,
|
||
ULONG State,
|
||
PIRP pIrp
|
||
);
|
||
VOID
|
||
SoundGetNextBuffer(
|
||
PSOUND_BUFFER_QUEUE BufferQueue
|
||
);
|
||
|
||
VOID __inline
|
||
SoundCompleteIoBuffer(
|
||
PSOUND_BUFFER_QUEUE BufferQueue
|
||
);
|
||
VOID
|
||
SoundInitializeBufferQ(
|
||
PSOUND_BUFFER_QUEUE BufferQueue
|
||
);
|
||
|
||
VOID
|
||
SoundInitializeDoubleBuffer(
|
||
IN OUT PWAVE_INFO WaveInfo
|
||
);
|
||
|
||
VOID
|
||
SoundClearDoubleBuffer(
|
||
IN OUT PWAVE_INFO WaveInfo
|
||
);
|
||
|
||
VOID
|
||
SoundLoadDMABuffer(
|
||
PSOUND_BUFFER_QUEUE BufferQueue,
|
||
PSOUND_DOUBLE_BUFFER DoubleBuffer,
|
||
ULONG BufferPosition,
|
||
PWAVE_INFO WaveInfo
|
||
);
|
||
VOID
|
||
SoundTestDeviceDeferred(
|
||
IN PKDPC Dpc,
|
||
IN PVOID Context,
|
||
IN PVOID Param1,
|
||
IN PVOID Param2
|
||
);
|
||
VOID
|
||
SoundSynchTimer(
|
||
IN PWAVE_INFO WaveInfo
|
||
);
|
||
VOID
|
||
SoundSaveLowPriority(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI
|
||
);
|
||
VOID
|
||
SoundQueueWaveComplete(
|
||
PWAVE_INFO WaveInfo
|
||
);
|
||
VOID
|
||
SoundWorkerStopWave(
|
||
PVOID Context
|
||
);
|
||
NTSTATUS
|
||
SoundWaveData(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN PIRP pIrp,
|
||
IN PIO_STACK_LOCATION pIrpStack
|
||
);
|
||
VOID
|
||
SoundWaveCreate(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN PDEVICE_OBJECT DeviceObject
|
||
);
|
||
NTSTATUS
|
||
SoundWaveCleanup(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN PFILE_OBJECT FileObject
|
||
);
|
||
NTSTATUS
|
||
SoundIoctlSetLowPriority(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN PFILE_OBJECT FileObject
|
||
);
|
||
NTSTATUS
|
||
SoundRestoreLowPriority(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI
|
||
);
|
||
VOID
|
||
SoundFreeLowPriority(
|
||
PWAVE_INFO WaveInfo
|
||
);
|
||
NTSTATUS SoundIoctlQueryFormat(
|
||
IN PLOCAL_DEVICE_INFO pLDI,
|
||
IN OUT PIRP pIrp,
|
||
IN PIO_STACK_LOCATION IrpStack
|
||
);
|
||
ULONG
|
||
SoundGetBufferPosition(
|
||
IN CONST WAVE_INFO * WaveInfo
|
||
);
|
||
|
||
|
||
BOOLEAN SoundFillInputBuffers(PWAVE_INFO WaveInfo, ULONG BufferPosition);
|
||
|
||
WAVE_INTERFACE_ROUTINE SoundMapDMA;
|
||
WAVE_INTERFACE_ROUTINE SoundFlushDMA;
|
||
|
||
//
|
||
// Remove initialization stuff from resident memory
|
||
//
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(INIT,SoundGetCommonBuffer)
|
||
#pragma alloc_text(INIT,SoundTestWaveDevice)
|
||
#pragma alloc_text(INIT,SoundInitializeWaveInfo)
|
||
#pragma alloc_text(INIT,SoundInitializeBufferQ)
|
||
|
||
#pragma alloc_text(PAGE,SoundFreeCommonBuffer)
|
||
#pragma alloc_text(PAGE,SoundWaveData)
|
||
#pragma alloc_text(PAGE,SoundWaveCreate)
|
||
#pragma alloc_text(PAGE,SoundWaveCleanup)
|
||
#pragma alloc_text(PAGE,SoundIoctlSetLowPriority)
|
||
#pragma alloc_text(PAGE,SoundRestoreLowPriority)
|
||
#pragma alloc_text(PAGE,SoundFreeLowPriority)
|
||
#pragma alloc_text(PAGE,SoundSaveLowPriority)
|
||
#pragma alloc_text(PAGE,SoundIoctlQueryFormat)
|
||
#pragma alloc_text(PAGE,SoundWaveDispatch)
|
||
#pragma alloc_text(PAGE,SoundSetWaveInputState)
|
||
#pragma alloc_text(PAGE,SoundSetWaveOutputState)
|
||
#pragma alloc_text(PAGE,SoundStartWaveRecord)
|
||
#pragma alloc_text(PAGE,SoundStopWaveRecord)
|
||
#endif
|
||
|
||
|
||
|
||
/***************************************************************************
|
||
*
|
||
* Allocate DMA Buffer for auto-init DMA
|
||
*
|
||
***************************************************************************/
|
||
|
||
NTSTATUS
|
||
SoundGetCommonBuffer(
|
||
IN PDEVICE_DESCRIPTION DeviceDescription,
|
||
IN OUT PSOUND_DMA_BUFFER AutoBuffer
|
||
)
|
||
/*++
|
||
|
||
Routine Description :
|
||
|
||
Find the adapter object for our adapter
|
||
|
||
Allocate and map a common buffer for use with auto-init DMA
|
||
devices. The buffer returned should not cross a 64KB boundary
|
||
for an Isa device
|
||
|
||
Arguments :
|
||
|
||
DeviceDescription - The adapter object description
|
||
SoundAutoData - the information describing our buffer
|
||
|
||
Return Value :
|
||
|
||
NTSTATUS code - STATUS_SUCCESS if OK
|
||
|
||
--*/
|
||
{
|
||
SOUND_DMA_BUFFER SoundAutoData;
|
||
ULONG NumberOfMapRegisters;
|
||
|
||
SoundAutoData = *AutoBuffer; // Pick up any input
|
||
SoundAutoData.BufferSize = DeviceDescription->MaximumLength;
|
||
|
||
//
|
||
// Try to find an adapter
|
||
//
|
||
|
||
SoundAutoData.AdapterObject[0] = HalGetAdapter(
|
||
DeviceDescription,
|
||
&NumberOfMapRegisters);
|
||
//
|
||
// Check we got a good adapter and enough registers
|
||
//
|
||
|
||
if (SoundAutoData.AdapterObject[0] == NULL) {
|
||
dprintf1(("Could not find adapter"));
|
||
return STATUS_DEVICE_CONFIGURATION_ERROR;
|
||
}
|
||
|
||
if (NumberOfMapRegisters < BYTES_TO_PAGES(SoundAutoData.BufferSize)) {
|
||
dprintf1(("Could only get %u mapping registers for DMA buffer",
|
||
NumberOfMapRegisters));
|
||
|
||
if (NumberOfMapRegisters == 0) {
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
SoundAutoData.BufferSize = NumberOfMapRegisters * PAGE_SIZE;
|
||
}
|
||
|
||
//
|
||
// Call the Hal to allocate the right kind of memory. It may
|
||
// not be able to get enough - but we never accept less than 4K
|
||
// and decrease our requirement in 1K chunks.
|
||
//
|
||
// Note that we may already have a buffer if we're reusing it
|
||
// for a second channel as for the sound blaster 16.
|
||
//
|
||
|
||
if (SoundAutoData.VirtualAddress == NULL) {
|
||
for (;
|
||
SoundAutoData.BufferSize >= SOUND_MINIMUM_WAVE_BUFFER_SIZE ;
|
||
SoundAutoData.BufferSize -= SOUND_MINIMUM_WAVE_BUFFER_SIZE / 4) {
|
||
SoundAutoData.VirtualAddress =
|
||
HalAllocateCommonBuffer(SoundAutoData.AdapterObject[0],
|
||
SoundAutoData.BufferSize,
|
||
&SoundAutoData.LogicalAddress,
|
||
FALSE // Non-cached
|
||
);
|
||
|
||
if (SoundAutoData.VirtualAddress == NULL) {
|
||
dprintf1(("Could not allocate DMA buffer size %8X",
|
||
SoundAutoData.BufferSize));
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
if (SoundAutoData.BufferSize < SOUND_MINIMUM_WAVE_BUFFER_SIZE) {
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
|
||
SoundAutoData.Mdl = IoAllocateMdl(
|
||
SoundAutoData.VirtualAddress,
|
||
SoundAutoData.BufferSize,
|
||
FALSE, // not a secondary buffer
|
||
FALSE, // no charge of quota
|
||
NULL // no irp
|
||
);
|
||
|
||
|
||
if (SoundAutoData.VirtualAddress == NULL) {
|
||
dprintf1(("Could not allocate DMA buffer size %8X",
|
||
SoundAutoData.BufferSize));
|
||
|
||
HalFreeCommonBuffer(
|
||
SoundAutoData.AdapterObject[0],
|
||
SoundAutoData.BufferSize,
|
||
SoundAutoData.LogicalAddress,
|
||
SoundAutoData.VirtualAddress,
|
||
FALSE);
|
||
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
//
|
||
// Build an Mdl (ie fill in the physical addresses)
|
||
//
|
||
|
||
MmBuildMdlForNonPagedPool(SoundAutoData.Mdl);
|
||
|
||
|
||
dprintf4((" DMA Buffer : %08lXH - physical %08lXH, Length %8lXH",
|
||
SoundAutoData.VirtualAddress,
|
||
MmGetPhysicalAddress((PVOID)SoundAutoData.VirtualAddress),
|
||
SoundAutoData.BufferSize));
|
||
}
|
||
|
||
*AutoBuffer = SoundAutoData;
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
VOID
|
||
SoundFreeCommonBuffer(
|
||
IN OUT PSOUND_DMA_BUFFER SoundAutoData
|
||
)
|
||
/*++
|
||
|
||
Routine Description :
|
||
|
||
Free the data associated with a common buffer
|
||
|
||
Arguments :
|
||
|
||
SoundAutoData - The data created when we created the buffer
|
||
|
||
Return Value :
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
if (SoundAutoData->Mdl) {
|
||
|
||
IoFreeMdl(SoundAutoData->Mdl);
|
||
|
||
HalFreeCommonBuffer(
|
||
SoundAutoData->AdapterObject[0],
|
||
SoundAutoData->BufferSize,
|
||
SoundAutoData->LogicalAddress,
|
||
SoundAutoData->VirtualAddress,
|
||
FALSE);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/**************************************************************************
|
||
*
|
||
* Open, close and dispatch routines
|
||
*
|
||
**************************************************************************/
|
||
|
||
|
||
// -----------------------------------------------------------------
|
||
// Name: SoundStartWaveDevice
|
||
// Desc: Add the input Irp (if any) to the device's input queue.
|
||
//
|
||
// Start the data flow and DMA on the device if necessary
|
||
// (ie if we're in playing or recording state and it's not
|
||
// running already.
|
||
//
|
||
// Params: pLDI - Local device info
|
||
// pIrp - IO request packet from application
|
||
//
|
||
// Returns: Irp status
|
||
VOID SoundStartWaveDevice( IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN PIRP pIrp OPTIONAL )
|
||
{
|
||
BOOLEAN StartDMA;
|
||
PWAVE_INFO WaveInfo;
|
||
BOOLEAN DontPlay;
|
||
|
||
WaveInfo = pLDI->DeviceSpecificData;
|
||
DontPlay = FALSE;
|
||
|
||
ASSERTMSG("WAVE_INFO structure not correctly initialized",
|
||
WaveInfo != NULL && WaveInfo->Key == WAVE_INFO_KEY);
|
||
|
||
DMAEnter(WaveInfo);
|
||
|
||
StartDMA = (BOOLEAN)(!WaveInfo->DMABusy);
|
||
|
||
// Put the request in the queue. This is valid for any state if
|
||
// the device is open.
|
||
if (pIrp)
|
||
{
|
||
PLIST_ENTRY QueueHead;
|
||
|
||
if (WaveInfo->LowPrioritySaved &&
|
||
IoGetCurrentIrpStackLocation(pIrp)->FileObject ==
|
||
WaveInfo->LowPriorityHandle)
|
||
{
|
||
QueueHead = &WaveInfo->LowPriorityModeSave.BufferQueue.QueueHead;
|
||
DontPlay = TRUE;
|
||
}
|
||
else
|
||
{
|
||
QueueHead = &WaveInfo->BufferQueue.QueueHead;
|
||
}
|
||
SoundAddIrpToCancellableQ(QueueHead, pIrp, FALSE);
|
||
|
||
dprintf3(("irp added"));
|
||
}
|
||
DMALeave(WaveInfo);
|
||
|
||
if (DontPlay)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// NOTE - at this point it is possible for some old output to
|
||
// complete but not pick up the buffer we've just inserted.
|
||
// This is OK - we'll notice this just below. This has
|
||
// actually happened.
|
||
if (pLDI->State == WAVE_DD_PLAYING || pLDI->State == WAVE_DD_RECORDING)
|
||
{
|
||
// Set the format if necessary
|
||
if (StartDMA)
|
||
{
|
||
// Synchronize with wave stop completion before we mess
|
||
// with buffer sizes
|
||
KeWaitForSingleObject(&WaveInfo->WaveReallyComplete,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE, // Not alertable
|
||
NULL);
|
||
|
||
// We always initialize the buffers for wave recording.
|
||
// For wave output we may be paused so we usually don't
|
||
// initialize things.
|
||
//
|
||
// Also select DMA Buffer size dependent on number of bytes per
|
||
// second.
|
||
if (WaveInfo->FormatChanged || !WaveInfo->Direction)
|
||
{
|
||
SoundInitializeDoubleBuffer(WaveInfo);
|
||
}
|
||
}
|
||
|
||
// Acquire the spin lock so we are synchronized with the Dpc routine
|
||
DMAEnter(WaveInfo);
|
||
|
||
// Remember whether Dma was running :
|
||
//
|
||
// If it is running now (while we hold the spin lock) then
|
||
// the data we've added will keep it going until the data we've
|
||
// added runs out - so it's safe to release the spin lock
|
||
//
|
||
// If the Dma is not running now then it needs restarting. In
|
||
// this case nobody but us can restart it (we hold the device
|
||
// mutex) so it's safe to release the spin lock
|
||
//
|
||
// DMA may have finished when we released the spin lock so re-test it
|
||
StartDMA = (BOOLEAN)(!WaveInfo->DMABusy);
|
||
|
||
// Process as much data as we can
|
||
if (WaveInfo->Direction)
|
||
{
|
||
SoundLoadDMABuffer(&WaveInfo->BufferQueue,
|
||
&WaveInfo->DoubleBuffer,
|
||
SoundGetBufferPosition(WaveInfo),
|
||
WaveInfo);
|
||
}
|
||
else
|
||
{
|
||
SoundFillInputBuffers(WaveInfo, SoundGetBufferPosition(WaveInfo));
|
||
}
|
||
|
||
// Ok to release the spin lock now
|
||
DMALeave(WaveInfo);
|
||
|
||
// Start the Dma if necessary
|
||
if (StartDMA)
|
||
{
|
||
// Set the format
|
||
(*WaveInfo->HwSetWaveFormat)(WaveInfo);
|
||
|
||
// Everybody knows about any change by now
|
||
WaveInfo->FormatChanged = FALSE;
|
||
|
||
// Synchronize with our timer routine if necessary
|
||
SoundSynchTimer(WaveInfo);
|
||
|
||
// Synchronize with wave stop completion
|
||
KeWaitForSingleObject(&WaveInfo->WaveReallyComplete,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE, // Not alertable
|
||
NULL);
|
||
// Start the DMA
|
||
SoundStartDMA(WaveInfo);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ASSERT(!WaveInfo->DMABusy);
|
||
StartDMA = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------
|
||
// Name: SoundWaveData
|
||
// Desc: The user has passed in a buffer of wave data to play
|
||
// or of wave data to record into.
|
||
//
|
||
// Call SoundStartWaveDevice to process the request.
|
||
//
|
||
// Params: pLDI - Local wave device info
|
||
// pIrp - The IO request packet
|
||
// pIrpStack - The current stack location
|
||
//
|
||
// Returns: Irp status
|
||
NTSTATUS SoundWaveData( IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN PIRP pIrp,
|
||
IN PIO_STACK_LOCATION pIrpStack )
|
||
{
|
||
NTSTATUS Status;
|
||
|
||
// Check we're the right kind of device
|
||
if (pIrpStack->MajorFunction == IRP_MJ_READ &&
|
||
pLDI->DeviceType != WAVE_IN ||
|
||
pIrpStack->MajorFunction == IRP_MJ_WRITE &&
|
||
pLDI->DeviceType != WAVE_OUT)
|
||
{
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
// Mark the Irp pending before starting processing
|
||
IoMarkIrpPending(pIrp);
|
||
pIrp->IoStatus.Status = STATUS_PENDING;
|
||
|
||
// Mark this request as pending completion.
|
||
Status = STATUS_PENDING;
|
||
|
||
if (pLDI->DeviceType == WAVE_IN)
|
||
{
|
||
// Inform debuggers that 0 length buffers are rather strange
|
||
if (pIrpStack->Parameters.Read.Length == 0)
|
||
{
|
||
dprintf2(("Wave buffer is zero length"));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// Inform debuggers that 0 length buffers are rather strange
|
||
if (pIrpStack->Parameters.Write.Length == 0)
|
||
{
|
||
dprintf2(("Wave buffer is zero length"));
|
||
}
|
||
}
|
||
|
||
SoundStartWaveDevice(pLDI, pIrp);
|
||
return Status;
|
||
}
|
||
|
||
|
||
// ----------------------------------------------------------------
|
||
// Name: SoundWaveCreate
|
||
// Desc: Create call (for FILE_WRITE_DATA access). Read access
|
||
// is granted to anyone in SoundWaveDispatch.
|
||
// SoundWaveDispatch has also verified whether the device
|
||
// can be opened by calling back to the device-specific
|
||
// exlusion routine.
|
||
//
|
||
// Params: pLDI - our local device into
|
||
//
|
||
// Returns: STATUS_SUCCESS if OK or
|
||
// STATUS_BUSY if someone else has the device
|
||
VOID SoundWaveCreate( IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN PDEVICE_OBJECT DeviceObject )
|
||
{
|
||
NTSTATUS Status;
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
WaveInfo = (PWAVE_INFO)pLDI->DeviceSpecificData;
|
||
|
||
ASSERTMSG("Invalid Wave Info Pointer",
|
||
WaveInfo != NULL && WaveInfo->Key == WAVE_INFO_KEY);
|
||
|
||
|
||
ASSERT(pLDI->State == 0 &&
|
||
IsListEmpty(&WaveInfo->BufferQueue.QueueHead) ||
|
||
WaveInfo->LowPriorityHandle != NULL &&
|
||
!WaveInfo->LowPrioritySaved);
|
||
|
||
// Check the device thinks we're open
|
||
ASSERT((*pLDI->DeviceInit->ExclusionRoutine)
|
||
(pLDI, SoundExcludeQueryOpen));
|
||
|
||
// Check we can really open it and save any low priority device
|
||
if (WaveInfo->LowPriorityHandle)
|
||
{
|
||
SoundSaveLowPriority(WaveInfo->LowPriorityDevice);
|
||
}
|
||
WaveInfo->BufferQueue.BytesProcessed = 0;
|
||
|
||
// Initialize format changed flag
|
||
WaveInfo->FormatChanged = TRUE;
|
||
|
||
// Initialize state data and interrupt usage for
|
||
// the chosen device type. We set the rates etc to
|
||
// something anyone can handle. In reality a device is
|
||
// never used before the format is set but we must be
|
||
// unbreakable.
|
||
switch (pLDI->DeviceType)
|
||
{
|
||
case WAVE_IN:
|
||
pLDI->State = WAVE_DD_STOPPED;
|
||
WaveInfo->DeviceObject = DeviceObject;
|
||
WaveInfo->Direction = FALSE;
|
||
|
||
WaveInfo->SamplesPerSec = WAVE_DEFAULT_RATE;
|
||
WaveInfo->BitsPerSample = WAVE_DEFAULT_BITS_PER_SAMPLE;
|
||
WaveInfo->Channels = 1;
|
||
dprintf3(("Opened for wave input"));
|
||
|
||
SoundLineNotify(pLDI, SOUND_LINE_NOTIFY_WAVE);
|
||
Status = STATUS_SUCCESS;
|
||
break;
|
||
|
||
case WAVE_OUT:
|
||
pLDI->State = WAVE_DD_PLAYING;
|
||
WaveInfo->DeviceObject = DeviceObject;
|
||
WaveInfo->Direction = TRUE;
|
||
|
||
WaveInfo->SamplesPerSec = WAVE_DEFAULT_RATE;
|
||
WaveInfo->BitsPerSample = WAVE_DEFAULT_BITS_PER_SAMPLE;
|
||
WaveInfo->Channels = 1;
|
||
|
||
dprintf3(("Opened for wave output"));
|
||
|
||
SoundLineNotify(pLDI, SOUND_LINE_NOTIFY_WAVE);
|
||
Status = STATUS_SUCCESS;
|
||
break;
|
||
|
||
default:
|
||
ASSERT(FALSE);
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SoundRestoreLowPriority(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI
|
||
)
|
||
{
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
dprintf2(("SoundRestoreLowPriority"));
|
||
|
||
WaveInfo = (PWAVE_INFO)pLDI->DeviceSpecificData;
|
||
|
||
if (!WaveInfo->LowPrioritySaved) {
|
||
return STATUS_INVALID_DEVICE_REQUEST;
|
||
}
|
||
|
||
ASSERT(WaveInfo->LowPriorityHandle != NULL);
|
||
|
||
WaveInfo->LowPrioritySaved = FALSE;
|
||
|
||
WaveInfo->BufferQueue = WaveInfo->LowPriorityModeSave.BufferQueue;
|
||
SoundMoveCancellableQueue(
|
||
&WaveInfo->LowPriorityModeSave.BufferQueue.QueueHead,
|
||
&WaveInfo->BufferQueue.QueueHead);
|
||
|
||
WaveInfo->SamplesPerSec = WaveInfo->LowPriorityModeSave.SamplesPerSec;
|
||
WaveInfo->BitsPerSample = WaveInfo->LowPriorityModeSave.BitsPerSample;
|
||
WaveInfo->Channels = WaveInfo->LowPriorityModeSave.Channels;
|
||
WaveInfo->WaveFormat = WaveInfo->LowPriorityModeSave.WaveFormat;
|
||
pLDI->State = WaveInfo->LowPriorityModeSave.State;
|
||
|
||
WaveInfo->LowPriorityModeSave.WaveFormat = NULL;
|
||
|
||
|
||
/*
|
||
** Remember to set the format
|
||
*/
|
||
|
||
WaveInfo->FormatChanged = TRUE;
|
||
|
||
/*
|
||
** We're recording now
|
||
*/
|
||
|
||
WaveInfo->Direction = FALSE;
|
||
|
||
/*
|
||
** Note that line is active again. This is called when the device
|
||
** to be notified is active.
|
||
*/
|
||
|
||
SoundLineNotify(pLDI, SOUND_LINE_NOTIFY_VOICE);
|
||
|
||
/*
|
||
** Start up again
|
||
*/
|
||
|
||
SoundStartWaveDevice(pLDI, NULL);
|
||
}
|
||
|
||
NTSTATUS
|
||
SoundIoctlSetLowPriority(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN PFILE_OBJECT FileObject
|
||
)
|
||
{
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
dprintf2(("SoundIoctlSetLowPriority"));
|
||
|
||
WaveInfo = (PWAVE_INFO)pLDI->DeviceSpecificData;
|
||
|
||
if (WaveInfo->Direction) {
|
||
return STATUS_INVALID_DEVICE_REQUEST;
|
||
}
|
||
|
||
if (WaveInfo->LowPriorityHandle != NULL) {
|
||
return STATUS_DEVICE_BUSY;
|
||
}
|
||
|
||
WaveInfo->LowPriorityHandle = FileObject;
|
||
WaveInfo->LowPriorityDevice = pLDI;
|
||
|
||
SoundLineNotify(pLDI, SOUND_LINE_NOTIFY_WAVE);
|
||
SoundLineNotify(pLDI, SOUND_LINE_NOTIFY_VOICE);
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
VOID
|
||
SoundSaveLowPriority(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI
|
||
)
|
||
{
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
dprintf2(("SoundSaveLowPriority"));
|
||
|
||
WaveInfo = (PWAVE_INFO)pLDI->DeviceSpecificData;
|
||
|
||
/*
|
||
** Note that line will be inactive
|
||
*/
|
||
|
||
SoundLineNotify(pLDI, SOUND_LINE_NOTIFY_VOICE);
|
||
|
||
ASSERTMSG("SoundSaveLowPriority should always succeed!",
|
||
WaveInfo->LowPriorityHandle != NULL &&
|
||
!WaveInfo->LowPrioritySaved);
|
||
|
||
/*
|
||
** Only makes sense for recording (?)
|
||
*/
|
||
|
||
ASSERT(!WaveInfo->Direction);
|
||
|
||
/*
|
||
** Must be recording so stop any recording that's going on
|
||
*/
|
||
|
||
WaveInfo->LowPriorityModeSave.State = pLDI->State;
|
||
SoundStopWaveRecord(pLDI);
|
||
|
||
/*
|
||
** Save our state
|
||
** Note that the saved state of the Buffer Queue is invalid for the
|
||
** Irp chaining so we have to move it
|
||
*/
|
||
|
||
WaveInfo->LowPriorityModeSave.BufferQueue = WaveInfo->BufferQueue;
|
||
SoundMoveCancellableQueue(
|
||
&WaveInfo->BufferQueue.QueueHead,
|
||
&WaveInfo->LowPriorityModeSave.BufferQueue.QueueHead);
|
||
|
||
WaveInfo->LowPriorityModeSave.SamplesPerSec = WaveInfo->SamplesPerSec;
|
||
WaveInfo->LowPriorityModeSave.BitsPerSample = WaveInfo->BitsPerSample;
|
||
WaveInfo->LowPriorityModeSave.Channels = WaveInfo->Channels;
|
||
WaveInfo->LowPriorityModeSave.WaveFormat = WaveInfo->WaveFormat;
|
||
|
||
WaveInfo->LowPrioritySaved = TRUE;
|
||
WaveInfo->WaveFormat = NULL;
|
||
|
||
/*
|
||
** Avoid an assertion on open. The wave input device may now be
|
||
** effectively closed. It's a good thing NOBODY ever calls get
|
||
** state!
|
||
*/
|
||
|
||
pLDI->State = 0;
|
||
}
|
||
|
||
VOID
|
||
SoundFreeLowPriority(
|
||
PWAVE_INFO WaveInfo
|
||
)
|
||
{
|
||
dprintf2(("SoundFreeLowPriority"));
|
||
|
||
SoundFreeQ(&WaveInfo->LowPriorityModeSave.BufferQueue.QueueHead,
|
||
STATUS_CANCELLED);
|
||
WaveInfo->LowPriorityHandle = NULL;
|
||
WaveInfo->LowPrioritySaved = FALSE;
|
||
|
||
if (WaveInfo->LowPriorityModeSave.WaveFormat != NULL) {
|
||
ExFreePool(WaveInfo->LowPriorityModeSave.WaveFormat);
|
||
WaveInfo->LowPriorityModeSave.WaveFormat = NULL;
|
||
}
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SoundWaveCleanup(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN PFILE_OBJECT FileObject
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Clean up the requested device (this is effectively CLOSE)
|
||
|
||
Arguments:
|
||
|
||
pLDI - pointer to our local device info
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS if OK otherwise
|
||
STATUS_INTERNAL_ERROR
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
PWAVE_INFO WaveInfo;
|
||
UCHAR NotifyCode;
|
||
|
||
WaveInfo = (PWAVE_INFO)pLDI->DeviceSpecificData;
|
||
NotifyCode = SOUND_LINE_NOTIFY_WAVE;
|
||
|
||
ASSERTMSG("Invalid Wave Info Pointer",
|
||
WaveInfo != NULL && WaveInfo->Key == WAVE_INFO_KEY);
|
||
|
||
//
|
||
// Check this is valid call
|
||
//
|
||
|
||
ASSERT((*pLDI->DeviceInit->ExclusionRoutine)(pLDI, SoundExcludeQueryOpen));
|
||
|
||
//
|
||
// Call the device reset function to complete any
|
||
// pending i/o requests and terminate any current
|
||
// requests in progress
|
||
//
|
||
|
||
switch (pLDI->DeviceType) {
|
||
case WAVE_IN:
|
||
|
||
//
|
||
// Check for low priority
|
||
//
|
||
|
||
if (FileObject != NULL &&
|
||
FileObject == WaveInfo->LowPriorityHandle &&
|
||
WaveInfo->LowPrioritySaved) {
|
||
SoundFreeLowPriority(WaveInfo);
|
||
SoundLineNotify(pLDI, SOUND_LINE_NOTIFY_VOICE);
|
||
|
||
|
||
/*
|
||
** HACK HACK - we don't call the exclude routine or
|
||
** anything because half the device is still open!
|
||
*/
|
||
|
||
return STATUS_SUCCESS;
|
||
} else {
|
||
SoundStopWaveRecord(pLDI);
|
||
|
||
//
|
||
// Reset position to start and free any pending Irps.
|
||
//
|
||
|
||
SoundFreeQ(&WaveInfo->BufferQueue.QueueHead, STATUS_CANCELLED);
|
||
|
||
SoundSynchTimer(WaveInfo);
|
||
|
||
if (WaveInfo->WaveFormat) {
|
||
ExFreePool(WaveInfo->WaveFormat);
|
||
WaveInfo->WaveFormat = NULL;
|
||
}
|
||
|
||
if (!WaveInfo->LowPrioritySaved && WaveInfo->LowPriorityHandle) {
|
||
WaveInfo->LowPriorityHandle = NULL;
|
||
NotifyCode = SOUND_LINE_NOTIFY_VOICE;
|
||
}
|
||
}
|
||
break;
|
||
|
||
case WAVE_OUT:
|
||
|
||
//
|
||
// If anything is in the queue then free it.
|
||
// beware that the final block of a request may still be
|
||
// being dma'd when we get this call. We now kill this as well
|
||
// because we've changed such that the if the application thinks
|
||
// all the requests are complete then they are complete.
|
||
//
|
||
|
||
SoundStopDMA(WaveInfo, FALSE); // Stop with no pause
|
||
|
||
SoundResetOutput(&WaveInfo->BufferQueue);
|
||
|
||
SoundSynchTimer(WaveInfo);
|
||
|
||
|
||
if (WaveInfo->WaveFormat) {
|
||
ExFreePool(WaveInfo->WaveFormat);
|
||
WaveInfo->WaveFormat = NULL;
|
||
}
|
||
|
||
break;
|
||
|
||
default:
|
||
dprintf1(("Bogus device type for cleanup request"));
|
||
Status = STATUS_INTERNAL_ERROR;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// return the device to it's idle state
|
||
//
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
pLDI->State = 0;
|
||
(*pLDI->DeviceInit->ExclusionRoutine)(pLDI, SoundExcludeClose);
|
||
dprintf3(("Device closing"));
|
||
|
||
//
|
||
// See if we can restart low priority
|
||
//
|
||
|
||
if (WaveInfo->LowPrioritySaved) {
|
||
SoundRestoreLowPriority(WaveInfo->LowPriorityDevice);
|
||
}
|
||
}
|
||
//
|
||
// Notify AFTER everything's complete (in particular we've
|
||
// called the exclude routine). Otherwise the mixer may
|
||
// deduce the wrong current state.
|
||
//
|
||
|
||
SoundLineNotify(pLDI, NotifyCode);
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SoundIoctlGetWaveState(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN PIRP pIrp,
|
||
IN PIO_STACK_LOCATION IrpStack
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Get the current state of the device and return it to the caller.
|
||
This code is COMMON for :
|
||
Wave out
|
||
Wave in
|
||
|
||
Arguments:
|
||
|
||
pLDI - Pointer to our own device data
|
||
pIrp - Pointer to the IO Request Packet
|
||
IrpStack - Pointer to current stack location
|
||
|
||
Return Value:
|
||
|
||
Status to put into request packet by caller.
|
||
|
||
--*/
|
||
{
|
||
PULONG pState;
|
||
|
||
if (IrpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(ULONG)) {
|
||
dprintf1(("Supplied buffer to small for requested data"));
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
//
|
||
// say how much we're sending back
|
||
//
|
||
|
||
pIrp->IoStatus.Information = sizeof(ULONG);
|
||
|
||
//
|
||
// cast the buffer address to the pointer type we want
|
||
//
|
||
|
||
pState = (PULONG)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
||
//
|
||
// fill in the info -
|
||
//
|
||
|
||
|
||
//
|
||
// We don't bother to maintain the WAVE_DD_IDLE state internally
|
||
// for Wave output
|
||
//
|
||
|
||
if (pLDI->State == WAVE_DD_PLAYING) {
|
||
|
||
ASSERT(pLDI->DeviceType == WAVE_OUT);
|
||
|
||
//
|
||
// We need to know if it's really playing
|
||
// and DMABusy can be cleared by the Dpc routine so we need the spin lock
|
||
//
|
||
|
||
if (!((PWAVE_INFO)pLDI->DeviceSpecificData)->DMABusy) {
|
||
*pState = WAVE_DD_IDLE;
|
||
}
|
||
|
||
} else {
|
||
*pState = pLDI->State;
|
||
}
|
||
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
NTSTATUS SoundIoctlQueryFormat(
|
||
IN PLOCAL_DEVICE_INFO pLDI,
|
||
IN OUT PIRP pIrp,
|
||
IN PIO_STACK_LOCATION IrpStack
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Tell the caller whether the wave format specified (input or
|
||
output) is supported
|
||
|
||
Arguments:
|
||
|
||
pLDI - pointer to local device info
|
||
pIrp - the Irp
|
||
IrpStack - the current stack location
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - format is supported
|
||
STATUS_NOT_SUPPORTED - format not supported
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
PPCMWAVEFORMAT pFormat;
|
||
PWAVEFORMATEX pFormatEx;
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
WaveInfo = pLDI->DeviceSpecificData;
|
||
|
||
ASSERT(WaveInfo->Key == WAVE_INFO_KEY);
|
||
|
||
//
|
||
// check the buffer really is big enough to contain the struct
|
||
// we expect before digging into it. If not then assume it's a
|
||
// format we don't know how to do.
|
||
//
|
||
|
||
if (IrpStack->Parameters.DeviceIoControl.InputBufferLength <
|
||
sizeof(PCMWAVEFORMAT)) {
|
||
|
||
dprintf1(("Format data wrong size"));
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
//
|
||
// check the buffer really is big enough to contain the struct
|
||
// we expect before digging into it. If not then assume it's a
|
||
// format we don't know how to do.
|
||
//
|
||
|
||
pFormat = (PPCMWAVEFORMAT)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
||
pFormatEx = (PWAVEFORMATEX)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
||
if (pFormat->wf.wFormatTag != WAVE_FORMAT_PCM &&
|
||
(IrpStack->Parameters.DeviceIoControl.InputBufferLength <
|
||
sizeof(WAVEFORMATEX) ||
|
||
IrpStack->Parameters.DeviceIoControl.InputBufferLength <
|
||
sizeof(WAVEFORMATEX) + pFormatEx->cbSize)) {
|
||
|
||
dprintf1(("Format data wrong size"));
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
//
|
||
// Check if the device can support this format
|
||
//
|
||
|
||
Status = (*WaveInfo->QueryFormat)(pLDI, pFormat);
|
||
|
||
//
|
||
// If we're setting the format then copy it to our global info
|
||
//
|
||
|
||
if (Status == STATUS_SUCCESS &&
|
||
IrpStack->Parameters.DeviceIoControl.IoControlCode ==
|
||
IOCTL_WAVE_SET_FORMAT) {
|
||
|
||
PWAVEFORMATEX NewFormat;
|
||
|
||
NewFormat = NULL;
|
||
|
||
/*
|
||
** If it's not PCM then save the complete new format
|
||
*/
|
||
|
||
if (pFormat->wf.wFormatTag != WAVE_FORMAT_PCM) {
|
||
NewFormat =
|
||
ExAllocatePool(NonPagedPool, sizeof(WAVEFORMATEX) +
|
||
pFormatEx->cbSize);
|
||
|
||
if (NewFormat == NULL) {
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
RtlCopyMemory((PVOID)NewFormat,
|
||
(PVOID)pFormatEx,
|
||
sizeof(WAVEFORMATEX) +
|
||
pFormatEx->cbSize);
|
||
}
|
||
|
||
if (WaveInfo->WaveFormat != NULL) {
|
||
ExFreePool(WaveInfo->WaveFormat);
|
||
WaveInfo->WaveFormat = NULL;
|
||
}
|
||
|
||
WaveInfo->FormatChanged = TRUE;
|
||
WaveInfo->SamplesPerSec = pFormat->wf.nSamplesPerSec;
|
||
WaveInfo->BitsPerSample = (UCHAR)pFormat->wBitsPerSample;
|
||
WaveInfo->Channels = (UCHAR)pFormat->wf.nChannels;
|
||
WaveInfo->WaveFormat = NewFormat;
|
||
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SoundWaveDispatch(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN PIRP pIrp,
|
||
IN PIO_STACK_LOCATION IrpStack
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
WAVE IOCTL call dispatcher. This is the entry point for all wave
|
||
specific calls. See dispatch.c.
|
||
|
||
This routine should be in the DispatchRoutine entry of the DeviceInit
|
||
structure hanging off the local device info - see devices.h.
|
||
|
||
Arguments:
|
||
|
||
pLDI - Pointer to local device data
|
||
pIrp - Pointer to IO request packet
|
||
IrpStack - Pointer to current stack location
|
||
|
||
Return Value:
|
||
|
||
Return status from dispatched routine
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
if (((PWAVE_INFO)pLDI->DeviceSpecificData)->DeviceBad) {
|
||
return STATUS_INVALID_DEVICE_REQUEST;
|
||
}
|
||
|
||
switch (IrpStack->MajorFunction) {
|
||
case IRP_MJ_CREATE:
|
||
Status = SoundSetShareAccess(pLDI, IrpStack);
|
||
if (NT_SUCCESS(Status) && IrpStack->FileObject->WriteAccess) {
|
||
SoundWaveCreate(pLDI, IrpStack->DeviceObject);
|
||
}
|
||
|
||
break;
|
||
|
||
case IRP_MJ_CLOSE:
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
break;
|
||
|
||
case IRP_MJ_READ:
|
||
case IRP_MJ_WRITE:
|
||
|
||
if (IrpStack->FileObject->WriteAccess) {
|
||
Status = SoundWaveData(pLDI, pIrp, IrpStack);
|
||
} else {
|
||
Status = STATUS_ACCESS_DENIED;
|
||
}
|
||
break;
|
||
|
||
|
||
case IRP_MJ_DEVICE_CONTROL:
|
||
|
||
//
|
||
// Check that if someone has the device open for 'write' it's
|
||
// marked as in use.
|
||
//
|
||
|
||
ASSERT(!IrpStack->FileObject->WriteAccess ||
|
||
(*pLDI->DeviceInit->ExclusionRoutine)
|
||
(pLDI, SoundExcludeQueryOpen));
|
||
//
|
||
// Check device access
|
||
//
|
||
if (!IrpStack->FileObject->WriteAccess) {
|
||
|
||
switch (IrpStack->Parameters.DeviceIoControl.IoControlCode) {
|
||
case IOCTL_WAVE_GET_VOLUME:
|
||
case IOCTL_WAVE_SET_VOLUME:
|
||
case IOCTL_SOUND_GET_CHANGED_VOLUME:
|
||
|
||
if (pLDI->PreventVolumeSetting) {
|
||
Status = STATUS_ACCESS_DENIED;
|
||
}
|
||
break;
|
||
|
||
|
||
case IOCTL_WAVE_GET_CAPABILITIES:
|
||
case IOCTL_WAVE_QUERY_FORMAT:
|
||
break;
|
||
|
||
default:
|
||
Status = STATUS_ACCESS_DENIED;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
//
|
||
// Dispatch the IOCTL function
|
||
// Note that some IOCTLs only make sense for input or output
|
||
// devices and not both.
|
||
// Note that APIs which are possibly asynchronous do not
|
||
// go through the Irp cleanup at the end here because they
|
||
// may get completed before returning here or they are made
|
||
// accessible to other requests by being queued.
|
||
//
|
||
|
||
switch (IrpStack->Parameters.DeviceIoControl.IoControlCode) {
|
||
|
||
case IOCTL_WAVE_SET_FORMAT:
|
||
case IOCTL_WAVE_QUERY_FORMAT:
|
||
Status = SoundIoctlQueryFormat(pLDI, pIrp, IrpStack);
|
||
break;
|
||
|
||
case IOCTL_WAVE_GET_CAPABILITIES:
|
||
Status = (*pLDI->DeviceInit->DevCapsRoutine)(pLDI, pIrp, IrpStack);
|
||
break;
|
||
|
||
case IOCTL_WAVE_SET_STATE:
|
||
if (IrpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(ULONG)) {
|
||
dprintf1(("Supplied buffer too small for expected data"));
|
||
Status = STATUS_BUFFER_TOO_SMALL;
|
||
} else {
|
||
PULONG pState;
|
||
|
||
//
|
||
// cast the buffer address to the pointer type we want
|
||
//
|
||
|
||
pState = (PULONG)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
||
switch (pLDI->DeviceType) {
|
||
case WAVE_IN:
|
||
Status = SoundSetWaveInputState(
|
||
pLDI,
|
||
*pState,
|
||
IrpStack->FileObject);
|
||
break;
|
||
|
||
case WAVE_OUT:
|
||
Status = SoundSetWaveOutputState(pLDI, *pState, pIrp);
|
||
break;
|
||
}
|
||
}
|
||
break;
|
||
|
||
case IOCTL_WAVE_SET_LOW_PRIORITY:
|
||
|
||
/*
|
||
** Try to turn a recording device into a low priority
|
||
** recording device.
|
||
*/
|
||
|
||
if (IrpStack->FileObject->WriteAccess) {
|
||
Status = SoundIoctlSetLowPriority(
|
||
pLDI,
|
||
IrpStack->FileObject);
|
||
} else {
|
||
Status = STATUS_ACCESS_DENIED;
|
||
}
|
||
break;
|
||
|
||
case IOCTL_WAVE_GET_STATE:
|
||
Status = SoundIoctlGetWaveState(pLDI, pIrp, IrpStack);
|
||
break;
|
||
|
||
case IOCTL_WAVE_GET_POSITION:
|
||
Status = SoundIoctlGetPosition(pLDI, pIrp, IrpStack);
|
||
break;
|
||
|
||
case IOCTL_WAVE_SET_VOLUME:
|
||
Status = SoundIoctlSetVolume(pLDI, pIrp, IrpStack);
|
||
break;
|
||
|
||
case IOCTL_WAVE_GET_VOLUME:
|
||
Status = SoundIoctlGetVolume(pLDI, pIrp, IrpStack);
|
||
break;
|
||
|
||
case IOCTL_SOUND_GET_CHANGED_VOLUME:
|
||
Status = SoundIoctlGetChangedVolume(pLDI, pIrp, IrpStack);
|
||
break;
|
||
|
||
case IOCTL_WAVE_SET_PITCH:
|
||
Status = STATUS_NOT_SUPPORTED;
|
||
break;
|
||
|
||
case IOCTL_WAVE_GET_PITCH:
|
||
// Status = SoundIoctlGetPitch(pLDI, pIrp, IrpStack);
|
||
Status = STATUS_NOT_SUPPORTED;
|
||
break;
|
||
|
||
case IOCTL_WAVE_SET_PLAYBACK_RATE:
|
||
Status = STATUS_NOT_SUPPORTED;
|
||
break;
|
||
|
||
case IOCTL_WAVE_GET_PLAYBACK_RATE:
|
||
// Status = SoundIoctlGetPlaybackRate(pLDI, pIrp, IrpStack);
|
||
Status = STATUS_NOT_SUPPORTED;
|
||
break;
|
||
|
||
#if 0
|
||
case IOCTL_WAVE_SET_DEBUG_LEVEL:
|
||
Status = SoundIoctlSetDebugLevel(pLDI, pIrp, IrpStack);
|
||
break;
|
||
#endif
|
||
|
||
default:
|
||
dprintf2(("Unimplemented IOCTL (%08lXH) requested", IrpStack->Parameters.DeviceIoControl.IoControlCode));
|
||
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
|
||
|
||
case IRP_MJ_CLEANUP:
|
||
if (IrpStack->FileObject->WriteAccess) {
|
||
Status = SoundWaveCleanup(pLDI, IrpStack->FileObject);
|
||
pLDI->PreventVolumeSetting = FALSE;
|
||
} else {
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
break;
|
||
|
||
|
||
default:
|
||
dprintf2(("Unimplemented major function requested: %08lXH", IrpStack->MajorFunction));
|
||
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
break;
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SoundSetWaveInputState(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI,
|
||
IN ULONG State,
|
||
IN PFILE_OBJECT FileObject
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Determine which sound recording function to call depending on the
|
||
state to be set.
|
||
|
||
Arguments:
|
||
|
||
pLDI - Pointer to local device data
|
||
State - the new state to set
|
||
LowPri - This is the low priority device
|
||
|
||
Return Value:
|
||
|
||
Return status for caller
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
PWAVE_INFO WaveInfo;
|
||
PSOUND_BUFFER_QUEUE BufferQueue;
|
||
BOOLEAN LowPriSaved;
|
||
|
||
WaveInfo = pLDI->DeviceSpecificData;
|
||
|
||
LowPriSaved = (BOOLEAN)(WaveInfo->LowPrioritySaved &&
|
||
FileObject == WaveInfo->LowPriorityHandle);
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
switch (State) {
|
||
case WAVE_DD_RECORD:
|
||
|
||
if (LowPriSaved) {
|
||
WaveInfo->LowPriorityModeSave.State = WAVE_DD_RECORDING;
|
||
} else {
|
||
SoundStartWaveRecord(pLDI);
|
||
dprintf3(("Input started"));
|
||
}
|
||
break;
|
||
|
||
case WAVE_DD_STOP:
|
||
|
||
if (LowPriSaved) {
|
||
WaveInfo->LowPriorityModeSave.State = WAVE_DD_STOPPED;
|
||
} else {
|
||
SoundStopWaveRecord(pLDI);
|
||
dprintf3(("Input stopped"));
|
||
}
|
||
break;
|
||
|
||
case WAVE_DD_RESET:
|
||
|
||
if (LowPriSaved) {
|
||
WaveInfo->LowPriorityModeSave.State = WAVE_DD_STOPPED;
|
||
BufferQueue = &WaveInfo->LowPriorityModeSave.BufferQueue;
|
||
} else {
|
||
SoundStopWaveRecord(pLDI);
|
||
BufferQueue = &WaveInfo->BufferQueue;
|
||
}
|
||
|
||
//
|
||
// Reset position to start and free any pending Irps.
|
||
//
|
||
|
||
SoundFreeQ(&BufferQueue->QueueHead, STATUS_CANCELLED);
|
||
BufferQueue->BytesProcessed = 0;
|
||
|
||
dprintf3(("Input reset"));
|
||
break;
|
||
|
||
default:
|
||
|
||
dprintf1(("Bogus set output state request: %08lXH", State));
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
break;
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
VOID
|
||
SoundResetOutput(
|
||
IN OUT PSOUND_BUFFER_QUEUE BufferQueue
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Clear out all the wave output buffers supplied by the application,
|
||
cancelling related IO request packets.
|
||
|
||
Set the Position to 0.
|
||
|
||
Arguments:
|
||
|
||
WaveInfo - Pointer to structure controlling processing of Irps for
|
||
this device
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
|
||
SoundCompleteIoBuffer(BufferQueue);
|
||
|
||
//
|
||
// Free all our lists of Irps, in the correct order
|
||
//
|
||
SoundFreeQ(&BufferQueue->ProgressQueue, STATUS_CANCELLED);
|
||
SoundFreeQ(&BufferQueue->QueueHead, STATUS_CANCELLED);
|
||
|
||
//
|
||
// Reset the output position count
|
||
//
|
||
|
||
BufferQueue->BytesProcessed = 0;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SoundSetWaveOutputState(
|
||
PLOCAL_DEVICE_INFO pLDI,
|
||
ULONG State,
|
||
PIRP pIrp
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Set the new sound state. This is the most complicated part of the
|
||
wave stuff because pauses cannot be completed immediately if there
|
||
is stuff being DMAd.
|
||
|
||
If reset is requested then additionally all the data supplied by
|
||
the application is deleted (the Irps are signalled as cancelled)
|
||
and the Position is set to 0. In this case the WAVE_DD_STOPPED
|
||
state is set until the reset is complete.
|
||
|
||
|
||
Arguments:
|
||
|
||
pLDI - local device info
|
||
State - the new state
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status = STATUS_INTERNAL_ERROR;
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
WaveInfo = pLDI->DeviceSpecificData;
|
||
|
||
switch (State) {
|
||
|
||
case WAVE_DD_RESET:
|
||
case WAVE_DD_STOP:
|
||
|
||
SoundStopDMA(WaveInfo, (BOOLEAN)(State == WAVE_DD_STOP));
|
||
|
||
if (State == WAVE_DD_RESET) {
|
||
SoundResetOutput(&WaveInfo->BufferQueue);
|
||
pLDI->State = WAVE_DD_PLAYING;
|
||
} else {
|
||
//
|
||
// Set STOPPED state for now anyway so we don't try to put
|
||
// anything more in the buffer
|
||
//
|
||
|
||
pLDI->State = WAVE_DD_STOPPED;
|
||
|
||
}
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
|
||
|
||
dprintf3(("Output stopped"));
|
||
break;
|
||
|
||
|
||
case WAVE_DD_PLAY:
|
||
//
|
||
// Restart playing. If we're already playing no need to
|
||
// restart, otherwise it's safe to restart.
|
||
//
|
||
|
||
pLDI->State = WAVE_DD_PLAYING;
|
||
SoundStartWaveDevice(pLDI, NULL);
|
||
|
||
Status = STATUS_SUCCESS;
|
||
dprintf3(("Output restarted"));
|
||
break;
|
||
|
||
default:
|
||
|
||
dprintf1(("Bogus set output state request: %08lXH", State));
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
break;
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
SoundStartWaveRecord(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Process the WAVE_DD_RECORD state change
|
||
|
||
If recording has already started just return
|
||
Otherwise start our DMA.
|
||
|
||
Arguments:
|
||
|
||
pLDI - our local device info
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
dprintf5(("SoundStartWaveRecord(): Start"));
|
||
|
||
WaveInfo = pLDI->DeviceSpecificData;
|
||
|
||
ASSERTMSG("Recording on output device !", !WaveInfo->Direction);
|
||
|
||
if (pLDI->State == WAVE_DD_RECORDING) {
|
||
ASSERT(WaveInfo->DMABusy);
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Set state
|
||
//
|
||
|
||
pLDI->State = WAVE_DD_RECORDING;
|
||
|
||
//
|
||
// Start the input
|
||
//
|
||
|
||
SoundStartWaveDevice(pLDI, NULL);
|
||
|
||
|
||
//
|
||
// Function is complete
|
||
//
|
||
|
||
}
|
||
|
||
|
||
VOID
|
||
SoundStopWaveRecord(
|
||
IN OUT PLOCAL_DEVICE_INFO pLDI
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Stop wave recording.
|
||
|
||
If recording is not in progress just return sucess.
|
||
Otherwise stop the DMA and return the data we have so far
|
||
recorded in the DMA buffer.
|
||
|
||
Arguments:
|
||
|
||
pLDI - pointer to our local device data
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
WaveInfo = pLDI->DeviceSpecificData;
|
||
|
||
ASSERTMSG("Recording on output device !", !WaveInfo->Direction);
|
||
|
||
if (WaveInfo->DMABusy) {
|
||
ULONG BufferPosition;
|
||
|
||
ASSERT(pLDI->State == WAVE_DD_RECORDING);
|
||
|
||
BufferPosition = SoundGetBufferPosition(WaveInfo);
|
||
|
||
//
|
||
// Stop any more input
|
||
//
|
||
|
||
SoundStopDMA(WaveInfo, FALSE);
|
||
|
||
//
|
||
// Pass back any data to the application. SoundFillInputBuffers
|
||
// returns TRUE if it completes any buffers
|
||
//
|
||
|
||
if (!SoundFillInputBuffers(WaveInfo, BufferPosition)) {
|
||
SoundGetNextBuffer(&WaveInfo->BufferQueue);
|
||
|
||
//
|
||
// Send back the first buffer if there is one
|
||
//
|
||
|
||
if (WaveInfo->BufferQueue.UserBuffer) {
|
||
WaveInfo->BufferQueue.pIrp->IoStatus.Status = STATUS_SUCCESS;
|
||
|
||
SoundCompleteIoBuffer(&WaveInfo->BufferQueue);
|
||
|
||
IoCompleteRequest(WaveInfo->BufferQueue.pIrp, IO_SOUND_INCREMENT);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Set state and make sure buffers are clear
|
||
//
|
||
|
||
pLDI->State = WAVE_DD_STOPPED;
|
||
}
|
||
}
|
||
|
||
VOID
|
||
SoundComputePeak(
|
||
IN PWAVE_INFO WaveInfo,
|
||
IN PBYTE Bytes,
|
||
IN ULONG Length,
|
||
IN OUT PLONG pAmplitudes
|
||
)
|
||
{
|
||
LONG Amplitudes[2];
|
||
|
||
ASSERTMSG("WAVE_INFO structure not correctly initialized",
|
||
WaveInfo != NULL && WaveInfo->Key == WAVE_INFO_KEY);
|
||
|
||
Amplitudes[0] = Amplitudes[1] = 0;
|
||
|
||
/*
|
||
** If we don't understand the format then give up
|
||
*/
|
||
|
||
if (WaveInfo->WaveFormat != NULL) {
|
||
return;
|
||
}
|
||
|
||
/*
|
||
** Check what format we have
|
||
*/
|
||
|
||
if (WaveInfo->Channels == 1 &&
|
||
WaveInfo->BitsPerSample == 8) {
|
||
UCHAR Min, Max, Diff;
|
||
LONG lMin, lMax;
|
||
|
||
Min = 0x80;
|
||
Max = 0x80;
|
||
Diff = 0x00;
|
||
|
||
for ( ; Length ; Length--, Bytes++) {
|
||
|
||
/*
|
||
** Don't know if this is 100% portable but it's going to save
|
||
** some time!
|
||
*/
|
||
|
||
if ((UCHAR)(*Bytes - Min) > Diff) {
|
||
if (*Bytes < Min) {
|
||
Min = *Bytes;
|
||
} else {
|
||
Max = *Bytes;
|
||
}
|
||
Diff = (UCHAR)(Max - Min);
|
||
}
|
||
}
|
||
|
||
|
||
lMin = ((LONG)(ULONG)Min - 0x80) << 8;
|
||
lMax = ((LONG)(ULONG)Max - 0x80) << 8;
|
||
|
||
if (-lMin > lMax) {
|
||
Amplitudes[0] = lMin;
|
||
} else {
|
||
Amplitudes[0] = lMax;
|
||
}
|
||
|
||
Amplitudes[1] = Amplitudes[0];
|
||
} else
|
||
|
||
if (WaveInfo->Channels == 2 &&
|
||
WaveInfo->BitsPerSample == 8) {
|
||
UCHAR MinL, MaxL, MinR, MaxR, DiffL, DiffR;
|
||
LONG lMin, lMax;
|
||
|
||
MinL = 0x80;
|
||
MaxL = 0x80;
|
||
DiffL = 0x00;
|
||
MinR = 0x80;
|
||
MaxR = 0x80;
|
||
DiffR = 0x00;
|
||
|
||
for ( Length = Length / 2 ; Length ; Length--) {
|
||
|
||
UCHAR Value;
|
||
|
||
/*
|
||
** Don't know if this is 100% portable but it's going to save
|
||
** some time!
|
||
*/
|
||
|
||
Value = *Bytes++;
|
||
if ((UCHAR)(Value - MinL) > DiffL) {
|
||
if (Value < MinL) {
|
||
MinL = Value;
|
||
} else {
|
||
MaxL = Value;
|
||
}
|
||
DiffL = (UCHAR)(MaxL - MinL);
|
||
}
|
||
Value = *Bytes++;
|
||
if ((UCHAR)(Value - MinR) > DiffR) {
|
||
if (Value < MinR) {
|
||
MinR = Value;
|
||
} else {
|
||
MaxR = Value;
|
||
}
|
||
DiffR = (UCHAR)(MaxR - MinR);
|
||
}
|
||
}
|
||
|
||
lMin = ((LONG)(ULONG)MinL - 0x80) << 8;
|
||
lMax = ((LONG)(ULONG)MaxL - 0x80) << 8;
|
||
|
||
if (-lMin > lMax) {
|
||
Amplitudes[0] = lMin;
|
||
} else {
|
||
Amplitudes[0] = lMax;
|
||
}
|
||
|
||
lMin = ((LONG)(ULONG)MinR - 0x80) << 8;
|
||
lMax = ((LONG)(ULONG)MaxR - 0x80) << 8;
|
||
|
||
if (-lMin > lMax) {
|
||
Amplitudes[1] = lMin;
|
||
} else {
|
||
Amplitudes[1] = lMax;
|
||
}
|
||
|
||
} else
|
||
|
||
if (WaveInfo->Channels == 1 &&
|
||
WaveInfo->BitsPerSample == 16) {
|
||
|
||
PSHORT pSamples;
|
||
LONG lMin, lMax;
|
||
LONG Value;
|
||
|
||
lMin = 0;
|
||
lMax = 0;
|
||
Length = Length / 2;
|
||
|
||
for ( pSamples = (PSHORT)Bytes ; Length ; Length--) {
|
||
|
||
Value = (LONG)*pSamples++;
|
||
if (Value >= lMin && Value <= lMax) {
|
||
} else {
|
||
if (Value < lMin) {
|
||
lMin = Value;
|
||
} else {
|
||
lMax = Value;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (-lMin > lMax) {
|
||
Amplitudes[0] = lMin;
|
||
} else {
|
||
Amplitudes[0] = lMax;
|
||
}
|
||
|
||
Amplitudes[1] = Amplitudes[0];
|
||
} else
|
||
|
||
if (WaveInfo->Channels == 2 &&
|
||
WaveInfo->BitsPerSample == 16) {
|
||
|
||
PSHORT pSamples;
|
||
LONG lMinL, lMaxL;
|
||
LONG lMinR, lMaxR;
|
||
LONG Value;
|
||
|
||
lMinL = 0;
|
||
lMaxL = 0;
|
||
lMinR = 0;
|
||
lMaxR = 0;
|
||
Length = Length / 4;
|
||
|
||
for ( pSamples = (PSHORT)Bytes ; Length ; Length--) {
|
||
|
||
Value = (LONG)*pSamples++;
|
||
if (Value >= lMinL && Value <= lMaxL) {
|
||
} else {
|
||
if (Value < lMinL) {
|
||
lMinL = Value;
|
||
} else {
|
||
lMaxL = Value;
|
||
}
|
||
}
|
||
|
||
Value = (LONG)*pSamples++;
|
||
if (Value >= lMinR && Value <= lMaxR) {
|
||
} else {
|
||
if (Value < lMinR) {
|
||
lMinR = Value;
|
||
} else {
|
||
lMaxR = Value;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (-lMinL > lMaxL) {
|
||
Amplitudes[0] = lMinL;
|
||
} else {
|
||
Amplitudes[0] = lMaxL;
|
||
}
|
||
|
||
if (-lMinR > lMaxR) {
|
||
Amplitudes[1] = lMinR;
|
||
} else {
|
||
Amplitudes[1] = lMaxR;
|
||
}
|
||
}
|
||
/*
|
||
** Combine with previous
|
||
*/
|
||
|
||
if (absval(Amplitudes[0]) > absval(pAmplitudes[0])) {
|
||
pAmplitudes[0] = Amplitudes[0];
|
||
}
|
||
|
||
if (absval(Amplitudes[1]) > absval(pAmplitudes[1])) {
|
||
pAmplitudes[1] = Amplitudes[1];
|
||
}
|
||
}
|
||
|
||
BOOLEAN
|
||
SoundPeakMeter(
|
||
IN PWAVE_INFO WaveInfo,
|
||
OUT PLONG Amplitudes
|
||
)
|
||
/*++
|
||
|
||
Routine Description
|
||
Find the peak of the last set of samples played
|
||
--*/
|
||
{
|
||
KIRQL OldIrql;
|
||
|
||
Amplitudes[0] = 0;
|
||
Amplitudes[1] = 0;
|
||
|
||
if (WaveInfo->DMABusy) {
|
||
ULONG DmaPosition;
|
||
ULONG QuarterSize;
|
||
|
||
/*
|
||
** Raise IRQL so we can keep up with the DMA!
|
||
*/
|
||
KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
|
||
|
||
|
||
/*
|
||
** Find out where we are in the DMA buffer
|
||
*/
|
||
|
||
DmaPosition = SoundGetBufferPosition(WaveInfo);
|
||
|
||
/*
|
||
** Do one or two computations depending on where we are in the buffer
|
||
*/
|
||
|
||
QuarterSize = WaveInfo->DoubleBuffer.BufferSize / 4;
|
||
|
||
if (DmaPosition < QuarterSize) {
|
||
SoundComputePeak(WaveInfo,
|
||
WaveInfo->DoubleBuffer.Buf,
|
||
DmaPosition,
|
||
Amplitudes);
|
||
|
||
SoundComputePeak(WaveInfo,
|
||
WaveInfo->DoubleBuffer.Buf +
|
||
(WaveInfo->DoubleBuffer.BufferSize - QuarterSize) +
|
||
DmaPosition,
|
||
QuarterSize - DmaPosition,
|
||
Amplitudes);
|
||
} else {
|
||
SoundComputePeak(WaveInfo,
|
||
WaveInfo->DoubleBuffer.Buf +
|
||
DmaPosition - QuarterSize,
|
||
QuarterSize,
|
||
Amplitudes);
|
||
}
|
||
KeLowerIrql(OldIrql);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
ULONG
|
||
SoundGetBufferPosition(
|
||
IN CONST WAVE_INFO * WaveInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description
|
||
Find position in by reading the Dma counter
|
||
Returns 0 if Dma is not running.
|
||
|
||
Requires caller to hold spin lock.
|
||
|
||
--*/
|
||
{
|
||
ULONG DmaPosition;
|
||
ULONG BytesPerSample;
|
||
|
||
if (!WaveInfo->DMABusy) {
|
||
return 0;
|
||
}
|
||
|
||
//
|
||
// Find out where we are in the DMA buffer
|
||
//
|
||
|
||
DmaPosition = HalReadDmaCounter(WaveInfo->DMABuf.AdapterObject[0]);
|
||
|
||
if (WaveInfo->DMAType != SoundAutoInitDMA ) {
|
||
if( !WaveInfo->Direction ){
|
||
//
|
||
// Compensate for reprogram double buffer method
|
||
//
|
||
if (WaveInfo->DoubleBuffer.NextHalf == LowerHalf) {
|
||
DmaPosition += WaveInfo->DoubleBuffer.BufferSize / 2;
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
if (DmaPosition >= WaveInfo->DoubleBuffer.BufferSize) {
|
||
DmaPosition = 0;
|
||
} else {
|
||
DmaPosition = WaveInfo->DoubleBuffer.BufferSize - DmaPosition;
|
||
}
|
||
|
||
//
|
||
// Wrap around
|
||
//
|
||
|
||
if (DmaPosition == WaveInfo->DoubleBuffer.BufferSize) {
|
||
DmaPosition = 0;
|
||
}
|
||
|
||
//
|
||
// Make sure we don't get in the middle of a sample - this ASSUMES
|
||
// the bytes per sample is a power of 2!!
|
||
//
|
||
BytesPerSample = (WaveInfo->BitsPerSample * WaveInfo->Channels) >> 3;
|
||
DmaPosition = DmaPosition & ~(BytesPerSample - 1);
|
||
|
||
return DmaPosition;
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SoundIoctlGetPosition(
|
||
IN PLOCAL_DEVICE_INFO pLDI,
|
||
IN PIRP pIrp,
|
||
IN PIO_STACK_LOCATION IrpStack
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
IOCTL get wave position. Read the current position of wave output.
|
||
|
||
|
||
Arguments:
|
||
|
||
pLDI - Local device data
|
||
pIrp - IO request packet
|
||
IrpStack - current Irp stack location
|
||
|
||
Return Value:
|
||
|
||
Irp status
|
||
|
||
--*/
|
||
{
|
||
PWAVE_DD_POSITION pPosition;
|
||
NTSTATUS status;
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
status = STATUS_SUCCESS;
|
||
WaveInfo = pLDI->DeviceSpecificData;
|
||
|
||
if (IrpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
||
sizeof(WAVE_DD_POSITION)) {
|
||
dprintf1(("Supplied buffer to small for requested data"));
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
//
|
||
// say how much we're sending back
|
||
//
|
||
|
||
pIrp->IoStatus.Information = sizeof(WAVE_DD_POSITION);
|
||
|
||
//
|
||
// cast the buffer address to the pointer type we want
|
||
//
|
||
|
||
pPosition = (PWAVE_DD_POSITION)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
||
|
||
//
|
||
// If DMA is still running we need to check how far it has
|
||
// progressed
|
||
//
|
||
|
||
DMAEnter(WaveInfo);
|
||
|
||
pPosition->ByteCount = WaveInfo->BufferQueue.BytesProcessed;
|
||
|
||
//
|
||
// Don't adjust answer for wave record becase we haven't
|
||
// yet copied the data to the application's buffers. Sound
|
||
// recorder in particular would get confused by this and mess
|
||
// up its display.
|
||
//
|
||
|
||
if (WaveInfo->DMABusy && WaveInfo->Direction) {
|
||
|
||
pPosition->ByteCount +=
|
||
BytesProcessedInBuffer(
|
||
&WaveInfo->DoubleBuffer,
|
||
OffsetInBuffer(
|
||
&WaveInfo->DoubleBuffer,
|
||
SoundGetBufferPosition(WaveInfo)));
|
||
}
|
||
|
||
DMALeave(WaveInfo);
|
||
|
||
//
|
||
// Convert to samples
|
||
//
|
||
|
||
pPosition->SampleCount =
|
||
(pPosition->ByteCount << 3) /
|
||
(WaveInfo->BitsPerSample * WaveInfo->Channels);
|
||
|
||
return status;
|
||
}
|
||
|
||
|
||
/**************************************************************************
|
||
*
|
||
* Starting and stopping DMA
|
||
*
|
||
**************************************************************************/
|
||
|
||
BOOLEAN
|
||
SoundMapDMA(
|
||
IN PWAVE_INFO WaveInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Call IoMapTransfer to start the DMA
|
||
|
||
Arguments:
|
||
|
||
WaveInfo - Wave parameters and state
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
ULONG length;
|
||
|
||
#if DBG
|
||
ULONG LengthRequested;
|
||
#endif // DBG
|
||
|
||
ULONG offset;
|
||
int Half;
|
||
|
||
Half = 0;
|
||
|
||
dprintf2(( "M" ));
|
||
|
||
if (WaveInfo->DMAType == SoundAutoInitDMA || WaveInfo->Direction) {
|
||
|
||
length = WaveInfo->DoubleBuffer.BufferSize;
|
||
offset = 0;
|
||
|
||
} else {
|
||
|
||
length = WaveInfo->DoubleBuffer.BufferSize / 2 ;
|
||
offset = WaveInfo->DoubleBuffer.NextHalf == LowerHalf ? 0 : length;
|
||
|
||
if (WaveInfo->DMAType == Sound2ChannelDMA) {
|
||
Half = WaveInfo->DoubleBuffer.NextHalf;
|
||
}
|
||
}
|
||
|
||
WaveInfo->MapOn = 0;
|
||
|
||
#if DBG
|
||
LengthRequested = length;
|
||
#endif // DBG
|
||
|
||
dprintf4(("Calling IoMapTransfer"));
|
||
IoMapTransfer(WaveInfo->DMABuf.AdapterObject[Half],
|
||
WaveInfo->DMABuf.Mdl,
|
||
WaveInfo->MRB[Half],
|
||
(PUCHAR)MmGetMdlVirtualAddress(WaveInfo->DMABuf.Mdl) +
|
||
offset,
|
||
&length,
|
||
WaveInfo->Direction);
|
||
// Direction
|
||
|
||
ASSERTMSG("Incorrect length mapped by IoMapTransfer",
|
||
length == LengthRequested);
|
||
//
|
||
// Now program the card to begin the transfer.
|
||
// Note that this must be synchronized with the isr so
|
||
// that we don't start taking interrupts prematurely
|
||
//
|
||
|
||
dprintf4(("Calling (sync) HwSetupDMA"));
|
||
|
||
//
|
||
// Prepare the hardware for running DMA
|
||
//
|
||
|
||
return TRUE;
|
||
|
||
}
|
||
|
||
|
||
|
||
BOOLEAN
|
||
SoundFlushDMA(
|
||
IN PWAVE_INFO WaveInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Call IoMapTransfer to start the DMA
|
||
Arguments:
|
||
|
||
WaveInfo - Wave parameters and state
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
ULONG length;
|
||
ULONG offset;
|
||
int Half;
|
||
|
||
Half = 0;
|
||
|
||
if (WaveInfo->DMAType == SoundAutoInitDMA || WaveInfo->Direction ) {
|
||
|
||
length = WaveInfo->DoubleBuffer.BufferSize;
|
||
offset = 0;
|
||
|
||
} else {
|
||
length = WaveInfo->DoubleBuffer.BufferSize / 2;
|
||
offset = WaveInfo->DoubleBuffer.NextHalf == LowerHalf ? 0 : length;
|
||
if (WaveInfo->DMAType == Sound2ChannelDMA) {
|
||
Half = WaveInfo->DoubleBuffer.NextHalf;
|
||
}
|
||
}
|
||
|
||
//
|
||
// IoFlushAdapterBuffers masks off the DMA amongst other things
|
||
//
|
||
|
||
IoFlushAdapterBuffers(WaveInfo->DMABuf.AdapterObject[Half],
|
||
WaveInfo->DMABuf.Mdl,
|
||
WaveInfo->MRB[Half],
|
||
(PUCHAR)MmGetMdlVirtualAddress(WaveInfo->DMABuf.Mdl) +
|
||
offset,
|
||
length,
|
||
WaveInfo->Direction);
|
||
// Direction
|
||
return TRUE;
|
||
}
|
||
|
||
VOID
|
||
SoundTestDeviceDeferred(
|
||
IN PKDPC Dpc,
|
||
IN PVOID Context,
|
||
IN PVOID Param1,
|
||
IN PVOID Param2
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Tests if our kernel device is still active
|
||
|
||
Arguments:
|
||
|
||
Dpc - Our DPC object
|
||
Context - our wave info structure
|
||
Param1 - not used - system time
|
||
Param2 - not used - system time
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
WaveInfo = (PWAVE_INFO)Context;
|
||
|
||
DMAEnter(WaveInfo);
|
||
|
||
if (WaveInfo->DMABusy) {
|
||
|
||
if (!WaveInfo->GotWaveDpc) {
|
||
|
||
//
|
||
// We're broken
|
||
//
|
||
|
||
dprintf2(("No interrupt from Wave device for 3 seconds! - cancelling IO"));
|
||
dprintf2(("Device was wave %s", WaveInfo->Direction ? "output" : "input"));
|
||
|
||
if (WaveInfo->FailureCount++ == 30 ) {
|
||
dprintf1(("Device has failed 30 times in a row - mark it as bad"));
|
||
WaveInfo->DeviceBad = TRUE;
|
||
}
|
||
|
||
//
|
||
// But we might be able to start up again! so clean up our
|
||
// state
|
||
//
|
||
|
||
WaveInfo->TimerActive = FALSE;
|
||
WaveInfo->DMABusy = FALSE;
|
||
|
||
|
||
//
|
||
// Free any outstanding IO - all future requests will be
|
||
// denied
|
||
//
|
||
|
||
if (WaveInfo->Direction) {
|
||
SoundResetOutput(&WaveInfo->BufferQueue);
|
||
} else {
|
||
|
||
//
|
||
// We ensure in SoundFillInputBuffers that there are no
|
||
// part-filled buffers for input
|
||
//
|
||
|
||
SoundFreeQ(&WaveInfo->BufferQueue.QueueHead, STATUS_CANCELLED);
|
||
|
||
((PLOCAL_DEVICE_INFO)
|
||
WaveInfo->DeviceObject->DeviceExtension)->State = WAVE_DD_STOPPED;
|
||
}
|
||
|
||
SoundQueueWaveComplete(WaveInfo);
|
||
|
||
} else {
|
||
//
|
||
// Start our timer off again
|
||
//
|
||
|
||
WaveInfo->GotWaveDpc = FALSE;
|
||
|
||
KeInitializeDpc(&WaveInfo->TimerDpc,
|
||
SoundTestDeviceDeferred,
|
||
(PVOID)WaveInfo);
|
||
|
||
//
|
||
// Wait for 3 seconds (in 100 ns units)
|
||
//
|
||
|
||
KeSetTimer(&WaveInfo->DeviceCheckTimer,
|
||
RtlConvertLongToLargeInteger(-30000000),
|
||
&WaveInfo->TimerDpc);
|
||
}
|
||
} else {
|
||
|
||
//
|
||
// Timers dropped off end
|
||
//
|
||
|
||
WaveInfo->TimerActive = FALSE;
|
||
}
|
||
|
||
KeSetEvent(&WaveInfo->TimerDpcEvent, 0, FALSE);
|
||
|
||
DMALeave(WaveInfo);
|
||
}
|
||
|
||
|
||
VOID
|
||
SoundSynchTimer(
|
||
IN PWAVE_INFO WaveInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Kill any timer running 'device alive' timer
|
||
|
||
Arguments:
|
||
|
||
WaveInfo - Wave parameters and state
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
|
||
BOOLEAN Wait;
|
||
|
||
Wait = FALSE;
|
||
|
||
DMAEnter(WaveInfo);
|
||
|
||
//
|
||
// 2 cases
|
||
//
|
||
// 1. TimerActive = FALSE - no timer is set, no Dpc routine is
|
||
// queued or about to be queued
|
||
//
|
||
// 2. TimerActive = TRUE ...
|
||
//
|
||
// NB TimerActive is synchronized via this spin lock
|
||
//
|
||
|
||
if (WaveInfo->TimerActive) {
|
||
|
||
//
|
||
// 2 cases :
|
||
//
|
||
// 1. Timer set - in this case killing the timer does it
|
||
//
|
||
// 2. Timer not set - dpc on queue or running but not
|
||
// yet entered device spin lock. Just reset the event
|
||
// so we can wait for it.
|
||
//
|
||
|
||
if (!KeCancelTimer(&WaveInfo->DeviceCheckTimer)) {
|
||
|
||
Wait = TRUE;
|
||
KeResetEvent(&WaveInfo->TimerDpcEvent);
|
||
}
|
||
|
||
WaveInfo->TimerActive = FALSE;
|
||
}
|
||
|
||
DMALeave(WaveInfo);
|
||
|
||
if (Wait) {
|
||
KeWaitForSingleObject(&WaveInfo->TimerDpcEvent,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
NULL);
|
||
}
|
||
|
||
ASSERTMSG("Timer synch failed", WaveInfo->TimerActive == FALSE);
|
||
}
|
||
|
||
|
||
VOID
|
||
SoundWorkerStopWave(
|
||
PVOID Context
|
||
)
|
||
/*++
|
||
|
||
Routine Description :
|
||
|
||
Stop the wave output. This is called from a worker thread and the
|
||
request is queued by the wave output Dpc.
|
||
|
||
Arguments :
|
||
|
||
Context - Wave Info for the device to be stopped
|
||
|
||
Return Value :
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
WaveInfo = Context;
|
||
|
||
SoundTerminateDMA(WaveInfo, FALSE); // Stop DMA
|
||
|
||
//
|
||
// Say we're done
|
||
//
|
||
KeSetEvent(&WaveInfo->WaveReallyComplete, 0 , FALSE);
|
||
}
|
||
|
||
|
||
VOID
|
||
SoundStartDMA(
|
||
IN PWAVE_INFO WaveInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Set up the DMA by calling IoAllocateAdapterChannel
|
||
|
||
Arguments:
|
||
|
||
WaveInfo - Wave parameters and state
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
KIRQL OldIrql;
|
||
NTSTATUS Status;
|
||
|
||
|
||
dprintf2(( "S" ));
|
||
|
||
//
|
||
// Check that DMA is not already running
|
||
//
|
||
|
||
//
|
||
// Set DMABusy early so that the Hardware routines etc know we're
|
||
// getting things going
|
||
//
|
||
|
||
WaveInfo->DMABusy = TRUE;
|
||
|
||
WaveInfo->Calibration = FALSE;
|
||
|
||
//
|
||
// The following is not necessary because we allocated non-cached
|
||
// memory for our common buffer
|
||
//
|
||
// KeFlushIoBuffers(pGDI->pDMABufferMDL,
|
||
// pGDI->Usage == SBInterruptUsageWaveIn,
|
||
// TRUE);
|
||
|
||
//
|
||
// Be prepared to wait
|
||
//
|
||
|
||
KeInitializeEvent(&WaveInfo->DmaSetupEvent,
|
||
SynchronizationEvent,
|
||
FALSE);
|
||
|
||
|
||
//
|
||
// Allocate an adapter channel. When the system allocates
|
||
// the channel, processing will continue in the SoundProgramDMA
|
||
// routine below.
|
||
//
|
||
|
||
dprintf3(("Allocating adapter channel"));
|
||
|
||
OldIrql = KeGetCurrentIrql();
|
||
if (OldIrql != DISPATCH_LEVEL) {
|
||
KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
|
||
}
|
||
Status =
|
||
IoAllocateAdapterChannel(WaveInfo->DMABuf.AdapterObject[0],
|
||
WaveInfo->DeviceObject,
|
||
BYTES_TO_PAGES(WaveInfo->DMABuf.BufferSize),
|
||
SoundProgramDMA,
|
||
(PVOID)WaveInfo);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
dprintf(("Failed to allocate adatper channel - code %X", Status));
|
||
}
|
||
|
||
if (OldIrql != DISPATCH_LEVEL) {
|
||
KeLowerIrql(OldIrql);
|
||
}
|
||
|
||
//
|
||
// Execution will continue in SoundProgramDMA when the
|
||
// adapter has been allocated
|
||
//
|
||
|
||
//
|
||
// Wait for it to complete
|
||
//
|
||
|
||
KeWaitForSingleObject(&WaveInfo->DmaSetupEvent,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
NULL);
|
||
//
|
||
// Set up our timer to check the device is alive occasionally
|
||
// (if we're not just restarting DMA)
|
||
|
||
if (OldIrql != DISPATCH_LEVEL) {
|
||
|
||
KeInitializeEvent(&WaveInfo->TimerDpcEvent,
|
||
SynchronizationEvent,
|
||
FALSE);
|
||
|
||
WaveInfo->TimerActive = TRUE;
|
||
WaveInfo->GotWaveDpc = TRUE; // Get through first time OK
|
||
SoundTestDeviceDeferred(NULL, (PVOID)WaveInfo, NULL, NULL);
|
||
|
||
}
|
||
|
||
//
|
||
// Program the DMA controller registers for the transfer
|
||
// Set the direction of transfer by whether we're wave in or
|
||
// wave out.
|
||
//
|
||
|
||
//SoundMapDMA(WaveInfo);
|
||
|
||
(*WaveInfo->HwSetupDMA)(WaveInfo);
|
||
|
||
SoundMapDMA(WaveInfo);
|
||
}
|
||
|
||
|
||
|
||
IO_ALLOCATION_ACTION
|
||
SoundProgramDMA(
|
||
IN PDEVICE_OBJECT pDO,
|
||
IN PIRP pIrp,
|
||
IN PVOID pMRB,
|
||
IN PVOID Context
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is executed when an adapter channel is allocated
|
||
for our DMA needs. It saves away the MRB (mapping register
|
||
base which has been extracted from the adapter object) and
|
||
sets the event which SoundStartDMA is waiting on.
|
||
|
||
Arguments:
|
||
|
||
pDO - Device object
|
||
pIrp - IO request packet
|
||
pMRB - Map register base of registers allocated by adapter
|
||
Context - Pointer to our device global data
|
||
|
||
|
||
Return Value:
|
||
|
||
Tell the system what to do with the adapter object
|
||
|
||
--*/
|
||
{
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
WaveInfo = (PWAVE_INFO) Context;
|
||
|
||
ASSERT(WaveInfo->Key == WAVE_INFO_KEY);
|
||
|
||
WaveInfo->MRB[0] = pMRB;// Remember our map register base
|
||
|
||
|
||
//
|
||
// Tell the caller it's time to go!
|
||
//
|
||
|
||
KeSetEvent(&WaveInfo->DmaSetupEvent, 0, FALSE);
|
||
|
||
//
|
||
// return a value that says we want to keep the channel
|
||
// and map registers.
|
||
//
|
||
|
||
return KeepObject;
|
||
}
|
||
|
||
|
||
VOID
|
||
SoundAppendList(
|
||
PLIST_ENTRY QueueHead,
|
||
PLIST_ENTRY ListToAppend
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Append a list of Irps to the head of a cancellable list
|
||
The list itself is emptied.
|
||
|
||
NOTE: Irps involved in these operations could be cancelled and
|
||
and freed from the cancellable list while this is going on.
|
||
This is OK.
|
||
|
||
Arguments:
|
||
|
||
QueueHead - The cancellable queue to be appended to
|
||
ListToAppend - The list to be appended
|
||
|
||
Return Value:
|
||
|
||
Note
|
||
|
||
--*/
|
||
{
|
||
while (!IsListEmpty(ListToAppend)) {
|
||
PLIST_ENTRY ListEntry;
|
||
PIRP Irp;
|
||
|
||
//
|
||
// Remove Irp from tail of queue and put it at
|
||
// head of input queue, making it cancellable
|
||
// at the same time
|
||
//
|
||
|
||
ListEntry = RemoveTailList(ListToAppend);
|
||
|
||
|
||
Irp = CONTAINING_RECORD(ListEntry, IRP, Tail.Overlay.ListEntry);
|
||
|
||
SoundAddIrpToCancellableQ(QueueHead,
|
||
Irp,
|
||
TRUE);
|
||
}
|
||
}
|
||
|
||
|
||
VOID
|
||
SoundFreeWaveOutputBuffers(
|
||
PLIST_ENTRY Queue,
|
||
ULONG BytesProcessed
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine frees entries from the queue until all the bytes are
|
||
used. If the last entry is not entirely used up then its
|
||
IoStatus.Information is bumped by the residual bytes.
|
||
|
||
|
||
Arguments:
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
--*/
|
||
{
|
||
while (BytesProcessed != 0) {
|
||
PIRP Irp;
|
||
ULONG BytesInEntry;
|
||
|
||
//
|
||
// We should NEVER exhaust the queue
|
||
//
|
||
|
||
ASSERT(!IsListEmpty(Queue));
|
||
|
||
Irp = CONTAINING_RECORD(Queue->Flink, IRP, Tail.Overlay.ListEntry);
|
||
|
||
//
|
||
// The Information field in the Irp records where we have completed
|
||
// to up until now
|
||
//
|
||
|
||
BytesInEntry =
|
||
IoGetCurrentIrpStackLocation(Irp)->Parameters.Write.Length -
|
||
Irp->IoStatus.Information;
|
||
|
||
if (BytesInEntry > BytesProcessed) {
|
||
Irp->IoStatus.Information += BytesProcessed;
|
||
BytesProcessed = 0;
|
||
} else {
|
||
//
|
||
// Free this entry
|
||
//
|
||
|
||
RemoveHeadList(Queue);
|
||
|
||
Irp->IoStatus.Information += BytesInEntry;
|
||
|
||
ASSERT(Irp->IoStatus.Information ==
|
||
IoGetCurrentIrpStackLocation(Irp)->Parameters.Write.Length);
|
||
|
||
Irp->IoStatus.Status = STATUS_SUCCESS;
|
||
|
||
IoCompleteRequest(Irp, IO_SOUND_INCREMENT);
|
||
|
||
BytesProcessed -= BytesInEntry;
|
||
}
|
||
}
|
||
}
|
||
|
||
VOID
|
||
SoundTerminateDMA(
|
||
IN PWAVE_INFO WaveInfo,
|
||
IN BOOLEAN Pause
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Stop the DMA at once by passing HALT to the DSP.
|
||
Free the adapter channel.
|
||
(Opposite of SoundStartDMA).
|
||
|
||
Arguments:
|
||
|
||
WaveInfo - pointer to wave parameters and data
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
ULONG BytesProcessed;
|
||
|
||
//
|
||
// Quiesce the hardware - do this before getting the buffer position
|
||
// so we get an accurate reading.
|
||
//
|
||
|
||
(*WaveInfo->HwStopDMA)(WaveInfo);
|
||
|
||
//
|
||
// Adjust our position before we stop the DMA
|
||
//
|
||
|
||
BytesProcessed = OffsetInBuffer(&WaveInfo->DoubleBuffer,
|
||
SoundGetBufferPosition(WaveInfo));
|
||
|
||
//
|
||
// For input work out how many more bytes we have for the user.
|
||
//
|
||
|
||
if (WaveInfo->Direction) {
|
||
|
||
// We could simplify things here by using the hardware pause
|
||
// in the device at the expense of introducing an extra state
|
||
//
|
||
|
||
if (Pause) {
|
||
//
|
||
// Complete any buffers which may now be complete
|
||
//
|
||
|
||
SoundFreeWaveOutputBuffers(
|
||
&WaveInfo->BufferQueue.ProgressQueue,
|
||
BytesProcessedInBuffer(&WaveInfo->DoubleBuffer,
|
||
BytesProcessed));
|
||
|
||
//
|
||
// Update the position
|
||
//
|
||
|
||
WaveInfo->BufferQueue.BytesProcessed +=
|
||
BytesProcessedInBuffer(&WaveInfo->DoubleBuffer,
|
||
BytesProcessed);
|
||
|
||
//
|
||
// Tidy up partially completed input buffer. We don't
|
||
// need to remember where we are here because we know
|
||
// at the other end where the DMA buffers start.
|
||
//
|
||
|
||
SoundCompleteIoBuffer(&WaveInfo->BufferQueue);
|
||
|
||
//
|
||
// Roll everything back into the input queue so we can
|
||
// support cancel of a paused state properly.
|
||
//
|
||
|
||
SoundAppendList(&WaveInfo->BufferQueue.QueueHead,
|
||
&WaveInfo->BufferQueue.ProgressQueue);
|
||
|
||
}
|
||
|
||
WaveInfo->DoubleBuffer.StartOfData = 0;
|
||
WaveInfo->DoubleBuffer.nBytes = 0;
|
||
|
||
//
|
||
// In either case we must be set up to restart DMA from the
|
||
// start of the lower half of the buffer next time
|
||
//
|
||
|
||
WaveInfo->DoubleBuffer.NextHalf = LowerHalf;
|
||
|
||
}
|
||
|
||
SoundFlushDMA(WaveInfo);
|
||
|
||
//
|
||
// Free the adapter channel
|
||
//
|
||
|
||
{
|
||
KIRQL OldIrql;
|
||
OldIrql = KeGetCurrentIrql();
|
||
if (OldIrql != DISPATCH_LEVEL) {
|
||
KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
|
||
}
|
||
IoFreeAdapterChannel(WaveInfo->DMABuf.AdapterObject[0]);
|
||
if (OldIrql != DISPATCH_LEVEL) {
|
||
KeLowerIrql(OldIrql);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
VOID
|
||
SoundStopDMA(
|
||
IN PWAVE_INFO WaveInfo,
|
||
IN BOOLEAN Pause
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Stop the DMA at once by passing HALT to the DSP.
|
||
Free the adapter channel.
|
||
(Opposite of SoundStartDMA).
|
||
|
||
Arguments:
|
||
|
||
WaveInfo - pointer to wave parameters and data
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
BOOLEAN DMABusy;
|
||
|
||
dprintf4(("SoundStopDMA(): start"));
|
||
|
||
//
|
||
// Acquire the spin lock so we can synchronize with the Dpc
|
||
// routine and correctly test DMABusy etc
|
||
//
|
||
|
||
DMAEnter(WaveInfo);
|
||
|
||
DMABusy = WaveInfo->DMABusy;
|
||
|
||
if (DMABusy) {
|
||
|
||
//
|
||
// Note our new state. We do this inside the spin lock
|
||
// so that if any Dpc routine runs now it knows that it
|
||
// should act as a NOOP.
|
||
//
|
||
// The ISR will not do anything once DMABusy is turned off,
|
||
// nor will the Dpc routine.
|
||
//
|
||
|
||
WaveInfo->DMABusy = FALSE;
|
||
WaveInfo->Overrun = FALSE;
|
||
|
||
//
|
||
// At this stage we know :
|
||
// 1. We won't get (or act on) any more interrupts
|
||
// 2. Nobody can start any more DMA because we hold the
|
||
// common wave input and output mutants
|
||
// 3. If a Dpc routine runs it will NOOP because DMABusy is FALSE
|
||
//
|
||
|
||
}
|
||
|
||
//
|
||
// Have a stab at stopping our timer
|
||
//
|
||
|
||
if (KeCancelTimer(&WaveInfo->DeviceCheckTimer)) {
|
||
|
||
//
|
||
// Update the state - otherwise SoundSynchTimer will
|
||
// hang waiting for non-existent timers
|
||
//
|
||
|
||
WaveInfo->TimerActive = FALSE;
|
||
}
|
||
|
||
|
||
DMALeave(WaveInfo);
|
||
|
||
|
||
if (DMABusy) {
|
||
|
||
SoundTerminateDMA(WaveInfo, Pause);
|
||
}
|
||
|
||
//
|
||
// Make sure no more Dpc routines are about to run!
|
||
//
|
||
|
||
KeResetEvent(&WaveInfo->DpcEvent);
|
||
|
||
//
|
||
// The Dpc routine clears DpcQueued just before setting this event
|
||
// so if it's set then the event will certainly be set.
|
||
//
|
||
// Note that there's no need to syncrhorinze with the ISR here
|
||
// because we NOOPed interrupts when we cleared DMABusy above.
|
||
//
|
||
|
||
if (WaveInfo->DpcQueued) {
|
||
|
||
dprintf2(("Waiting for Dpc routine to finish"));
|
||
KeWaitForSingleObject(&WaveInfo->DpcEvent,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE, // Not alertable
|
||
NULL);
|
||
}
|
||
}
|
||
|
||
VOID
|
||
SoundFillWithSilence(
|
||
PSOUND_DOUBLE_BUFFER DoubleBuffer,
|
||
ULONG Length
|
||
)
|
||
/*++
|
||
|
||
Routine Description
|
||
|
||
Fill the DMA buffer from the current fill mark up to BufferPosition with
|
||
silence.
|
||
|
||
--*/
|
||
{
|
||
ULONG StartOffset;
|
||
|
||
StartOffset = PositionInBuffer(DoubleBuffer, DoubleBuffer->nBytes);
|
||
|
||
if (Length + StartOffset > DoubleBuffer->BufferSize) {
|
||
RtlFillMemory(DoubleBuffer->Buf + StartOffset,
|
||
DoubleBuffer->BufferSize - StartOffset,
|
||
DoubleBuffer->Pad);
|
||
RtlFillMemory(DoubleBuffer->Buf,
|
||
Length - (DoubleBuffer->BufferSize - StartOffset),
|
||
DoubleBuffer->Pad);
|
||
} else {
|
||
RtlFillMemory(DoubleBuffer->Buf + StartOffset,
|
||
Length,
|
||
DoubleBuffer->Pad);
|
||
}
|
||
}
|
||
|
||
VOID
|
||
SoundQueueWaveComplete(
|
||
PWAVE_INFO WaveInfo
|
||
)
|
||
{
|
||
//
|
||
// Spin lock MUST be held for this
|
||
//
|
||
|
||
ASSERT(WaveInfo->LockHeld);
|
||
KeResetEvent(&WaveInfo->WaveReallyComplete);
|
||
ExQueueWorkItem(&WaveInfo->WaveStopWorkItem, CriticalWorkQueue);
|
||
}
|
||
|
||
|
||
/***************************************************************************
|
||
*
|
||
* Dpc routine and support code
|
||
*
|
||
***************************************************************************/
|
||
|
||
VOID
|
||
SoundOutDeferred(
|
||
PWAVE_INFO WaveInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Deferred procedure call routine for wave output interrupts.
|
||
|
||
The basic job is just to move to the next buffer which consists of
|
||
|
||
-- Completing Irps that made up the buffer just played (in ProgressQueue)
|
||
|
||
-- Filling up the next buffer
|
||
|
||
However, if the buffer which is about to play is empty we can deduce
|
||
that there isn't anything to play - either output was stopped by a
|
||
WAVE_DD_STOP or no buffers have arrived (otherwise data would
|
||
have already been moved into the buffer which is about to play).
|
||
|
||
In this case we
|
||
|
||
-- Stop the DMA
|
||
|
||
-- Complete any pause packet
|
||
|
||
-- Set our new state (WAVE_DD_IDLE if not currently WAVE_DD_STOPPED).
|
||
|
||
Arguments:
|
||
|
||
WaveInfo - Data associated with wave input/output
|
||
|
||
Return Value:
|
||
|
||
TRUE if DMA is to be halted
|
||
|
||
--*/
|
||
{
|
||
ULONG BytesProcessed;
|
||
ULONG BufferPosition;
|
||
ULONG BytesReallyProcessed;
|
||
ULONG DmaPosition;
|
||
|
||
|
||
ASSERTMSG("SoundOutDeferred called when DMA not busy!",
|
||
WaveInfo->DMABusy);
|
||
|
||
dprintf2(( "O" ));
|
||
|
||
BufferPosition = SoundGetBufferPosition(WaveInfo);
|
||
|
||
BytesProcessed = OffsetInBuffer(&WaveInfo->DoubleBuffer, BufferPosition);
|
||
|
||
//
|
||
// Work out how many of the application's bytes we've processed
|
||
//
|
||
BytesReallyProcessed = BytesProcessedInBuffer(&WaveInfo->DoubleBuffer,
|
||
BytesProcessed);
|
||
|
||
//
|
||
// Flush the DMA unless we're doing autoinit
|
||
//
|
||
|
||
WaveInfo->DoubleBuffer.nBytes -= BytesReallyProcessed;
|
||
if (BytesReallyProcessed < BytesProcessed) {
|
||
ASSERT(WaveInfo->DoubleBuffer.nBytes == 0);
|
||
}
|
||
WaveInfo->DoubleBuffer.StartOfData = BufferPosition;
|
||
|
||
//
|
||
// Kill everything on the dead queue
|
||
// move the transit queue to the dead queue
|
||
// and reinitialize the transit queue ready to receive
|
||
// data from the queue of new buffers
|
||
//
|
||
|
||
SoundFreeWaveOutputBuffers(&WaveInfo->BufferQueue.ProgressQueue,
|
||
BytesReallyProcessed);
|
||
|
||
//
|
||
// The block we've just done is now empty, ready for reuse
|
||
// Update the number of bytes processed, then free it
|
||
//
|
||
|
||
WaveInfo->BufferQueue.BytesProcessed += BytesReallyProcessed;
|
||
|
||
//
|
||
// That was the end of a normal block.
|
||
// Try to load the next half of the dma buffer.
|
||
// If this is the tail of the request, the load routine
|
||
// will pad it out with silence so we get a full block.
|
||
//
|
||
|
||
if (((PLOCAL_DEVICE_INFO)WaveInfo->DeviceObject->
|
||
DeviceExtension)->State != WAVE_DD_STOPPED) {
|
||
SoundLoadDMABuffer(
|
||
&WaveInfo->BufferQueue,
|
||
&WaveInfo->DoubleBuffer,
|
||
BufferPosition,
|
||
WaveInfo);
|
||
}
|
||
|
||
|
||
//
|
||
// See if we were doing the last block.
|
||
//
|
||
|
||
if (WaveInfo->DoubleBuffer.nBytes == 0) {
|
||
|
||
dprintf4(("end_last"));
|
||
|
||
//
|
||
// It's possible that a request was queued during the last block
|
||
// but that would have caused the LastBlock flag to have been
|
||
// cleared if there were any data, unless we're STOPPED.
|
||
// If a restart occurred after a stop the silence would
|
||
// have been filled out.
|
||
//
|
||
//
|
||
// However, with 0 length buffers possible the dead queue
|
||
// can be non-empty even though the next buffer has nothing
|
||
// to play in it.
|
||
//
|
||
|
||
//ASSERT(IsListEmpty(&WaveInfo->BufferQueue.QueueHead) ||
|
||
// ((PLOCAL_DEVICE_INFO)WaveInfo->DeviceObject->
|
||
// DeviceExtension)->State == WAVE_DD_STOPPED);
|
||
|
||
if (!IsListEmpty(&WaveInfo->BufferQueue.ProgressQueue)) {
|
||
|
||
//
|
||
// Note that it should not be possible for there to be
|
||
// a half complete buffer lying around. If we're paused
|
||
// that buffer would have been moved back to the
|
||
// input queue.
|
||
|
||
ASSERTMSG("Unexpected incomplete buffer",
|
||
WaveInfo->BufferQueue.UserBuffer == NULL);
|
||
|
||
dprintf1(("Empty buffers being freed at end of playing"));
|
||
SoundFreeQ(&WaveInfo->BufferQueue.ProgressQueue, STATUS_SUCCESS);
|
||
}
|
||
|
||
WaveInfo->DMABusy = FALSE;
|
||
|
||
#if 0 // SoundLoadDMABuffer will already have done this
|
||
//
|
||
// Make sure we play nothing but silence
|
||
//
|
||
|
||
SoundFillWithSilence(
|
||
&WaveInfo->DoubleBuffer,
|
||
OffsetInBuffer(&WaveInfo->DoubleBuffer, BufferPosition));
|
||
#endif
|
||
|
||
SoundQueueWaveComplete(WaveInfo);
|
||
|
||
|
||
} else {
|
||
|
||
WaveInfo->DoubleBuffer.NextHalf =
|
||
LowerHalf + UpperHalf - WaveInfo->DoubleBuffer.NextHalf;
|
||
|
||
if( WaveInfo->DoubleBuffer.NextHalf == UpperHalf ){
|
||
WaveInfo->MapOn = 1;
|
||
}
|
||
//
|
||
// If we're not doing auto-initialize re-start the DMA
|
||
//
|
||
|
||
return;
|
||
}
|
||
|
||
}
|
||
|
||
|
||
VOID
|
||
SoundInDeferred(
|
||
IN OUT PWAVE_INFO WaveInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Dpc routine for wave input device
|
||
|
||
Collect the data from the DMA buffer and pass it to the application's
|
||
buffer(s).
|
||
|
||
Arguments:
|
||
|
||
WaveInfo - wave data structure
|
||
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
|
||
ULONG DmaPosition; //
|
||
ULONG length; //
|
||
ULONG offset; //
|
||
|
||
//
|
||
// Fill in any buffers we can
|
||
//
|
||
dprintf2((WaveInfo->DoubleBuffer.NextHalf == LowerHalf ? "L" : "U"));
|
||
|
||
|
||
//
|
||
// Move on to next buffer
|
||
//
|
||
|
||
WaveInfo->DoubleBuffer.NextHalf =
|
||
UpperHalf + LowerHalf - WaveInfo->DoubleBuffer.NextHalf;
|
||
|
||
//
|
||
// If we're not doing auto-initialize re-start the DMA
|
||
//
|
||
|
||
SoundFlushDMA(WaveInfo); // Bugfix FW5516
|
||
|
||
if (WaveInfo->DMAType != SoundAutoInitDMA) {
|
||
// Bug, HwSetupDMA calls HwEnter that uses KeWait from DPC
|
||
// (*WaveInfo->HwSetupDMA)(WaveInfo);
|
||
|
||
SoundMapDMA(WaveInfo);
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Request input without posting the last buffer
|
||
//
|
||
|
||
SoundFillInputBuffers(WaveInfo, SoundGetBufferPosition(WaveInfo));
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
BOOLEAN
|
||
SoundSignalDpcEnd(
|
||
PVOID Context
|
||
)
|
||
{
|
||
PWAVE_INFO WaveInfo;
|
||
|
||
WaveInfo = (PWAVE_INFO)Context;
|
||
WaveInfo->DpcQueued = FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
SoundWaveDeferred(
|
||
PKDPC pDpc,
|
||
PDEVICE_OBJECT pDeviceObject,
|
||
PIRP pIrp,
|
||
PVOID Context
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Deferred procedure call routine for wave output interrupts.
|
||
|
||
The job is to call the appropriate wave input or output
|
||
Dpc routine under the spin lock if DMA is still busy.
|
||
|
||
The Dpc complete event is then signalled in case we're waiting
|
||
to synchronize on some thread.
|
||
|
||
Arguments:
|
||
|
||
pDPC - pointer to DPC object
|
||
pDeviceObject - pointer to our device object
|
||
pIrp - ???
|
||
Context - our Dpc context (NULL in our case).
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
PWAVE_INFO WaveInfo;
|
||
PLOCAL_DEVICE_INFO pLDI;
|
||
|
||
pLDI = (PLOCAL_DEVICE_INFO)pDeviceObject->DeviceExtension;
|
||
ASSERT(pLDI->Key == LDI_WAVE_IN_KEY ||
|
||
pLDI->Key == LDI_WAVE_OUT_KEY);
|
||
|
||
WaveInfo = (PWAVE_INFO)pLDI->DeviceSpecificData;
|
||
|
||
ASSERTMSG("Invalid Wave Info structure in SoundAutoInitDeferred",
|
||
WaveInfo->Key == WAVE_INFO_KEY);
|
||
|
||
dprintf5(("("));
|
||
|
||
//
|
||
// Acquire the spin lock before we mess with the list
|
||
//
|
||
|
||
DMAEnter(WaveInfo);
|
||
|
||
//
|
||
// Keep the timer check stuff happy
|
||
//
|
||
|
||
WaveInfo->GotWaveDpc = TRUE;
|
||
WaveInfo->FailureCount = 0;
|
||
|
||
//
|
||
// The Dpc routine only does something if Dma is active.
|
||
// This means that if the device is paused, reset etc
|
||
// this routine can be disabled by turning off DMABusy
|
||
//
|
||
|
||
if (WaveInfo->DMABusy) {
|
||
(WaveInfo->Direction ? SoundOutDeferred : SoundInDeferred)(WaveInfo);
|
||
}
|
||
|
||
//
|
||
// Release the spin lock
|
||
//
|
||
|
||
DMALeave(WaveInfo);
|
||
|
||
//
|
||
// Tell the world we've finished.
|
||
//
|
||
|
||
KeSynchronizeExecution(
|
||
WaveInfo->Interrupt,
|
||
SoundSignalDpcEnd,
|
||
(PVOID)WaveInfo);
|
||
|
||
//
|
||
// Tell SoundStopDMA that we're really finished
|
||
// (Note this MUST be done after calling SoundSignalDpcEnd)
|
||
//
|
||
|
||
KeSetEvent(&WaveInfo->DpcEvent, 0, FALSE);
|
||
|
||
dprintf5((")"));
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
/************************************************************************
|
||
*
|
||
* Routines to handle filling and emptying of the DMA buffer
|
||
*
|
||
************************************************************************/
|
||
|
||
|
||
VOID
|
||
SoundLoadDMABuffer(
|
||
PSOUND_BUFFER_QUEUE BufferQueue,
|
||
PSOUND_DOUBLE_BUFFER DoubleBuffer,
|
||
ULONG BufferPosition,
|
||
PWAVE_INFO WaveInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Fill the given DMA buffer with as much data as is available.
|
||
|
||
This is where the supply of bytes is chopped if we're in a
|
||
WAVE_DD_STOPPED state. The supply then dries up and the Dpc routine
|
||
stops the DMA (and posts the pause packet).
|
||
|
||
|
||
Arguments:
|
||
|
||
BufferQueue - our stream of application buffers
|
||
DoubleBuffer - where to put the data
|
||
BufferPosition - Where to fill up to
|
||
|
||
Return Value:
|
||
|
||
|
||
--*/
|
||
{
|
||
ULONG Space;
|
||
|
||
|
||
// dprintf1(( "L" ));
|
||
|
||
//
|
||
// This calculation yields a full buffer if the current position
|
||
// == StartOfData. This allows us to fill an empty buffer
|
||
//
|
||
|
||
if (BufferPosition <= DoubleBuffer->StartOfData) {
|
||
Space = DoubleBuffer->BufferSize + BufferPosition -
|
||
DoubleBuffer->StartOfData;
|
||
} else {
|
||
Space = BufferPosition - DoubleBuffer->StartOfData;
|
||
}
|
||
|
||
//
|
||
// Loop copying data to the output buffers. Typically the
|
||
// output buffer will be much bigger than the DMA buffer.
|
||
//
|
||
|
||
while (Space > DoubleBuffer->nBytes) {
|
||
|
||
ULONG BytesToCopy;
|
||
ULONG CopyTo;
|
||
|
||
//
|
||
// We might have completed the last buffer
|
||
// Note that we cope with 0 length buffers here
|
||
//
|
||
|
||
if (BufferQueue->UserBuffer == NULL) {
|
||
SoundGetNextBuffer(BufferQueue);
|
||
if (BufferQueue->UserBuffer == NULL) {
|
||
|
||
//
|
||
// There REALLY aren't any buffers
|
||
//
|
||
|
||
break;
|
||
} else {
|
||
InsertTailList(
|
||
&BufferQueue->ProgressQueue,
|
||
&BufferQueue->pIrp->Tail.Overlay.ListEntry);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Find out how much space we have left in the
|
||
// client's buffers
|
||
//
|
||
// Note that BytesToCopy may be 0 - this is OK
|
||
//
|
||
|
||
BytesToCopy =
|
||
min(BufferQueue->UserBufferSize - BufferQueue->UserBufferPosition,
|
||
Space - DoubleBuffer->nBytes);
|
||
|
||
CopyTo = PositionInBuffer(DoubleBuffer, DoubleBuffer->nBytes);
|
||
|
||
//
|
||
// Copy the data - may need more than one copy
|
||
//
|
||
|
||
if (CopyTo + BytesToCopy > DoubleBuffer->BufferSize) {
|
||
BytesToCopy = DoubleBuffer->BufferSize - CopyTo;
|
||
|
||
}
|
||
ASSERT(BytesToCopy > 0 ||
|
||
BufferQueue->UserBufferSize == BufferQueue->UserBufferPosition);
|
||
RtlCopyMemory(DoubleBuffer->Buf + CopyTo,
|
||
BufferQueue->UserBuffer + BufferQueue->UserBufferPosition,
|
||
BytesToCopy);
|
||
|
||
//
|
||
// Update counters etc.
|
||
//
|
||
|
||
BufferQueue->UserBufferPosition += BytesToCopy;
|
||
ASSERT(Space - DoubleBuffer->nBytes >= BytesToCopy);
|
||
DoubleBuffer->nBytes += BytesToCopy;
|
||
|
||
//
|
||
// BufferQueue->BytesProcessed will be updated by the
|
||
// Dpc routine
|
||
//
|
||
|
||
//
|
||
// See if we've now filled a buffer
|
||
//
|
||
|
||
if (BufferQueue->UserBufferPosition == BufferQueue->UserBufferSize) {
|
||
|
||
dprintf4((" finished"));
|
||
|
||
//
|
||
// Complete the buffer
|
||
//
|
||
|
||
SoundCompleteIoBuffer(BufferQueue);
|
||
}
|
||
|
||
} // Continue around the loop until the request is satisfied
|
||
|
||
//
|
||
// if we transferred something, pad out the request with
|
||
// silence
|
||
//
|
||
// BUGBUG do the exponential decay thing here
|
||
|
||
if (Space > DoubleBuffer->nBytes) {
|
||
SoundFillWithSilence(DoubleBuffer, Space - DoubleBuffer->nBytes);
|
||
}
|
||
|
||
//
|
||
// flush the i/o buffers
|
||
//
|
||
// BUGBUG is this right ? Actually I386 needs none of this
|
||
// KeFlushIoBuffers(BufferQueue->pDMABufferMDL, FALSE); // flush for write
|
||
|
||
}
|
||
|
||
|
||
|
||
BOOLEAN
|
||
SoundFillInputBuffers(
|
||
PWAVE_INFO WaveInfo,
|
||
ULONG BufferPosition
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Send input to client
|
||
|
||
Take the data from the last recorded position in the DMA
|
||
buffer. The length of the data is passed in. Try to
|
||
insert it into the caller's buffers. Note that the client gets
|
||
no notification if the data is truncated.
|
||
|
||
Arguments:
|
||
|
||
WaveInfo - current wave (input) state
|
||
|
||
Return Value:
|
||
|
||
TRUE if we completed at least one of the user's Irps
|
||
|
||
--*/
|
||
|
||
{
|
||
BOOLEAN BufferCompleted;
|
||
PSOUND_DOUBLE_BUFFER DoubleBuffer;
|
||
PSOUND_BUFFER_QUEUE BufferQueue;
|
||
ULONG BytesAvailable;
|
||
|
||
BufferQueue = &WaveInfo->BufferQueue;
|
||
DoubleBuffer = &WaveInfo->DoubleBuffer;
|
||
|
||
BufferCompleted = FALSE;
|
||
|
||
BytesAvailable = OffsetInBuffer(DoubleBuffer, BufferPosition);
|
||
|
||
dprintf4(("Buffer Position = 0x%x", BufferPosition));
|
||
dprintf4(("BytesAvailable = 0x%x", BytesAvailable));
|
||
|
||
//
|
||
// While there is data and somewhere to put it
|
||
//
|
||
|
||
while (BytesAvailable > 0) {
|
||
|
||
ULONG BytesToCopy;
|
||
|
||
//
|
||
// We might have completed the last buffer
|
||
// Note that we cope with 0 length buffers here
|
||
//
|
||
|
||
if (BufferQueue->UserBuffer == NULL) {
|
||
SoundGetNextBuffer(BufferQueue);
|
||
|
||
|
||
if (BufferQueue->UserBuffer == NULL) {
|
||
|
||
//
|
||
// There REALLY aren't any buffers
|
||
//
|
||
|
||
break;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Find out how much space we have left in the
|
||
// client's buffers
|
||
// Note that BytesToCopy may be 0 - this is OK
|
||
//
|
||
|
||
BytesToCopy =
|
||
min(BufferQueue->UserBufferSize - BufferQueue->pIrp->IoStatus.Information,
|
||
BytesAvailable);
|
||
|
||
if (DoubleBuffer->StartOfData + BytesToCopy > DoubleBuffer->BufferSize) {
|
||
BytesToCopy = DoubleBuffer->BufferSize - DoubleBuffer->StartOfData;
|
||
}
|
||
//
|
||
// Copy the data
|
||
//
|
||
|
||
RtlCopyMemory(BufferQueue->UserBuffer +
|
||
BufferQueue->pIrp->IoStatus.Information,
|
||
DoubleBuffer->Buf + DoubleBuffer->StartOfData,
|
||
BytesToCopy);
|
||
|
||
//
|
||
// Update counters etc.
|
||
//
|
||
|
||
BufferQueue->pIrp->IoStatus.Information += BytesToCopy;
|
||
BufferQueue->BytesProcessed += BytesToCopy;
|
||
DoubleBuffer->StartOfData = PositionInBuffer(DoubleBuffer, BytesToCopy);
|
||
|
||
ASSERT(BytesToCopy <= BytesAvailable);
|
||
BytesAvailable -= BytesToCopy;
|
||
|
||
//
|
||
// See if we've now filled a buffer
|
||
//
|
||
|
||
if (BufferQueue->pIrp->IoStatus.Information == BufferQueue->UserBufferSize) {
|
||
|
||
SoundCompleteIoBuffer(BufferQueue);
|
||
|
||
//
|
||
// Mark request as complete
|
||
//
|
||
|
||
BufferQueue->pIrp->IoStatus.Status = STATUS_SUCCESS;
|
||
IoCompleteRequest(BufferQueue->pIrp, IO_SOUND_INCREMENT);
|
||
|
||
BufferCompleted = TRUE;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If there is a buffer part filled put it back on the queue so that
|
||
// it can be cancelled if necessary
|
||
//
|
||
|
||
if (BufferQueue->UserBuffer != NULL) {
|
||
SoundAddIrpToCancellableQ(&BufferQueue->QueueHead,
|
||
BufferQueue->pIrp,
|
||
TRUE);
|
||
|
||
BufferQueue->UserBuffer = NULL;
|
||
}
|
||
|
||
return BufferCompleted;
|
||
}
|
||
|
||
/************************************************************************
|
||
*
|
||
* Routines to handle queues of wave buffers
|
||
*
|
||
************************************************************************/
|
||
|
||
|
||
|
||
VOID
|
||
SoundGetNextBuffer(
|
||
PSOUND_BUFFER_QUEUE BufferQueue
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Get the next user's buffer :
|
||
|
||
If there is another buffer :
|
||
|
||
Remove the first buffer from the head of the list
|
||
Discard it if it's cancelled and go to the next
|
||
Map the locked user pages so we can refer to them
|
||
Update the BufferQueue fields :
|
||
UserBuffer - our pointer to buffer
|
||
UserBufferPosition - 0
|
||
pIrp - The request packet for the current buffer
|
||
|
||
|
||
Arguments:
|
||
|
||
BufferQueue - pointer to our local queue info
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
PIO_STACK_LOCATION pIrpStack;
|
||
|
||
dprintf5(("New Packet"));
|
||
|
||
ASSERT(BufferQueue->UserBuffer == NULL);
|
||
|
||
//
|
||
// May be no more buffers. If there are they may be cancelled
|
||
// IO requests.
|
||
//
|
||
|
||
for (;;) {
|
||
|
||
//
|
||
// pull the next request packet from the front of the list
|
||
// This call makes the Irp non cancellable.
|
||
//
|
||
|
||
|
||
BufferQueue->pIrp =
|
||
SoundRemoveFromCancellableQ(&BufferQueue->QueueHead);
|
||
|
||
if (BufferQueue->pIrp == NULL) {
|
||
break;
|
||
}
|
||
|
||
pIrpStack = IoGetCurrentIrpStackLocation(BufferQueue->pIrp);
|
||
|
||
//
|
||
// Get the length of the wave bits
|
||
//
|
||
|
||
BufferQueue->UserBufferSize =
|
||
pIrpStack->MajorFunction == IRP_MJ_WRITE ?
|
||
pIrpStack->Parameters.Write.Length :
|
||
pIrpStack->Parameters.Read.Length;
|
||
|
||
//
|
||
// Map the buffer pages to kernel mode
|
||
// Keep the address of the start so we can unmap them later
|
||
// Note the system falls over mapping 0 length buffers !
|
||
//
|
||
|
||
if (BufferQueue->UserBufferSize != 0) {
|
||
BufferQueue->UserBuffer =
|
||
(PUCHAR) MmGetSystemAddressForMdl(BufferQueue->pIrp->MdlAddress);
|
||
} else {
|
||
BufferQueue->UserBuffer = (PUCHAR)BufferQueue; // Dummy
|
||
}
|
||
|
||
//
|
||
// We now have a buffer - set the position from the
|
||
// information field.
|
||
//
|
||
|
||
BufferQueue->UserBufferPosition =
|
||
BufferQueue->pIrp->IoStatus.Information;
|
||
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
VOID __inline
|
||
SoundCompleteIoBuffer(
|
||
PSOUND_BUFFER_QUEUE BufferQueue
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Complete the processing of a wave buffer
|
||
|
||
This involves
|
||
|
||
-- Setting the length of data processed in the Irp and
|
||
|
||
-- Clearing the UserBuffer field.
|
||
|
||
Arguments:
|
||
|
||
BufferQueue - pointer to our queue data
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
//
|
||
// Note that there is currently no mapped buffer to use
|
||
//
|
||
|
||
BufferQueue->UserBuffer = NULL;
|
||
}
|
||
|
||
|
||
VOID
|
||
SoundInitializeWaveInfo(
|
||
PWAVE_INFO WaveInfo,
|
||
UCHAR DMAType,
|
||
PSOUND_QUERY_FORMAT_ROUTINE QueryFormat,
|
||
PVOID HwContext
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Initialize the WAVE_INFO structure
|
||
|
||
Arguments:
|
||
|
||
WaveInfo - The one to initialize
|
||
DMAType - type of DMA to do (autoinit, 2 channel etc)
|
||
QueryFormat - callback to see if format is supported
|
||
Context - hardware specific context stored in WAVE_INFO structure
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
WaveInfo->Key = WAVE_INFO_KEY;
|
||
WaveInfo->DMAType = DMAType;
|
||
WaveInfo->QueryFormat = QueryFormat;
|
||
WaveInfo->HwContext = HwContext;
|
||
SoundInitializeBufferQ(&WaveInfo->BufferQueue);
|
||
|
||
KeInitializeSpinLock(&WaveInfo->DeviceSpinLock);
|
||
KeInitializeTimer(&WaveInfo->DeviceCheckTimer);
|
||
|
||
//
|
||
// The event is used for waiting for the Dpc routine to complete -
|
||
// it is not reset on completion - hence use of NotificationEvent type.
|
||
//
|
||
|
||
KeInitializeEvent(&WaveInfo->DpcEvent,
|
||
NotificationEvent,
|
||
TRUE);
|
||
|
||
//
|
||
// Set up stop wave output from Dpc stuff
|
||
// Event is set to TRUE as we test if before starting any DMA
|
||
//
|
||
KeInitializeEvent(&WaveInfo->WaveReallyComplete,
|
||
NotificationEvent,
|
||
TRUE);
|
||
|
||
ExInitializeWorkItem(&WaveInfo->WaveStopWorkItem,
|
||
SoundWorkerStopWave,
|
||
WaveInfo);
|
||
|
||
}
|
||
|
||
VOID
|
||
SoundInitializeBufferQ(
|
||
PSOUND_BUFFER_QUEUE BufferQueue
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Initialize the BufferQ structure
|
||
|
||
Arguments:
|
||
|
||
BufferQueue - The one to initialize
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// Set up the lists
|
||
//
|
||
|
||
InitializeListHead(&BufferQueue->QueueHead);
|
||
InitializeListHead(&BufferQueue->ProgressQueue);
|
||
}
|
||
|
||
|
||
VOID
|
||
SoundInitializeDoubleBuffer(
|
||
IN OUT PWAVE_INFO WaveInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Initialize the Double buffer structure
|
||
|
||
Arguments:
|
||
|
||
WaveInfo - pointer to containing structure
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
ULONG BufferSize;
|
||
|
||
ASSERTMSG("Setting buffer size while DMA running!", !WaveInfo->DMABusy);
|
||
|
||
//
|
||
// go for 8 interrupts per second, and round down to the nearest
|
||
// 8 bytes
|
||
//
|
||
|
||
BufferSize = SoundGetDMABufferSize( WaveInfo );
|
||
|
||
if (WaveInfo->WaveFormat != NULL) {
|
||
switch (WaveInfo->WaveFormat->wFormatTag) {
|
||
case WAVE_FORMAT_ALAW:
|
||
WaveInfo->DoubleBuffer.Pad = 0xD5;
|
||
break;
|
||
|
||
case WAVE_FORMAT_MULAW:
|
||
WaveInfo->DoubleBuffer.Pad = 0x7F;
|
||
break;
|
||
|
||
default:
|
||
WaveInfo->DoubleBuffer.Pad = 0;
|
||
}
|
||
} else {
|
||
WaveInfo->DoubleBuffer.Pad = WaveInfo->BitsPerSample == 8 ?
|
||
0x80 : 0;
|
||
}
|
||
WaveInfo->DoubleBuffer.BufferSize = BufferSize;
|
||
WaveInfo->DoubleBuffer.NextHalf = LowerHalf;
|
||
WaveInfo->DoubleBuffer.nBytes = 0;
|
||
WaveInfo->DoubleBuffer.StartOfData = 0;
|
||
WaveInfo->DoubleBuffer.Buf = (PUCHAR)
|
||
WaveInfo->DMABuf.VirtualAddress;
|
||
}
|
||
|
||
|
||
int
|
||
SoundTestWaveDevice(
|
||
IN PDEVICE_OBJECT pDO
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Fire up the wave device for a short transfer and return whether it's
|
||
working (ie interrupts) or not.
|
||
|
||
This routine should not be called except from the DriverEntry routine.
|
||
|
||
NOTE - ONLY use this as a last resort - if there's no configuration
|
||
information.
|
||
|
||
ALSO - before unloading your driver if it fails make sure any termination
|
||
of the transfer is complete (eg ExWorker routines etc).
|
||
|
||
WARNING - this routine currently only works for auto-init devices - the
|
||
test on the DMA position at the end should be corrected for other
|
||
forms of DMA.
|
||
|
||
Arguments:
|
||
|
||
pDO - device object of wave device
|
||
|
||
Return Value:
|
||
|
||
0 if success
|
||
1 if bad interrupt
|
||
2 if bad DMA
|
||
|
||
--*/
|
||
{
|
||
PWAVE_INFO WaveInfo;
|
||
PLOCAL_DEVICE_INFO pLDI;
|
||
ULONG DmaBytesLeft;
|
||
|
||
pLDI = (PLOCAL_DEVICE_INFO)pDO->DeviceExtension;
|
||
|
||
WaveInfo = pLDI->DeviceSpecificData;
|
||
|
||
//
|
||
// To test the wave device we just start a 0 length transfer. The
|
||
// way the code is written this will actually cause a half DMA buffer
|
||
// to be sent so we should get an interrupt in less than 1/16 of a second.
|
||
// We arrange to wait for 1/8 of a second and see if we got one
|
||
//
|
||
|
||
if (!(*pLDI->DeviceInit->ExclusionRoutine)(
|
||
pLDI, SoundExcludeOpen)) {
|
||
return FALSE; // Something funny going on here
|
||
}
|
||
|
||
SoundWaveCreate(pLDI, pDO);
|
||
|
||
if (!WaveInfo->Direction) {
|
||
SoundSetWaveInputState(pLDI, WAVE_DD_RECORDING, NULL);
|
||
}
|
||
|
||
SoundStartWaveDevice(pLDI, NULL);
|
||
|
||
SoundDelay(75); // 75 ms > 1/16 second which is DMA latency
|
||
|
||
SoundWaveCleanup(pLDI, NULL);
|
||
|
||
//
|
||
// Should be around half a buffer left to go - ie we're in the second
|
||
// half buffer
|
||
//
|
||
DmaBytesLeft = HalReadDmaCounter(WaveInfo->DMABuf.AdapterObject[0]);
|
||
|
||
if (!WaveInfo->GotWaveDpc) {
|
||
return 1; // Bad interrupt
|
||
}
|
||
if (DmaBytesLeft > 0 &&
|
||
DmaBytesLeft < WaveInfo->DoubleBuffer.BufferSize - 4) {
|
||
|
||
return 0; // OK
|
||
} else {
|
||
return 2; // Bad DMA
|
||
}
|
||
}
|
||
|
||
/****************************************************************************
|
||
|
||
Routine Description:
|
||
|
||
Get the DMA Buffer size based on the Sample Rate
|
||
|
||
Arguments:
|
||
|
||
WaveInfo - pointer to containing structure
|
||
|
||
Return Value:
|
||
|
||
ULONG DmaBufferSize
|
||
|
||
****************************************************************************/
|
||
ULONG SoundGetDMABufferSize( IN OUT PWAVE_INFO WaveInfo )
|
||
{
|
||
/***** Local Variables *****/
|
||
|
||
ULONG BytesPerSecond;
|
||
ULONG BufferSize;
|
||
|
||
/***** Start *****/
|
||
|
||
#if 0
|
||
if (WaveInfo->WaveFormat) {
|
||
BytesPerSecond = WaveInfo->WaveFormat->nAvgBytesPerSec;
|
||
} else
|
||
#endif
|
||
{
|
||
BytesPerSecond = (WaveInfo->Channels *
|
||
WaveInfo->SamplesPerSec *
|
||
WaveInfo->BitsPerSample) >> 3;
|
||
}
|
||
|
||
//
|
||
// go for 16 interrupts per second, and round down to the nearest
|
||
// 8 bytes
|
||
//
|
||
|
||
BufferSize = min((BytesPerSecond >> 3) & ~7,
|
||
WaveInfo->DMABuf.BufferSize);
|
||
|
||
|
||
dprintf2(("SoundGetDMABufferSize(): DMA Buffer Size calculated = %XH", BufferSize));
|
||
|
||
return( BufferSize );
|
||
|
||
} // End SoundGetDMABufferSize()
|
||
|
||
|
||
/************************************ END ***********************************/
|
||
|