|
|
/********************************************************************************
** 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; }
|