NT4/private/ntos/dd/sound/necsnd/wave.c
2020-09-30 17:12:29 +02:00

4419 lines
105 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
"@(#) 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 ***********************************/