windows-nt/Source/XPSP1/NT/drivers/ddk/wdmaudio/ac97/driver/ichwave.cpp

1872 lines
57 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/********************************************************************************
** Copyright (c) 1998-2000 Microsoft Corporation. All Rights Reserved.
**
** Portions Copyright (c) 1998-1999 Intel Corporation
**
********************************************************************************/
// Every debug output has "Modulname text"
static char STR_MODULENAME[] = "ICH Stream: ";
#include "minwave.h"
#include "ichwave.h"
/*****************************************************************************
* General Info
*****************************************************************************
* To protect the stBDList structure that is used to store mappings, we use a
* spin lock called MapLock. This spin lock is also acquired when we change
* the DMA registers. Normally, changes in stBDList and the DMA registers go
* hand in hand. In case we only want to change the DMA registers, we need
* to acquire the spin lock!
*/
#pragma code_seg("PAGE")
/*****************************************************************************
* CreateMiniportWaveICHStream
*****************************************************************************
* Creates a wave miniport stream object for the ICH audio adapter. This is
* (nearly) like the macro STD_CREATE_BODY_ from STDUNK.H.
*/
NTSTATUS CreateMiniportWaveICHStream
(
OUT CMiniportWaveICHStream **WaveIchStream,
IN PUNKNOWN pUnknownOuter,
IN POOL_TYPE PoolType
)
{
PAGED_CODE ();
DOUT (DBG_PRINT, ("[CreateMiniportWaveICHStream]"));
//
// This is basically like the macro at stdunk with the change that we
// don't cast to interface unknown but to interface WaveIchStream.
//
*WaveIchStream = new (PoolType, 'rCcP')
CMiniportWaveICHStream (pUnknownOuter);
if (*WaveIchStream)
{
(*WaveIchStream)->AddRef ();
return STATUS_SUCCESS;
}
return STATUS_INSUFFICIENT_RESOURCES;
}
/*****************************************************************************
* CMiniportWaveICHStream::~CMiniportWaveICHStream
*****************************************************************************
* Destructor
*/
CMiniportWaveICHStream::~CMiniportWaveICHStream ()
{
PAGED_CODE ();
DOUT (DBG_PRINT, ("[CMiniportWaveICHStream::~CMiniportWaveICHStream]"));
//
// Print information about the scatter gather list.
//
DOUT (DBG_DMA, ("Head %d, Tail %d, Tag counter %d, Entries %d.",
stBDList.nHead, stBDList.nTail, stBDList.ulTagCounter,
stBDList.nBDEntries));
if (Wave)
{
//
// Disable interrupts and stop DMA just in case.
//
if (Wave->AdapterCommon)
{
Wave->AdapterCommon->WriteBMControlRegister (m_ulBDAddr + X_CR, (UCHAR)0);
//
// Update also the topology miniport if this was the render stream.
//
if (Wave->AdapterCommon->GetMiniportTopology () &&
(Channel == PIN_WAVEOUT_OFFSET))
{
Wave->AdapterCommon->GetMiniportTopology ()->SetCopyProtectFlag (FALSE);
}
}
//
// Remove stream from miniport Streams array.
//
if (Wave->Streams[Channel] == this)
{
Wave->Streams[Channel] = NULL;
}
//
// Release the scatter/gather table.
//
if (stBDList.pBDEntry)
{
HalFreeCommonBuffer (Wave->AdapterObject,
PAGE_SIZE,
stBDList.PhysAddr,
(PVOID)stBDList.pBDEntry,
FALSE);
stBDList.pBDEntry = NULL;
}
//
// Release the miniport.
//
Wave->Release ();
Wave = NULL;
}
//
// Release the service group.
//
if (ServiceGroup)
{
ServiceGroup->Release ();
ServiceGroup = NULL;
}
//
// Release the mapping table.
//
if (stBDList.pMapData)
{
ExFreePool (stBDList.pMapData);
stBDList.pMapData = NULL;
}
//
// Release the port stream.
//
if (PortStream)
{
PortStream->Release ();
PortStream = NULL;
}
}
/*****************************************************************************
* CMiniportWaveICHStream::Init
*****************************************************************************
* This routine initializes the stream object, sets up the BDL, and programs
* the buffer descriptor list base address register for the pin being
* initialized.
*/
NTSTATUS CMiniportWaveICHStream::Init
(
IN CMiniportWaveICH *Miniport_,
IN PPORTWAVEPCISTREAM PortStream_,
IN ULONG Channel_,
IN BOOLEAN Capture_,
IN PKSDATAFORMAT DataFormat_,
OUT PSERVICEGROUP *ServiceGroup_
)
{
PAGED_CODE ();
DOUT (DBG_PRINT, ("[CMiniportWaveICHStream::Init]"));
ASSERT (Miniport_);
ASSERT (PortStream_);
ASSERT (DataFormat_);
ASSERT (ServiceGroup_);
//
// The rule here is that we return when we fail without a cleanup.
// The destructor will relase the allocated memory.
//
NTSTATUS ntStatus = STATUS_SUCCESS;
//
// Initialize BDL info.
//
stBDList.pBDEntry = NULL;
stBDList.pMapData = NULL;
stBDList.nHead = 0;
stBDList.nTail = 0;
stBDList.ulTagCounter = 0;
stBDList.nBDEntries = 0;
//
// Save miniport pointer and addref it.
//
Wave = Miniport_;
Wave->AddRef ();
//
// Save portstream interface pointer and addref it.
//
PortStream = PortStream_;
PortStream->AddRef ();
//
// Save channel ID and capture flag.
//
Channel = Channel_;
Capture = Capture_;
//
// Save data format and current sample rate.
//
DataFormat = (PKSDATAFORMAT_WAVEFORMATEX)DataFormat_;
CurrentRate = DataFormat->WaveFormatEx.nSamplesPerSec;
NumberOfChannels = DataFormat->WaveFormatEx.nChannels;
//
// Initialize the BDL spinlock.
//
KeInitializeSpinLock (&MapLock);
//
// Create a service group (a DPC abstraction/helper) to help with
// interrupts.
//
ntStatus = PcNewServiceGroup (&ServiceGroup, NULL);
if (!NT_SUCCESS (ntStatus))
{
DOUT (DBG_ERROR, ("Failed to create a service group!"));
return ntStatus;
}
//
// Pass the ServiceGroup pointer to portcls.
//
*ServiceGroup_ = ServiceGroup;
ServiceGroup->AddRef ();
//
// Setup the Buffer Descriptor List (BDL)
// Allocate 32 entries of 8 bytes (one BDL entry). We allocate two tables
// because we need one table as a backup.
// The pointer is aligned on a 8 byte boundary (that's what we need).
//
stBDList.pBDEntry = (tBDEntry *)HalAllocateCommonBuffer (Wave->AdapterObject,
MAX_BDL_ENTRIES * sizeof (tBDEntry) * 2,
&stBDList.PhysAddr,
FALSE);
if (!stBDList.pBDEntry)
{
DOUT (DBG_ERROR, ("Failed HalAllocateCommonBuffer!"));
return STATUS_INSUFFICIENT_RESOURCES;
}
// calculate the (backup) pointer.
stBDList.pBDEntryBackup = (tBDEntry *)stBDList.pBDEntry + MAX_BDL_ENTRIES;
//
// Allocate a buffer for the 32 possible mappings. We allocate two tables
// because we need one table as a backup
//
stBDList.pMapData =
(tMapData *)ExAllocatePool (NonPagedPool, sizeof(tMapData) *
MAX_BDL_ENTRIES * 2);
if (!stBDList.pMapData)
{
DOUT (DBG_ERROR, ("Failed to allocate the back up buffer!"));
return STATUS_INSUFFICIENT_RESOURCES;
}
// calculate the (backup) pointer.
stBDList.pMapDataBackup = stBDList.pMapData + MAX_BDL_ENTRIES;
//
// Store the base address of this DMA engine.
//
if (Capture)
{
//
// could be PCM or MIC capture
//
if (Channel == PIN_WAVEIN_OFFSET)
{
// Base address for DMA registers.
m_ulBDAddr = PI_BDBAR;
}
else
{
// Base address for DMA registers.
m_ulBDAddr = MC_BDBAR;
}
}
else // render
{
// Base address for DMA registers.
m_ulBDAddr = PO_BDBAR;
}
//
// Reset the DMA and set the BD list pointer.
//
ResetDMA ();
//
// Reset the position pointers.
//
TotalBytesMapped = 0;
TotalBytesReleased = 0;
//
// Now set the requested sample rate. In case of a failure, the object
// gets destroyed and releases all memory etc.
//
ntStatus = SetFormat (DataFormat_);
if (!NT_SUCCESS (ntStatus))
{
DOUT (DBG_ERROR, ("Stream init SetFormat call failed!"));
return ntStatus;
}
//
// Initialize the device state.
//
m_PowerState = PowerDeviceD0;
PPREFETCHOFFSET PreFetchOffset;
//
// Query for the new interface "PreFetchOffset" and use
// function offered there in case the interface is offered.
//
if (NT_SUCCESS(PortStream->QueryInterface(IID_IPreFetchOffset, (PVOID *)&PreFetchOffset)))
{
// why don't we pad by 32 sample frames
PreFetchOffset->SetPreFetchOffset(32 * (DataFormat->WaveFormatEx.nChannels * 2));
PreFetchOffset->Release();
}
//
// Store the stream pointer, it is used by the ISR.
//
Wave->Streams[Channel] = this;
return STATUS_SUCCESS;
}
/*****************************************************************************
* CMiniportWaveICHStream::NonDelegatingQueryInterface
*****************************************************************************
* Obtains an interface. This function works just like a COM QueryInterface
* call and is used if the object is not being aggregated.
*/
STDMETHODIMP_(NTSTATUS) CMiniportWaveICHStream::NonDelegatingQueryInterface
(
IN REFIID Interface,
OUT PVOID * Object
)
{
PAGED_CODE ();
ASSERT (Object);
DOUT (DBG_PRINT, ("[CMiniportWaveICHStream::NonDelegatingQueryInterface]"));
//
// Convert for IID_IMiniportWavePciStream
//
if (IsEqualGUIDAligned (Interface, IID_IMiniportWavePciStream))
{
*Object = (PVOID)(PMINIPORTWAVEPCISTREAM)this;
}
//
// Convert for IID_IServiceSink
//
else if (IsEqualGUIDAligned (Interface, IID_IServiceSink))
{
*Object = (PVOID)(PSERVICESINK)this;
}
//
// Convert for IID_IDrmAudioStream
//
else if (IsEqualGUIDAligned (Interface, IID_IDrmAudioStream))
{
*Object = (PVOID)(PDRMAUDIOSTREAM)this;
}
//
// Convert for IID_IUnknown
//
else if (IsEqualGUIDAligned (Interface, IID_IUnknown))
{
*Object = (PVOID)(PUNKNOWN)(PMINIPORTWAVEPCISTREAM)this;
}
else
{
*Object = NULL;
return STATUS_INVALID_PARAMETER;
}
((PUNKNOWN)*Object)->AddRef ();
return STATUS_SUCCESS;
}
/*****************************************************************************
* CMiniportWaveICHStream::GetAllocatorFraming
*****************************************************************************
* Returns the framing requirements for this device.
* That is sample size (for one sample) and preferred frame (buffer) size.
*/
STDMETHODIMP_(NTSTATUS) CMiniportWaveICHStream::GetAllocatorFraming
(
OUT PKSALLOCATOR_FRAMING AllocatorFraming
)
{
PAGED_CODE ();
ULONG SampleSize;
DOUT (DBG_PRINT, ("[CMiniportWaveICHStream::GetAllocatorFraming]"));
//
// Determine sample size in bytes. Always number of
// channels * 2 (because 16-bit).
//
SampleSize = DataFormat->WaveFormatEx.nChannels * 2;
//
// Report the suggested requirements.
//
AllocatorFraming->RequirementsFlags =
KSALLOCATOR_REQUIREMENTF_SYSTEM_MEMORY |
KSALLOCATOR_REQUIREMENTF_PREFERENCES_ONLY;
AllocatorFraming->Frames = 8;
//
// Currently, arbitrarily selecting 10ms as the frame target size.
//
// This value needs to be sample block aligned for ICH to work correctly.
// Assumes 100Hz minimum sample rate (otherwise FrameSize is 0 bytes)
//
AllocatorFraming->FrameSize = SampleSize * (DataFormat->WaveFormatEx.nSamplesPerSec / 100);
AllocatorFraming->FileAlignment = FILE_LONG_ALIGNMENT;
AllocatorFraming->PoolType = NonPagedPool;
return STATUS_SUCCESS;
}
/*****************************************************************************
* CMiniportWaveICHStream::SetFormat
*****************************************************************************
* This routine tests for proper data format (calls wave miniport) and sets
* or changes the stream data format.
* To figure out if the codec supports the sample rate, we just program the
* sample rate and read it back. If it matches we return happy, if not then
* we restore the sample rate and return unhappy.
* We fail this routine if we are currently running (playing or recording).
*/
STDMETHODIMP_(NTSTATUS) CMiniportWaveICHStream::SetFormat
(
IN PKSDATAFORMAT Format
)
{
PAGED_CODE ();
ASSERT (Format);
ULONG TempRate;
DWORD dwControlReg;
DOUT (DBG_PRINT, ("[CMiniportWaveICHStream::SetFormat]"));
//
// Change sample rate when we are in the stop or pause states - not
// while running!
//
if (DMAEngineState & DMA_ENGINE_ON)
{
return STATUS_UNSUCCESSFUL;
}
//
// Ensure format falls in proper range and is supported.
//
NTSTATUS ntStatus = Wave->TestDataFormat (Format, (WavePins)(Channel << 1));
if (!NT_SUCCESS (ntStatus))
return ntStatus;
//
// Retrieve wave format portion.
//
PWAVEFORMATPCMEX waveFormat = (PWAVEFORMATPCMEX)(Format + 1);
//
// Save current rate in this context.
//
TempRate = waveFormat->Format.nSamplesPerSec;
//
// Check if we have a codec with one sample rate converter and there are streams
// already open.
//
if (Wave->Streams[PIN_WAVEIN_OFFSET] && Wave->Streams[PIN_WAVEOUT_OFFSET] &&
!Wave->AdapterCommon->GetNodeConfig (NODEC_PCM_VSR_INDEPENDENT_RATES))
{
//
// Figure out at which sample rate the other stream is running.
//
ULONG ulFrequency;
if (Wave->Streams[PIN_WAVEIN_OFFSET] == this)
ulFrequency = Wave->Streams[PIN_WAVEOUT_OFFSET]->CurrentRate;
else
ulFrequency = Wave->Streams[PIN_WAVEIN_OFFSET]->CurrentRate;
//
// Check if this sample rate is requested sample rate.
//
if (ulFrequency != TempRate)
{
return STATUS_UNSUCCESSFUL;
}
}
//
// Program the ICH to support n channels.
//
if (Channel == PIN_WAVEOUT_OFFSET)
{
dwControlReg = Wave->AdapterCommon->ReadBMControlRegister32 (GLOB_CNT);
dwControlReg = (dwControlReg & 0x03F) |
(((waveFormat->Format.nChannels >> 1) - 1) * GLOB_CNT_PCM4);
Wave->AdapterCommon->WriteBMControlRegister (GLOB_CNT, dwControlReg);
}
//
// Check for rate support by hardware. If it is supported, then update
// hardware registers else return not implemented and audio stack will
// handle it.
//
if (Capture)
{
if (Channel == PIN_WAVEIN_OFFSET)
{
ntStatus = Wave->AdapterCommon->
ProgramSampleRate (AC97REG_RECORD_SAMPLERATE, TempRate);
}
else
{
ntStatus = Wave->AdapterCommon->
ProgramSampleRate (AC97REG_MIC_SAMPLERATE, TempRate);
}
}
else
{
//
// In the playback case we might need to update several DACs
// with the new sample rate.
//
ntStatus = Wave->AdapterCommon->
ProgramSampleRate (AC97REG_FRONT_SAMPLERATE, TempRate);
if (Wave->AdapterCommon->GetNodeConfig (NODEC_SURROUND_DAC_PRESENT))
{
ntStatus = Wave->AdapterCommon->
ProgramSampleRate (AC97REG_SURROUND_SAMPLERATE, TempRate);
}
if (Wave->AdapterCommon->GetNodeConfig (NODEC_LFE_DAC_PRESENT))
{
ntStatus = Wave->AdapterCommon->
ProgramSampleRate (AC97REG_LFE_SAMPLERATE, TempRate);
}
}
if (NT_SUCCESS (ntStatus))
{
//
// print information and save the format information.
//
DataFormat = (PKSDATAFORMAT_WAVEFORMATEX)Format;
CurrentRate = TempRate;
NumberOfChannels = waveFormat->Format.nChannels;
}
return ntStatus;
}
/*****************************************************************************
* CMiniportWaveICHStream::SetContentId
*****************************************************************************
* This routine gets called by drmk.sys to pass the content to the driver.
* The driver has to enforce the rights passed.
*/
STDMETHODIMP_(NTSTATUS) CMiniportWaveICHStream::SetContentId
(
IN ULONG contentId,
IN PCDRMRIGHTS drmRights
)
{
PAGED_CODE ();
DOUT (DBG_PRINT, ("[CMiniportWaveICHStream::SetContentId]"));
//
// If "drmRights->DigitalOutputDisable" is set, we need to disable S/P-DIF.
// Currently, we don't have knowledge about the S/P-DIF interface. However,
// in case you expanded the driver with S/P-DIF features you need to disable
// S/P-DIF or fail SetContentId. If you have HW that has S/P-DIF turned on
// by default and you don't know how to turn off (or you cannot do that)
// then you must fail SetContentId.
//
// In our case, we assume the codec has no S/P-DIF or disabled S/P-DIF by
// default, so we can ignore the flag.
//
// Store the copyright flag. We have to disable PCM recording if it's set.
//
if (!Wave->AdapterCommon->GetMiniportTopology ())
{
DOUT (DBG_ERROR, ("Topology pointer not set!"));
return STATUS_UNSUCCESSFUL;
}
else
{
Wave->AdapterCommon->GetMiniportTopology ()->
SetCopyProtectFlag (drmRights->CopyProtect);
}
//
// We assume that if we can enforce the rights, that the old content
// will be destroyed. We don't need to store the content id since we
// have only one playback channel, so we are finished here.
//
return STATUS_SUCCESS;
}
/*****************************************************************************
* Non paged code begins here
*****************************************************************************
*/
#pragma code_seg()
/*****************************************************************************
* CMiniportWaveICHStream::PowerChangeNotify
*****************************************************************************
* This functions saves and maintains the stream state through power changes.
*/
NTSTATUS CMiniportWaveICHStream::PowerChangeNotify
(
IN POWER_STATE NewState
)
{
PAGED_CODE ();
KIRQL OldIrql;
NTSTATUS ntStatus = STATUS_SUCCESS;
DOUT (DBG_PRINT, ("[CMiniportWaveICHStream::PowerChangeNotify]"));
//
// We don't have to check the power state, that's already done by the wave
// miniport.
//
DOUT (DBG_POWER, ("Changing state to D%d.",
(ULONG)NewState.DeviceState - (ULONG)PowerDeviceD0));
switch (NewState.DeviceState)
{
case PowerDeviceD0:
//
// If we are coming from D2 or D3 we have to restore the registers cause
// there might have been a power loss.
//
if ((m_PowerState == PowerDeviceD3) || (m_PowerState == PowerDeviceD2))
{
//
// The scatter gather list is already arranged. A reset of the DMA
// brings all pointers to the default state. From there we can start.
//
// Acquire the mapping spin lock
KeAcquireSpinLock (&MapLock,&OldIrql);
ntStatus = ResetDMA ();
// Restore the remaining DMA registers, that is last valid index
// only if the index is not pointing to 0. Note that the index is
// equal to head + entries.
if (stBDList.nTail)
{
Wave->AdapterCommon->WriteBMControlRegister (m_ulBDAddr + X_LVI,
(UCHAR)((stBDList.nTail - 1) & BDL_MASK));
}
// Release the mapping spin lock
KeReleaseSpinLock (&MapLock,OldIrql);
}
break;
case PowerDeviceD1:
// Here we do nothing. The device has still enough power to keep all
// it's register values.
break;
case PowerDeviceD2:
case PowerDeviceD3:
//
// If we power down to D2 or D3 we might loose power, so we have to be
// aware of the DMA engine resetting. In that case a play would start
// with scatter gather entry 0 (the current index is read only).
// We just rearrange the scatter gather list (like we do on
// RevokeMappings) so that the current buffer which is played is at
// entry 0.
//
// Acquire the mapping spin lock
KeAcquireSpinLock (&MapLock,&OldIrql);
// Disable interrupts and stop DMA just in case.
Wave->AdapterCommon->WriteBMControlRegister (m_ulBDAddr + X_CR, (UCHAR)0);
// Get current index
int nCurrentIndex = (int)Wave->AdapterCommon->
ReadBMControlRegister8 (m_ulBDAddr + X_CIV);
//
// First move the BD list to the beginning.
//
// In case the DMA engine was stopped, current index may point to an
// empty BD entry. When we start the DMA engine it would then play this
// (undefined) entry, so we check here for that condition.
//
if ((nCurrentIndex == ((stBDList.nHead - 1) & BDL_MASK)) &&
(stBDList.nBDEntries != MAX_BDL_ENTRIES - 1))
{
nCurrentIndex = stBDList.nHead; // point to head
}
//
// Move BD list to (0-((current - head) & mask)) & mask, where
// ((current - head) & mask) is the difference between head and
// current index, no matter where they are :)
//
MoveBDList (stBDList.nHead, (stBDList.nTail - 1) & BDL_MASK,
(0 - ((nCurrentIndex - stBDList.nHead) & BDL_MASK)) & BDL_MASK);
//
// Update structure.
//
stBDList.nHead = (0 - ((nCurrentIndex - stBDList.nHead) & BDL_MASK)) &
BDL_MASK;
stBDList.nTail = (stBDList.nHead + stBDList.nBDEntries) & BDL_MASK;
// release the mapping spin lock
KeReleaseSpinLock (&MapLock,OldIrql);
break;
}
//
// Save the new state. This local value is used to determine when to
// cache property accesses and when to permit the driver from accessing
// the hardware.
//
m_PowerState = NewState.DeviceState;
DOUT (DBG_POWER, ("Entering D%d",
(ULONG)m_PowerState - (ULONG)PowerDeviceD0));
return ntStatus;
}
/*****************************************************************************
* CMiniportWaveICHStream::SetState
*****************************************************************************
* This routine sets/changes the DMA engine state to play, stop, or pause
*/
STDMETHODIMP_(NTSTATUS) CMiniportWaveICHStream::SetState
(
IN KSSTATE State
)
{
PAGED_CODE (); // Gets called at PASSIVE_LEVEL
KIRQL OldIrql;
DOUT (DBG_PRINT, ("[CMiniportWaveICHStream::SetState]"));
DOUT (DBG_STREAM, ("SetState to %d", State));
//
// Start or stop the DMA engine dependent of the state.
//
switch (State)
{
case KSSTATE_STOP:
// acquire the mapping spin lock
KeAcquireSpinLock (&MapLock,&OldIrql);
// Just pause DMA. If we have mappings left in our queue,
// portcls will call RevokeMappings to destroy them.
PauseDMA ();
// release the mapping spin lock
KeReleaseSpinLock (&MapLock,OldIrql);
// Release processed mappings
ReleaseUsedMappings ();
// Reset the position counters.
TotalBytesMapped = TotalBytesReleased = 0;
break;
case KSSTATE_ACQUIRE:
case KSSTATE_PAUSE:
// acquire the mapping spin lock
KeAcquireSpinLock (&MapLock,&OldIrql);
// pause now.
PauseDMA ();
// release the mapping spin lock
KeReleaseSpinLock (&MapLock,OldIrql);
// Release processed mappings
ReleaseUsedMappings ();
break;
case KSSTATE_RUN:
//
// Let's rock.
//
// Make sure we are not running already.
if (DMAEngineState & DMA_ENGINE_ON)
{
return STATUS_SUCCESS;
}
// Release processed mappings.
ReleaseUsedMappings ();
// Get new mappings.
GetNewMappings ();
// acquire the mapping spin lock
KeAcquireSpinLock (&MapLock,&OldIrql);
// Kick DMA again just in case.
ResumeDMA ();
// release the mapping spin lock
KeReleaseSpinLock (&MapLock,OldIrql);
break;
}
return STATUS_SUCCESS;
}
/*****************************************************************************
* CMiniportWaveICHStream::MoveBDList
*****************************************************************************
* Moves the BDList.
* This function is used to remove entries from the scatter gather list or to
* move the valid entries to the top. The mapping table which is hard linked
* to the scatter gather entries is moved too.
* The function does not change any variables in tBDList.
* The mapping spin lock must be held when calling this routine.
* We use this function to remove mappings (RevokeMappings) or to rearrange the
* list for powerdown/up management (the DMA starts at position zero again).
* Note that there is a simple way of doing this also. When you zero the buffer
* length in the scatter gather, the DMA engine ignores the entry and continues
* with the next. But our way is more generic and if you ever want to port the
* driver to another DMA engine you might be thankful for this code.
*/
void CMiniportWaveICHStream::MoveBDList
(
IN int nFirst,
IN int nLast,
IN int nNewPos
)
{
DOUT (DBG_PRINT, ("[CMiniportWaveICHStream::MoveBDList]"));
//
// Print information about the scatter gather list.
//
DOUT (DBG_DMA, ("Moving BD entry %d-%d to %d.", nFirst, nLast, nNewPos));
//
// First copy the tables to a save place.
//
RtlCopyMemory ((PVOID)stBDList.pBDEntryBackup,
(PVOID)stBDList.pBDEntry,
sizeof (tBDEntry) * MAX_BDL_ENTRIES);
RtlCopyMemory ((PVOID)stBDList.pMapDataBackup,
(PVOID)stBDList.pMapData,
sizeof (tMapData) * MAX_BDL_ENTRIES);
//
// We move all the entries in blocks to the new position.
//
int nBlockCounter = 0;
do
{
nBlockCounter++;
//
// We must copy the block when the index wraps around (ring buffer)
// or we are at the last entry.
//
if (((nNewPos + nBlockCounter) == MAX_BDL_ENTRIES) || // wrap around
((nFirst + nBlockCounter) == MAX_BDL_ENTRIES) || // wrap around
((nFirst + nBlockCounter) == (nLast + 1))) // last entry
{
//
// copy one block (multiple entries).
//
RtlCopyMemory ((PVOID)&stBDList.pBDEntry[nNewPos],
(PVOID)&stBDList.pBDEntryBackup[nFirst],
sizeof (tBDEntry) * nBlockCounter);
RtlCopyMemory ((PVOID)&stBDList.pMapData[nNewPos],
(PVOID)&stBDList.pMapDataBackup[nFirst],
sizeof (tMapData) * nBlockCounter);
// adjust the index
nNewPos = (nNewPos + nBlockCounter) & BDL_MASK;
nFirst = (nFirst + nBlockCounter) & BDL_MASK;
nBlockCounter = 0;
}
// nBlockCounter should be zero when the end condition hits.
} while (((nFirst + nBlockCounter - 1) & BDL_MASK) != nLast);
}
/*****************************************************************************
* CMiniportWaveICHStream::Service
*****************************************************************************
* This routine is called by the port driver in response to the interrupt
* service routine requesting service on the stream's service group.
* Requesting service on the service group results in a DPC being scheduled
* that calls this routine when it runs.
*/
STDMETHODIMP_(void) CMiniportWaveICHStream::Service (void)
{
DOUT (DBG_PRINT, ("Service"));
// release all mappings
ReleaseUsedMappings ();
// get new mappings
GetNewMappings ();
}
/*****************************************************************************
* CMiniportWaveICHStream::NormalizePhysicalPosition
*****************************************************************************
* Given a physical position based on the actual number of bytes transferred,
* this function converts the position to a time-based value of 100ns units.
*/
STDMETHODIMP_(NTSTATUS) CMiniportWaveICHStream::NormalizePhysicalPosition
(
IN OUT PLONGLONG PhysicalPosition
)
{
ULONG SampleSize;
DOUT (DBG_PRINT, ("NormalizePhysicalPosition"));
//
// Determine the sample size in bytes
//
SampleSize = DataFormat->WaveFormatEx.nChannels * 2;
//
// Calculate the time in 100ns steps.
//
*PhysicalPosition = (_100NS_UNITS_PER_SECOND / SampleSize *
*PhysicalPosition) / CurrentRate;
return STATUS_SUCCESS;
}
/*****************************************************************************
* CMiniportWaveICHStream::GetPosition
*****************************************************************************
* Gets the stream position. This is a byte count of the current position of
* a stream running on a particular DMA engine. We must return a sample
* accurate count or the WaveDrv32 wave drift tests (35.2 & 36.2) will fail.
*
* The position is the sum of three parts:
* 1) The total number of bytes in released buffers
* 2) The position in the current buffer.
* 3) The total number of bytes in played but not yet released buffers
*/
STDMETHODIMP_(NTSTATUS) CMiniportWaveICHStream::GetPosition
(
OUT PULONGLONG Position
)
{
KIRQL OldIrql;
UCHAR nCurrentIndex = 0;
DWORD RegisterX_PICB;
ASSERT (Position);
//
// Acquire the mapping spin lock.
//
KeAcquireSpinLock (&MapLock, &OldIrql);
//
// Start with TotalBytesReleased (mappings released).
//
*Position = TotalBytesReleased;
//
// If we have entries in the list, we may have buffers that have not been
// released but have been at least partially played.
//
if (stBDList.nBDEntries)
{
//
// Repeat this until we get the same reading twice. This will prevent
// jumps when we are near the end of the buffer.
//
do
{
nCurrentIndex = Wave->AdapterCommon->
ReadBMControlRegister8 (m_ulBDAddr + X_CIV);
RegisterX_PICB = (DWORD)Wave->AdapterCommon->ReadBMControlRegister16 (m_ulBDAddr + X_PICB);
} while (nCurrentIndex != (int)Wave->AdapterCommon->
ReadBMControlRegister8 (m_ulBDAddr + X_CIV));
//
// If we never released a buffer and register X_PICB is zero then the DMA was not
// initialized yet and the position should be zero.
//
if (RegisterX_PICB || nCurrentIndex || TotalBytesReleased)
{
//
// Add in our position in the current buffer. The read returns the
// amount left in the buffer.
//
*Position += (stBDList.pMapData[nCurrentIndex].ulBufferLength - (RegisterX_PICB << 1));
//
// Total any buffers that have been played and not released.
//
if (nCurrentIndex != ((stBDList.nHead -1) & BDL_MASK))
{
int i = stBDList.nHead;
while (i != nCurrentIndex)
{
*Position += (ULONGLONG)stBDList.pMapData[i].ulBufferLength;
i = (i + 1) & BDL_MASK;
}
}
}
}
DOUT (DBG_POSITION, ("[GetPosition] POS: %08x'%08x\n", (DWORD)(*Position >> 32), (DWORD)*Position));
//
// Release the mapping spin lock.
//
KeReleaseSpinLock (&MapLock, OldIrql);
return STATUS_SUCCESS;
}
/*****************************************************************************
* CMiniportWaveICHStream::RevokeMappings
*****************************************************************************
* This routine is used by the port to revoke mappings previously delivered
* to the miniport stream that have not yet been unmapped. This would
* typically be called in response to an I/O cancellation request.
*/
STDMETHODIMP_(NTSTATUS) CMiniportWaveICHStream::RevokeMappings
(
IN PVOID FirstTag,
IN PVOID LastTag,
OUT PULONG MappingsRevoked
)
{
ASSERT (MappingsRevoked);
KIRQL OldIrql;
ULONG ulOldDMAEngineState;
int nCurrentIndex, nSearchIndex, nFirst, nLast, nNumMappings;
DOUT (DBG_PRINT, ("[CMiniportWaveICHStream::RevokeMappings]"));
//
// print information about the scatter gather list.
//
DOUT (DBG_DMA, ("Head %d, Tail %d, Tag counter %d, Entries %d.",
stBDList.nHead, stBDList.nTail, stBDList.ulTagCounter,
stBDList.nBDEntries));
//
// Start accessing the mappings.
//
KeAcquireSpinLock (&MapLock, &OldIrql);
//
// Save old DMA engine state.
//
ulOldDMAEngineState = DMAEngineState;
//
// First stop the DMA engine so it won't process the next buffer in the
// scatter gather list which might be one of the revoked buffers.
//
PauseDMA ();
// Get current index
nCurrentIndex = Wave->AdapterCommon->
ReadBMControlRegister8 (m_ulBDAddr + X_CIV);
//
// We always rearrange the scatter gather list. That means we reset the DMA
// engine and move the BD list so that the entry where the current index
// pointed to is located at position 0.
//
ResetDMA ();
//
// Return immediately if we just have 1 entry in our BD list.
//
if (!stBDList.nBDEntries || (stBDList.nBDEntries == 1))
{
*MappingsRevoked = stBDList.nBDEntries;
stBDList.nHead = stBDList.nTail = stBDList.nBDEntries = 0;
//
// CIV and LVI of DMA registers are set to 0 already.
//
KeReleaseSpinLock (&MapLock, OldIrql);
return STATUS_SUCCESS;
}
//
// First move the BD list to the beginning. In case the DMA engine was
// stopped, current index may point to an empty BD entry.
//
if ((nCurrentIndex == ((stBDList.nHead - 1) & BDL_MASK)) &&
(stBDList.nBDEntries != MAX_BDL_ENTRIES - 1))
{
nCurrentIndex = stBDList.nHead; // point to head
}
//
// Move BD list to (0-((current - head) & mask)) & mask
// where ((current - head) & mask) is the difference between head and
// current index, no matter where they are :)
//
MoveBDList (stBDList.nHead, (stBDList.nTail - 1) & BDL_MASK,
(0 - ((nCurrentIndex - stBDList.nHead) & BDL_MASK)) & BDL_MASK);
//
// Update structure.
//
stBDList.nHead = (0 - ((nCurrentIndex - stBDList.nHead) & BDL_MASK)) &
BDL_MASK;
stBDList.nTail = (stBDList.nHead + stBDList.nBDEntries) & BDL_MASK;
//
// Then we have to search for the tags. If we wouldn't have to rearrange the
// scatter gather list all the time, then we could use the tag as an index
// to the array, but the only way to clear DMA caches is a reset which has
// the side-effect that we have to rearrange the BD list (see above).
//
// search for first...
//
nSearchIndex = stBDList.nHead;
do
{
if ((void *)ULongToPtr(stBDList.pMapData[nSearchIndex].ulTag) == FirstTag)
break;
nSearchIndex = (nSearchIndex + 1) & BDL_MASK;
} while (nSearchIndex != stBDList.nTail);
nFirst = nSearchIndex;
//
// Search for last...
//
nSearchIndex = stBDList.nHead;
do
{
if ((void *)ULongToPtr(stBDList.pMapData[nSearchIndex].ulTag) == LastTag)
break;
nSearchIndex = (nSearchIndex + 1) & BDL_MASK;
} while (nSearchIndex != stBDList.nTail);
nLast = nSearchIndex;
//
// Check search result.
//
if ((nFirst == stBDList.nTail) || (nLast == stBDList.nTail))
{
DOUT (DBG_ERROR, ("!!! Entry not found !!!"));
//
// restart DMA in case it was running
//
if ((ulOldDMAEngineState & DMA_ENGINE_ON) && stBDList.nBDEntries)
ResumeDMA ();
*MappingsRevoked = 0;
KeReleaseSpinLock (&MapLock, OldIrql);
return STATUS_UNSUCCESSFUL; // one of the tags not found
}
// Print the index numbers found.
DOUT (DBG_DMA, ("Removing entries %d (%d) to %d (%d).", nFirst, FirstTag,
nLast, LastTag));
//
// Calculate the entries between the indizes.
//
if (nLast < nFirst)
{
nNumMappings = ((nLast + MAX_BDL_ENTRIES) - nFirst) + 1;
}
else
{
nNumMappings = (nLast - nFirst) + 1;
}
//
// Print debug inormation.
//
DOUT (DBG_DMA, ("Found entries: %d-%d, %d entries.", nFirst, nLast,
nNumMappings));
//
// Now remove the revoked buffers. Move the BD list and modify the
// status information.
//
if (nFirst < stBDList.nTail)
{
//
// In this case, both first and last are >= the current index (0)
//
if (nLast != ((stBDList.nTail - 1) & BDL_MASK))
{
//
// Not the last entry, so move the BD list + mappings.
//
MoveBDList ((nLast + 1) & BDL_MASK, (stBDList.nTail - 1) & BDL_MASK,
nFirst);
}
stBDList.nTail = (stBDList.nTail - nNumMappings) & BDL_MASK;
}
//
// In this case, at least first is "<" than current index (0)
//
else
{
//
// Check for last.
//
if (nLast < stBDList.nTail)
{
//
// Last is ">=" current index and first is "<" current index (0).
// Remove MAX_DBL_ENTRIES - first entries in front of current index.
//
if (nFirst != stBDList.nHead)
{
//
// Move from head towards current index.
//
MoveBDList (stBDList.nHead, nFirst - 1,
(stBDList.nHead + (MAX_BDL_ENTRIES - nFirst)) &
BDL_MASK);
}
//
// Adjust head.
//
stBDList.nHead = (stBDList.nHead + (MAX_BDL_ENTRIES - nFirst)) &
BDL_MASK;
//
// Remove nLast entries from CIV to tail.
//
if (nLast != ((stBDList.nTail - 1) & BDL_MASK))
{
//
// Not the last entry, so move the BD list + mappings.
//
MoveBDList (nLast + 1, (stBDList.nTail - 1) & BDL_MASK, 0);
}
//
// Adjust tail.
//
stBDList.nTail = (stBDList.nTail - (nLast + 1)) & BDL_MASK;
}
//
// Last is "<" current index and first is "<" current index (0).
//
else
{
//
// Remove nNumMappings entries in front of current index.
//
if (nFirst != stBDList.nHead)
{
//
// Move from head towards current index.
//
MoveBDList (stBDList.nHead, nFirst - 1,
(nLast - nNumMappings) + 1);
}
//
// Adjust head.
//
stBDList.nHead = (stBDList.nHead + nNumMappings) & BDL_MASK;
}
}
//
// In all cases, reduce the number of mappings.
//
stBDList.nBDEntries -= nNumMappings;
//
// Print debug information.
//
DOUT (DBG_DMA, ("Number of mappings is now %d, Head is %d, Tail is %d",
stBDList.nBDEntries, stBDList.nHead, stBDList.nTail));
//
// Reprogram the last valid index only when tail != 0
//
if (stBDList.nTail)
{
Wave->AdapterCommon->WriteBMControlRegister (m_ulBDAddr + X_LVI,
(UCHAR)(stBDList.nTail - 1 & BDL_MASK));
}
//
// Just un-pause the DMA engine if it was running before and there are
// still entries left and tail != 0.
//
if ((ulOldDMAEngineState & DMA_ENGINE_ON) && stBDList.nBDEntries
&& stBDList.nTail)
{
ResumeDMA ();
}
//
// Release the mapping spin lock and return the number of mappings we
// revoked.
//
KeReleaseSpinLock (&MapLock, OldIrql);
*MappingsRevoked = nNumMappings;
return STATUS_SUCCESS;
}
/*****************************************************************************
* CMiniportWaveICHStream::MappingAvailable
*****************************************************************************
* This routine is called by the port driver to notify the stream that there
* are new mappings available. Note that this is ONLY called after the stream
* has previously had a GetMapping() call fail due to lack of available
* mappings.
*/
STDMETHODIMP_(void) CMiniportWaveICHStream::MappingAvailable (void)
{
DOUT (DBG_PRINT, ("MappingAvailable"));
//
// Release processed mappings.
//
ReleaseUsedMappings ();
//
// Process the new mappings.
//
GetNewMappings ();
}
/*****************************************************************************
* CMiniportWaveICHStream::GetNewMappings
*****************************************************************************
* This routine is called when new mappings are available from the port driver.
* The routine places mappings into the input mapping queue. ICH can handle up
* to 32 entries (descriptors). We program the DMA registers if we have at least
* one mapping in the queue. The mapping spin lock must be held when calling
* this routine.
*/
NTSTATUS CMiniportWaveICHStream::GetNewMappings (void)
{
KIRQL OldIrql;
NTSTATUS ntStatus = STATUS_SUCCESS;
ULONG ulBytesMapped = 0;
int nInsertMappings = 0;
int nTail; // aut. variable
DOUT (DBG_PRINT, ("[CMiniportWaveICHStream::GetNewMappings]"));
// acquire the mapping spin lock
KeAcquireSpinLock (&MapLock,&OldIrql);
#if (DBG)
if (Wave->AdapterCommon->ReadBMControlRegister16 (m_ulBDAddr + X_SR) & SR_CELV)
{
//
// We starve. :-(
//
DOUT (DBG_DMA, ("[GetNewMappings] We starved ... Head %d, Tail %d, Entries %d.",
stBDList.nHead, stBDList.nTail, stBDList.nBDEntries));
}
#endif
//
// Get available mappings up to the max of 31 that we can hold in the BDL.
//
while (stBDList.nBDEntries < (MAX_BDL_ENTRIES - 1))
{
//
// Get the information from the list.
//
ULONG Flags;
ULONG ulTag = stBDList.ulTagCounter++;
ULONG ulBufferLength;
PHYSICAL_ADDRESS PhysAddr;
PVOID VirtAddr;
// Release the mapping spin lock
KeReleaseSpinLock (&MapLock,OldIrql);
//
// Try to get the mapping from the port.
// Here comes the problem: When calling GetMapping or ReleaseMapping we
// cannot hold our spin lock. So we need to buffer the return values and
// stick the information into the structure later when we have the spin
// lock acquired.
//
ntStatus = PortStream->GetMapping ((PVOID)ULongToPtr(ulTag),
(PPHYSICAL_ADDRESS)&PhysAddr,
&VirtAddr,
&ulBufferLength,
&Flags);
// Acquire the mapping spin lock
KeAcquireSpinLock (&MapLock,&OldIrql);
//
// Quit this loop when we run out of mappings.
//
if (!NT_SUCCESS (ntStatus))
{
break;
}
// Sanity check: The audio stack will not give you data
// that you cannot handle, but an application could use
// DirectKS and send bad buffer down.
// One mapping needs to be <0x1FFFF bytes for mono
// streams on the ICH.
if (ulBufferLength > 0x1FFFE)
{
// That is a little too long. That should never happen.
DOUT (DBG_ERROR, ("[GetNewMappings] Buffer length too long!"));
ulBufferLength = 0x1FFFE;
}
// The ICH can only handle WORD aligned buffers.
if (PhysAddr.LowPart & 0x01)
{
// we cannot play that! Set the buffer length to 0 so
// that the HW will skip the buffer.
DOUT (DBG_WARNING, ("[GetNewMappings] Buffer address unaligned!"));
ulBufferLength = 0;
}
// The ICH cannot handle unaligned mappings with respect
// to the frame size (eg. 42 bytes on 4ch playback).
if (ulBufferLength % NumberOfChannels)
{
// modify the length (don't play the rest of the bytes)
DOUT (DBG_WARNING, ("[GetNewMappings] Buffer length unaligned!"));
ulBufferLength -= ulBufferLength % NumberOfChannels;
}
//
// Save the mapping.
//
nTail = stBDList.nTail;
stBDList.pMapData[nTail].ulTag = ulTag;
stBDList.pMapData[nTail].PhysAddr = PhysAddr;
stBDList.pMapData[nTail].pVirtAddr = VirtAddr;
stBDList.pMapData[nTail].ulBufferLength = ulBufferLength;
ulBytesMapped += ulBufferLength;
//
// Fill in the BDL entry with pointer to physical address and length.
//
stBDList.pBDEntry[nTail].dwPtrToPhyAddress = PhysAddr.LowPart;
stBDList.pBDEntry[nTail].wLength = (WORD)(ulBufferLength >> 1);
stBDList.pBDEntry[nTail].wPolicyBits = BUP_SET;
//
// Generate an interrupt when portcls tells us to or roughly every 10ms.
//
if (Flags || (ulBytesMapped > (CurrentRate * NumberOfChannels * 2) / 100))
{
stBDList.pBDEntry[nTail].wPolicyBits |= IOC_ENABLE;
ulBytesMapped = 0;
}
//
// Take the new mapping into account.
//
stBDList.nTail = (stBDList.nTail + 1) & BDL_MASK;
stBDList.nBDEntries++;
TotalBytesMapped += (ULONGLONG)ulBufferLength;
nInsertMappings++;
//
// Set last valid index (LVI) register! We need to do this here to avoid inconsistency
// of the BDList with the HW. Note that we need to release spin locks every time
// we call into portcls, that means we can be interrupted by ReleaseUsedMappings.
//
Wave->AdapterCommon->WriteBMControlRegister (m_ulBDAddr + X_LVI, (UCHAR)nTail);
}
//
// If there were processed mappings, print out some debug messages and eventually try to
// restart DMA engine.
//
if (nInsertMappings)
{
//
// Print debug information ...
//
DOUT (DBG_DMA, ("[GetNewMappings] Got %d mappings.", nInsertMappings));
DOUT (DBG_DMA, ("[GetNewMappings] Head %d, Tail %d, Entries %d.",
stBDList.nHead, stBDList.nTail, stBDList.nBDEntries));
if (DMAEngineState & DMA_ENGINE_NEED_START)
ResumeDMA ();
}
// Release the mapping spin lock
KeReleaseSpinLock (&MapLock,OldIrql);
return ntStatus;
}
/*****************************************************************************
* CMiniportWaveICHStream::ReleaseUsedMappings
*****************************************************************************
* This routine unmaps previously mapped memory that the hardware has
* completed processing on. This routine is typically called at DPC level
* from the stream deferred procedure call that results from a stream
* interrupt. The mapping spin lock must be held when calling this routine.
*/
NTSTATUS CMiniportWaveICHStream::ReleaseUsedMappings (void)
{
KIRQL OldIrql;
int nMappingsReleased = 0;
DOUT (DBG_PRINT, ("[CMiniportWaveICHStream::ReleaseUsedMappings]"));
// acquire the mapping spin lock
KeAcquireSpinLock (&MapLock,&OldIrql);
//
// Clean up everything to that index.
//
while (stBDList.nBDEntries)
{
//
// Get current index
//
int nCurrentIndex = (int)Wave->AdapterCommon->
ReadBMControlRegister8 (m_ulBDAddr + X_CIV);
//
// When CIV is == Head -1 we released all mappings.
//
if (nCurrentIndex == ((stBDList.nHead - 1) & BDL_MASK))
{
break;
}
//
// Check if CIV is between head and tail.
//
if (nCurrentIndex < stBDList.nHead)
{
//
// Check for CIV being outside range.
//
if ((nCurrentIndex + MAX_BDL_ENTRIES) >=
(stBDList.nHead + stBDList.nBDEntries))
{
DOUT (DBG_ERROR, ("[ReleaseUsedMappings] CIV out of range!"));
break;
}
}
else
{
//
// Check for CIV being outside range.
//
if (nCurrentIndex >= (stBDList.nHead + stBDList.nBDEntries))
{
DOUT (DBG_ERROR, ("[ReleaseUsedMappings] CIV out of range!"));
break;
}
}
//
// Check to see if we've released all the buffers.
//
if (stBDList.nHead == nCurrentIndex)
{
if (nCurrentIndex == ((stBDList.nTail - 1) & BDL_MASK))
{
//
// A special case is starvation or stop of stream, when the
// DMA engine finished playing the buffers, CVI is equal LVI
// and SR_CELV is set.
//
if (!(Wave->AdapterCommon->
ReadBMControlRegister16 (m_ulBDAddr + X_SR) & SR_CELV))
{
// It is still playing the last buffer.
break;
}
//
// In case the CVI=LVI bit is set, nBDEntries should be 1
//
if (stBDList.nBDEntries != 1)
{
DOUT (DBG_ERROR, ("[ReleaseUsedMappings] Inconsitency: Tail reached and Entries != 1."));
}
}
else
{
//
// Bail out. Current Index did not move.
//
break;
}
}
//
// Save the tag and remove the entry from the list.
//
ULONG ulTag = stBDList.pMapData[stBDList.nHead].ulTag;
TotalBytesReleased += (ULONGLONG)stBDList.pMapData[stBDList.nHead].ulBufferLength;
stBDList.nBDEntries--;
stBDList.nHead = (stBDList.nHead + 1) & BDL_MASK;
nMappingsReleased++;
// Release the mapping spin lock
KeReleaseSpinLock (&MapLock,OldIrql);
//
// Release this entry.
//
PortStream->ReleaseMapping ((PVOID)ULongToPtr(ulTag));
// acquire the mapping spin lock
KeAcquireSpinLock (&MapLock,&OldIrql);
}
// Print some debug information in case we released mappings.
if (nMappingsReleased)
{
//
// Print release information and return.
//
DOUT (DBG_DMA, ("[ReleaseUsedMappings] Head %d, Tail %d, Entries %d.",
stBDList.nHead, stBDList.nTail, stBDList.nBDEntries));
}
// Release the mapping spin lock
KeReleaseSpinLock (&MapLock,OldIrql);
return STATUS_SUCCESS;
}
/*****************************************************************************
* CMiniportWaveICHStream::ResetDMA
*****************************************************************************
* This routine resets the Run/Pause bit in the control register. In addition, it
* resets all DMA registers contents.
* You need to have the spin lock "MapLock" acquired.
*/
NTSTATUS CMiniportWaveICHStream::ResetDMA (void)
{
DOUT (DBG_PRINT, ("ResetDMA"));
//
// Turn off DMA engine (or make sure it's turned off)
//
UCHAR RegisterValue = Wave->AdapterCommon->
ReadBMControlRegister8 (m_ulBDAddr + X_CR) & ~CR_RPBM;
Wave->AdapterCommon->
WriteBMControlRegister (m_ulBDAddr + X_CR, RegisterValue);
//
// Reset all register contents.
//
RegisterValue |= CR_RR;
Wave->AdapterCommon->
WriteBMControlRegister (m_ulBDAddr + X_CR, RegisterValue);
//
// Wait until reset condition is cleared by HW; should not take long.
//
ULONGLONG ullStartTime = PcGetTimeInterval (0);
BOOL bTimedOut = TRUE;
do
{
if (!(Wave->AdapterCommon->
ReadBMControlRegister8 (m_ulBDAddr + X_CR) & CR_RR))
{
bTimedOut = FALSE;
break;
}
} while (PcGetTimeInterval (ullStartTime) < GTI_MILLISECONDS (1000));
if (bTimedOut)
{
DOUT (DBG_ERROR, ("ResetDMA TIMEOUT!!"));
}
//
// We only want interrupts upon completion.
//
RegisterValue = CR_IOCE | CR_LVBIE;
Wave->AdapterCommon->
WriteBMControlRegister (m_ulBDAddr + X_CR, RegisterValue);
//
// Setup the Buffer Descriptor Base Address (BDBA) register.
//
Wave->AdapterCommon->
WriteBMControlRegister (m_ulBDAddr,
stBDList.PhysAddr.u.LowPart);
//
// Set the DMA engine state.
//
DMAEngineState = DMA_ENGINE_RESET;
return STATUS_SUCCESS;
}
/*****************************************************************************
* CMiniportWaveICHStream::PauseDMA
*****************************************************************************
* This routine pauses a hardware stream by reseting the Run/Pause bit in the
* control registers, leaving DMA registers content intact so that the stream
* can later be resumed.
* You need to have the spin lock "MapLock" acquired.
*/
NTSTATUS CMiniportWaveICHStream::PauseDMA (void)
{
DOUT (DBG_PRINT, ("PauseDMA"));
//
// Only pause if we're actually "ON" (DMA_ENGINE_ON or DMA_ENGINE_NEED_START)
//
if (!(DMAEngineState & DMA_ENGINE_ON))
{
return STATUS_SUCCESS;
}
//
// Turn off DMA engine by resetting the RPBM bit to 0. Don't reset any
// registers.
//
UCHAR RegisterValue = Wave->AdapterCommon->
ReadBMControlRegister8 (m_ulBDAddr + X_CR);
RegisterValue &= ~CR_RPBM;
Wave->AdapterCommon->
WriteBMControlRegister (m_ulBDAddr + X_CR, RegisterValue);
//
// DMA_ENGINE_NEED_START transitions to DMA_ENGINE_RESET.
// DMA_ENGINE_ON transitions to DMA_ENGINE_OFF.
//
DMAEngineState &= DMA_ENGINE_RESET;
return STATUS_SUCCESS;
}
/*****************************************************************************
* CMiniportWaveICHStream::ResumeDMA
*****************************************************************************
* This routine sets the Run/Pause bit for the particular DMA engine to resume
* it after it's been paused. This assumes that DMA registers content have
* been preserved.
* You need to have the spin lock "MapLock" acquired.
*/
NTSTATUS CMiniportWaveICHStream::ResumeDMA (void)
{
DOUT (DBG_PRINT, ("ResumeDMA"));
//
// Before we can turn on the DMA engine the first time, we need to check
// if we have at least 2 mappings in the scatter gather table.
//
if ((DMAEngineState & DMA_ENGINE_RESET) && (stBDList.nBDEntries < 2))
{
//
// That won't work. Set engine state to DMA_ENGINE_NEED_START so that
// we don't forget to call here regularly.
//
DMAEngineState = DMA_ENGINE_NEED_START;
return STATUS_SUCCESS;
}
//
// Turn DMA engine on by setting the RPBM bit to 1. Don't do anything to
// the registers.
//
UCHAR RegisterValue = Wave->AdapterCommon->
ReadBMControlRegister8 (m_ulBDAddr + X_CR) | CR_RPBM;
Wave->AdapterCommon->
WriteBMControlRegister (m_ulBDAddr + X_CR, RegisterValue);
//
// Set the DMA engine state.
//
DMAEngineState = DMA_ENGINE_ON;
return STATUS_SUCCESS;
}