2020-09-30 17:12:29 +02:00

831 lines
20 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.

/*++
Copyright (c) 1991 Microsoft Corporation
Module Name:
play.c
Abstract:
This module contains code for ioctl play request functions.
Author:
Nigel Thompson (nigelt) 25-Apr-1991
Environment:
Kernel mode
Revision History:
Rewritten by Robin Speed (RobinSp) 10-Dec-1991 - 29-Jan-1992
Sameer Dekate (sameer@mips.com) 19-Aug-1992
- Changes to support the MIPS sound board
--*/
#include "sound.h"
NTSTATUS
sndWavePlay(
IN OUT PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION pIrpStack
)
/*++
Routine Description:
The user has passed in a buffer of wave data to play.
If this is not a wave output device return with STATUS_NOT_SUPPORTED
This buffer is added to the list of buffers to be played and
sndStartOutput is called to start playing data if the device is not
already playing or if the output has not been (temporarily)
stopped by the application.
Arguments:
pLDI - Local device info
pIrp - The IO request packet
pIrpStack - The current stack location
Return Value:
Irp status
--*/
{
NTSTATUS Status;
dprintf5("In sndIoctlWavePlay pLDI= 0x%x pIrp = 0x%x pIrpStack = 0x%x", pLDI, pIrp, pIrpStack);
// DbgBreakPoint();
//
// confirm we are doing this on the output device!
//
if (pLDI->DeviceType != WAVE_OUT) {
dprintf1("Attempt to play on input device");
return STATUS_NOT_SUPPORTED;
}
//
// Initialize data length.
//
pIrp->IoStatus.Information = 0;
//
// Mark the Irp pending before starting processing
//
IoMarkIrpPending(pIrp);
pIrp->IoStatus.Status = STATUS_PENDING;
//
// Inform debuggers that 0 length buffers are rather strange
//
if (pIrpStack->Parameters.Write.Length == 0) {
dprintf1("Wave planday buffer is zero length");
}
//
// Put the request in the queue and start the transfer if possible.
//
//
// Acquire the spin lock
//
GlobalEnter(pLDI->pGlobalInfo);
//
//
InsertTailList(&pLDI->QueueHead, &pIrp->Tail.Overlay.ListEntry);
dprintf5("irp added");
//
// test if dma is running. If not then start a new transfer.
// Otherwise try to fill out any silence in the next buffer to
// go before it starts.
//
sndStartOutput(pLDI);
//
// Ok to release the spin lock now
//
GlobalLeave(pLDI->pGlobalInfo);
//
// Mark this request as pending completion.
// The dispatch routine handles the rest of it
//
Status = STATUS_PENDING;
return Status;
}
VOID
sndStartOutput(
PLOCAL_DEVICE_INFO pLDI
)
/*++
Routine Description:
This routine is called whenever an event occurs which could
output more wave data :
A new buffer is supplied
The state is changed from stopped to idle
If wave data is already playing it may still be possible to move
some data into the half buffer which is not playing.
If no data is playing then both dma half buffers are primed if
possible. If no data exists to be played this function is
a NOOP except that some 0 length buffers may be completed.
Arguments:
Return Value:
--*/
{
PGLOBAL_DEVICE_INFO pGDI;
pGDI = pLDI->pGlobalInfo;
//
// Try to fill up both buffers
//
//
// If DMA is running we may be able to pad out some silence to
// stop DMA from dying later. If not just initiate everything
//
if (!pGDI->DMABusy) {
ASSERT(pGDI->DMABuffer[0].nBytes == 0 &&
pGDI->DMABuffer[1].nBytes == 0);
//
// Compute the size of the buffer. We want interrupts at least once
// per 1/8 of a second to match AVI's assumptions. Round to 4 bytes.
//
pGDI->DmaHalfBufferSize =
((pGDI->SamplesPerSec * pGDI->BytesPerSample) / 8) & ~3;
if (pGDI->DmaHalfBufferSize > DMA_BUFFER_SIZE / 2) {
pGDI->DmaHalfBufferSize = DMA_BUFFER_SIZE / 2;
}
//
// A paused loop could leave stuff on the transit queue
// waiting to be looped round again
//
#ifdef WAVE_DD_DO_LOOPS
ASSERT(IsListEmpty(&pLDI->TransitQueue) || pLDI->LoopBegin);
#else
ASSERT(IsListEmpty(&pLDI->TransitQueue));
#endif // WAVE_DD_DO_LOOPS
ASSERT(IsListEmpty(&pLDI->DeadQueue));
pGDI->SoundHardware.TcInterruptsPending = 0;
pGDI->GotUnderFlow = 0;
pGDI->NextHalf = LowerHalf; // This causes the code which follows
// to do the right thing
}
//
// First try to stoke up the buffer currently being output (or about
// to be started if DMA is not currently running).
//
if (pGDI->DMABuffer[pGDI->NextHalf].nBytes <
pGDI->DmaHalfBufferSize) {
//
// Try to stoke up our buffer. We may also succeed in
// putting data in an empty buffer
//
sndLoadDMABuffer(pLDI,
&pGDI->DMABuffer[pGDI->NextHalf]);
//
// The buffers processed are now about to
// be played so move them to the dead queue.
//
while (!IsListEmpty(&pLDI->TransitQueue)) {
PLIST_ENTRY pListNode;
pListNode = RemoveHeadList(&pLDI->TransitQueue);
#ifdef WAVE_DD_DO_LOOPS
//
// Do not move loops over to the dead queue - there's
// still life in them !
//
if (pListNode == pLDI->LoopBegin) {
InsertHeadList(&pLDI->TransitQueue, pListNode);
break;
}
#endif // WAVE_DD_DO_LOOPS
//
// Move to death row
//
InsertTailList(&pLDI->DeadQueue, pListNode);
}
}
//
// Try to fill the second buffer
//
if (pGDI->DMABuffer[UpperHalf + LowerHalf - pGDI->NextHalf].nBytes <
pGDI->DmaHalfBufferSize) {
sndLoadDMABuffer(pLDI,
&pGDI->DMABuffer[UpperHalf + LowerHalf -
pGDI->NextHalf]);
}
//
// See if DMA needs starting
//
if (!pGDI->DMABusy) {
if (pGDI->DMABuffer[LowerHalf].nBytes == 0) {
dprintf4("None loaded");
//
// Free any 0 length buffers to satisfy the testers
//
ASSERT(IsListEmpty(&pLDI->TransitQueue));
sndFreeQ(pLDI, &pLDI->DeadQueue, STATUS_SUCCESS);
//
// Note - the Dpc routine RELIES on us returning here and
// not restarting the DMA which it may have just stopped.
// This is because it tries to restart the DMA in case we
// overran or something. This is OK except when we think (on
// the device user's thread) we stopped the DMA but a Dpc
// has just been sheduled to run. This 'extra' Dpc restarts the
// DMA we thought was stopped and we end up in a real mess.
//
// The actual crash hit assert failed when DMABusy was TRUE
// in sndStartDMA in recording.
//
// NOTE - this driver is STILL NOT MP safe. When we stop the
// DMA technically we should flush out any Dpcs like the soundlib
// code does - but I don't think the hardware for this driver runs
// on any MP machines.
//
return;
}
//
// We have something to dma
// Start the DMA - we're actually going to play something
//
sndStartDMA(pGDI, 1);
}
}
VOID
sndLoadDMABuffer(
PLOCAL_DEVICE_INFO pLDI,
struct SOUND_DMABUF *pDMA
)
/*++
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:
pLDI - our local device data
pDMA - The buffer and how full it is now
Return Value:
--*/
{
PGLOBAL_DEVICE_INFO pGDI;
pGDI = pLDI->pGlobalInfo;
ASSERT(pGDI->Key == GDI_KEY);
//
// Loop, copying data from the request list to the
// dma buffer. As we complete request blocks we mark
// them as done.
//
//
// If wave output is paused, do not process any data.
//
if (pLDI->State != WAVE_DD_STOPPED) {
//
// There should be no pending pauses because WAVE_DD_STOPPED
// should be the state if there is one
//
ASSERT(pGDI->pIrpPause == NULL);
//
// Loop copying data to the output buffers. Typically the
// output buffer will be much bigger than the DMA buffer.
//
while (pDMA->nBytes < pGDI->DmaHalfBufferSize) {
ULONG BytesToCopy;
//
// We might have completed the last buffer
// Note that we cope with 0 length buffers here
//
if (pGDI->pUserBuffer == NULL) {
sndGetNextBuffer(pLDI);
if (pGDI->pUserBuffer == NULL) {
//
// There REALLY aren't any buffers
//
break;
}
}
#ifdef WAVE_DD_DO_LOOPS
//
// See if this request contains any loop information
//
if (IoGetCurrentIrpStackLocation(pGDI->pIrp)->
Parameters.DeviceIoControl.InputBufferLength >=
sizeof(WAVEHDR)) {
//
// Check if this is the start of a new loop
// If so remember where the loop starts and
// how many iterations there are.
//
LPWAVEHDR pwhd;
pwhd = (LPWAVEHDR)pGDI->pIrp->AssociatedIrp.SystemBuffer;
if ((pwhd->dwFlags & WHDR_BEGINLOOP) &&
pLDI->LoopBegin != &pGDI->pIrp->Tail.Overlay.ListEntry) {
pLDI->LoopCount = pwhd->dwLoops;
if (pwhd->dwLoops == 0) {
pLDI->LoopBegin = NULL;
} else {
pLDI->LoopBegin = &pGDI->pIrp->Tail.Overlay.ListEntry;
}
}
}
#endif // WAVE_DD_DO_LOOPS
//
// Find out how much space we have left in the
// client's buffers
// Note that BytesToCopy may be 0 - this is OK
//
BytesToCopy =
min(pGDI->UserBufferSize - pGDI->UserBufferPosition,
pGDI->DmaHalfBufferSize - pDMA->nBytes);
//
// Copy the data
//
RtlMoveMemory(pDMA->Buf + pDMA->nBytes,
pGDI->pUserBuffer + pGDI->UserBufferPosition,
BytesToCopy);
//
// Update counters etc.
//
pGDI->UserBufferPosition += BytesToCopy;
pDMA->nBytes += BytesToCopy;
//
// Update our total of bytes
//
pLDI->SampleNumber += BytesToCopy;
//
// See if we've now filled a buffer
//
if (pGDI->UserBufferPosition == pGDI->UserBufferSize) {
dprintf4(" finished");
//
// Unmap the users buffer and set data length
//
sndCompleteIoBuffer(pGDI);
//
// Move the request into the transit camp
//
InsertTailList(&pLDI->TransitQueue,
&pGDI->pIrp->Tail.Overlay.ListEntry);
#ifdef WAVE_DD_DO_LOOPS
//
// Check for the ends of loops
//
if (IoGetCurrentIrpStackLocation(pGDI->pIrp)->
Parameters.DeviceIoControl.InputBufferLength >=
sizeof(WAVEHDR)) {
//
// Set input and output loop
//
LPWAVEHDR pwhd;
pwhd =
(LPWAVEHDR)pGDI->pIrp->AssociatedIrp.SystemBuffer;
if (pwhd->dwFlags & WHDR_ENDLOOP) {
if (pLDI->LoopCount == 0) {
//
// Finished looping - there may be some
// Irps which can be completed now but
// we leave them until the end of this
// DMA buffer.
//
pLDI->LoopBegin = NULL;
} else {
PLIST_ENTRY pListNode;
//
// There are more loops. Decrement the
// counter and restart the loop.
//
pLDI->LoopCount--;
do {
ASSERT(!IsListEmpty(&pLDI->TransitQueue));
pListNode = RemoveTailList(&pLDI->TransitQueue);
InsertHeadList(&pLDI->QueueHead, pListNode);
} while (pListNode != pLDI->LoopBegin);
}
}
} // End of loop stuff
#endif // WAVE_DD_DO_LOOPS
//
// Move the request into the transit camp
// Actually this is already done by sndNextBuffer
//
}
} // Continue around the loop until the request is satisfied
}
//
// if we transferred something, pad out the request with
// silence. For 8-bit 0 level is 0x80, for 16-bit it is 0
//
if (pDMA->nBytes < pGDI->DmaHalfBufferSize) {
dprintf4(" pad %d ", pGDI->DmaHalfBufferSize - pDMA->nBytes);
RtlFillMemory(pDMA->Buf + pDMA->nBytes,
pGDI->DmaHalfBufferSize - pDMA->nBytes,
pGDI->BytesPerSample == 1 ? 0x80 : 0x00);
}
//
// flush the i/o buffers
//
// Actually I386 needs none of this
// KeFlushIoBuffers(pGDI->pDMABufferMDL, FALSE); // flush for write
}
VOID
SoundOutDeferred(
PKDPC pDpc,
PDEVICE_OBJECT pDeviceObject,
PIRP pIrp,
PVOID Context
)
/*++
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 DeadQueue)
-- Moving Irps that make up the next buffer to play from
TransitQueue to DeadQueue and clearing TransitQueue.
-- 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:
pDPC - pointer to DPC object
pDeviceObject - pointer to our device object
pIrp - ???
Context - our Dpc context (NULL in our case).
Return Value:
None
--*/
{
PLOCAL_DEVICE_INFO pLDI;
PGLOBAL_DEVICE_INFO pGDI;
pLDI = (PLOCAL_DEVICE_INFO)pDeviceObject->DeviceExtension;
pGDI = pLDI->pGlobalInfo;
ASSERT(pGDI->Key == GDI_KEY);
dprintf4("(");
//
// Acquire the spin lock before we mess with the list
//
GlobalEnter(pGDI);
//
// 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
//
sndFreeQ(pLDI, &pLDI->DeadQueue, STATUS_SUCCESS);
//
// Move the Transit queue into the dead queue and empty the
// transit queue.
// Note that if a loop is in progress not everything is moved
// now.
//
while (!IsListEmpty(&pLDI->TransitQueue)) {
PLIST_ENTRY pListNode;
pListNode = RemoveHeadList(&pLDI->TransitQueue);
#ifdef WAVE_DD_DO_LOOPS
//
// Do not move loops over to the dead queue - there's
// still life in them !
//
if (pListNode == pLDI->LoopBegin) {
InsertHeadList(&pLDI->TransitQueue, pListNode);
break;
}
#endif // WAVE_DD_DO_LOOPS
//
// Move to death row
//
InsertTailList(&pLDI->DeadQueue, pListNode);
}
//
// The block we've just done is now empty, ready for reuse
//
pGDI->DMABuffer[pGDI->NextHalf].nBytes = 0;
dprintf4("%d", pGDI->SoundHardware.TcInterruptsPending);
//
// See if we were doing the last block or we got a real underflow
//
if (pGDI->DMABuffer[UpperHalf + LowerHalf - pGDI->NextHalf].nBytes == 0
|| pGDI->GotUnderFlow) {
#ifdef MIPSSND_TAIL_BUG
//
// Mute the sound so that no Click can be heard at the end.
// We are right now playing a blank block anyway.
//
sndMute(pGDI);
//
// Mute is same as turning off the headphone and lineout
// But Mute using volume "sounds" better.
// sndHeadphoneControl(pGDI, OFF);
// sndLineoutControl(pGDI, OFF);
//
#endif // MIPSSND_TAIL_BUG
// We wait for the underflow interrupt to occur and then
// stop the dma.
//
// Overflow can also occur as an error
//
if (pGDI->GotUnderFlow) {
pGDI->GotUnderFlow = 0;
//
// That was the end of the last block of the transfer.
// The MIPSSND are currently sending silence.
// We can stop DMA and release the adapter channel now.
//
sndStopDMA(pGDI);
//
// 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.
//
//
// Because we can really underflow we must flush out all our
// data
//
sndFreeQ(pLDI, &pLDI->DeadQueue, STATUS_SUCCESS);
sndFreeQ(pLDI, &pLDI->TransitQueue, STATUS_SUCCESS);
pGDI->DMABuffer[0].nBytes = 0;
pGDI->DMABuffer[1].nBytes = 0;
//
// Complete any STOP or RESET packet
//
if (pGDI->pIrpPause != NULL) {
ASSERT(pLDI->State == WAVE_DD_STOPPED);
//
// Complete any RESET. Note that this can delete
// input received since RESET was issued
//
if (*(PULONG)pGDI->pIrpPause->AssociatedIrp.SystemBuffer ==
WAVE_DD_RESET) {
sndResetOutput(pLDI);
}
pGDI->pIrpPause->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(pGDI->pIrpPause, IO_SOUND_INCREMENT);
pGDI->pIrpPause = NULL;
}
//
// If we really overflowed we can try starting our sound again.
// sndStartOutput detects if we're really stopped and does
// nothing.
// Just in case
if (pLDI->State != WAVE_DD_STOPPED){
// In case it had underflowed the volume would have been
// turned off. So in that case turn on the volume before
// you start to put out wave data.
sndSetOutputVolume( pGDI );
sndStartOutput( pLDI );
}
} // if got underflow
} else {
//
// 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.
//
sndFlush(pGDI, pGDI->NextHalf);
sndLoadDMABuffer(pLDI, &pGDI->DMABuffer[pGDI->NextHalf]);
//
// Restart DMA
//
sndReStartDMA(pGDI, pGDI->NextHalf);
//
// Move to the next half buffer
//
pGDI->NextHalf = LowerHalf + UpperHalf - pGDI->NextHalf;
}
//
// Release the spin lock
//
GlobalLeave(pGDI);
dprintf4(")");
return;
DBG_UNREFERENCED_PARAMETER(pDpc);
DBG_UNREFERENCED_PARAMETER(Context);
DBG_UNREFERENCED_PARAMETER(pIrp);
}