1 /********************************************************************************
2 **    Copyright (c) 1998-2000 Microsoft Corporation. All Rights Reserved.
3 **
4 **       Portions Copyright (c) 1998-1999 Intel Corporation
5 **
6 ********************************************************************************/
7 
8 /* The file rtstream.cpp was reviewed by LCA in June 2011 and is acceptable for use by Microsoft. */
9 
10 // Every debug output has "Modulname text"
11 #define STR_MODULENAME "AC97 RT Stream: "
12 
13 #include "rtminiport.h"
14 #include "rtstream.h"
15 
16 #if (NTDDI_VERSION >= NTDDI_VISTA)
17 
18 /*****************************************************************************
19  * General Info
20  *****************************************************************************
21  * To protect the stBDList structure that is used to store mappings, we use a
22  * spin lock called MapLock. This spin lock is also acquired when we change
23  * the DMA registers. Normally, changes in stBDList and the DMA registers go
24  * hand in hand. In case we only want to change the DMA registers, we need
25  * to acquire the spin lock!
26  */
27 
28 #ifdef _MSC_VER
29 #pragma code_seg("PAGE")
30 #endif
31 /*****************************************************************************
32  * CreateAC97MiniportWaveRTStream
33  *****************************************************************************
34  * Creates a wave miniport stream object for the AC97 audio adapter. This is
35  * (nearly) like the macro STD_CREATE_BODY_ from STDUNK.H.
36  */
CreateAC97MiniportWaveRTStream(OUT CAC97MiniportWaveRTStream ** RTStream)37 NTSTATUS CreateAC97MiniportWaveRTStream
38 (
39     OUT CAC97MiniportWaveRTStream  **RTStream
40 )
41 {
42     PAGED_CODE ();
43 
44     DOUT (DBG_PRINT, ("[CreateAC97MiniportWaveRTStream]"));
45 
46     //
47     // This is basically like the macro at stdunk with the change that we
48     // don't cast to interface unknown but to interface CAC97MiniportWaveRTStream.
49     //
50     *RTStream = new (NonPagedPool, PoolTag) CAC97MiniportWaveRTStream (NULL);
51     if (*RTStream)
52     {
53         (*RTStream)->AddRef ();
54         return STATUS_SUCCESS;
55     }
56 
57     return STATUS_INSUFFICIENT_RESOURCES;
58 }
59 
60 
61 /*****************************************************************************
62  * CAC97MiniportWaveRTStream::~CAC97MiniportWaveRTStream
63  *****************************************************************************
64  * Destructor
65  */
~CAC97MiniportWaveRTStream()66 CAC97MiniportWaveRTStream::~CAC97MiniportWaveRTStream ()
67 {
68     PAGED_CODE ();
69 
70 
71     DOUT (DBG_PRINT, ("[CAC97MiniportWaveRTStream::~CAC97MiniportWaveRTStream]"));
72 
73     //
74     // Delete the scatter gather list since it's not needed anymore
75     //
76     if (BDListMdl && BDList)
77     {
78         PortStream->UnmapAllocatedPages (BDList, BDListMdl);
79         PortStream->FreePagesFromMdl (BDListMdl);
80         BDListMdl = NULL;
81         BDList = NULL;
82     }
83     if (BDList)
84     {
85         ExFreePool (BDList);
86     }
87 
88     //
89     // Release the port stream.
90     //
91     if (PortStream)
92     {
93         PortStream->Release ();
94         PortStream = NULL;
95     }
96 }
97 
98 
99 /*****************************************************************************
100  * CAC97MiniportWaveRTStream::Init
101  *****************************************************************************
102  * This routine initializes the stream object & allocates the BDL.
103  * It doesn't allocate the audio buffer or initialize the BDL.
104  */
Init(IN CAC97MiniportWaveRT * Miniport_,IN PPORTWAVERTSTREAM PortStream_,IN ULONG Channel_,IN BOOLEAN Capture_,IN PKSDATAFORMAT DataFormat_)105 NTSTATUS CAC97MiniportWaveRTStream::Init
106 (
107     IN  CAC97MiniportWaveRT     *Miniport_,
108     IN  PPORTWAVERTSTREAM    PortStream_,
109     IN  ULONG            Channel_,
110     IN  BOOLEAN          Capture_,
111     IN  PKSDATAFORMAT    DataFormat_
112 )
113 {
114     PAGED_CODE ();
115 
116     DOUT (DBG_PRINT, ("[CAC97MiniportWaveRTStream::Init]"));
117 
118     ASSERT (Miniport_);
119     ASSERT (PortStream_);
120     ASSERT (DataFormat_);
121 
122     //
123     // The rule here is that we return when we fail without a cleanup.
124     // The destructor will relase the allocated memory.
125     //
126 
127     //
128     // Allocate memory for the BDL.
129     // First try the least expensive way, which is to allocate it from the pool.
130     // If that fails (it's outside of the controller's address range which can
131     // happen on 64bit machines or PAE) then use portcls's AllocatePagesForMdl.
132     //
133     BDList = (tBDEntry *)ExAllocatePoolWithTag (NonPagedPool,
134                         MAX_BDL_ENTRIES * sizeof (tBDEntry), PoolTag);
135     if (!BDList)
136     {
137         DOUT (DBG_ERROR, ("Failed to allocate the BD list!"));
138         return STATUS_INSUFFICIENT_RESOURCES;
139     }
140 
141     //
142     // Check to see if our HW can access it.
143     // If the HW cannot see the memory, free it and use AllocatePagesForMdl
144     // which allocates always complete pages, so we have to waste some memory.
145     //
146     if (MmGetPhysicalAddress (BDList).HighPart != 0)
147     {
148       PHYSICAL_ADDRESS  high;
149 
150       high.HighPart = 0;
151       high.LowPart = MAXULONG;
152       ExFreePool (BDList);
153       BDListMdl = PortStream->AllocatePagesForMdl (high, PAGE_SIZE);
154       if (!BDListMdl)
155       {
156           DOUT (DBG_ERROR, ("Failed to allocate page for BD list!"));
157           return STATUS_INSUFFICIENT_RESOURCES;
158       }
159       BDList = (tBDEntry *)PortStream->MapAllocatedPages (BDListMdl, MmCached);
160       if (!BDList)
161       {
162           PortStream->FreePagesFromMdl (BDListMdl);
163           BDListMdl = NULL;
164           DOUT (DBG_ERROR, ("Failed to map the page for the BD list!"));
165           return STATUS_INSUFFICIENT_RESOURCES;
166       }
167     }
168 
169 
170     return CMiniportStream::Init(Miniport_,
171                                  Channel_,
172                                  Capture_,
173                                  DataFormat_,
174                                  NULL);
175 }
176 
177 
178 /*****************************************************************************
179  * CAC97MiniportWaveRTStream::AllocateAudioBuffer
180  *****************************************************************************
181  * This functions allocates an audio buffer of the size specified and maps
182  * it into the scatter gather table of the AC97 DMA engine.
183  * Once audio is played the driver only changes the last valid index to make
184  * the DMA cycle through this buffer over and over again.
185  * The buffer needs to be freed when the stream gets destroyed.
186  */
STDMETHODIMP_(NTSTATUS)187 STDMETHODIMP_(NTSTATUS) CAC97MiniportWaveRTStream::AllocateAudioBuffer
188 (
189     _In_  ULONG               size,
190     _Out_ PMDL                *userModeBuffer,
191     _Out_ ULONG               *bufferSize,
192     _Out_ ULONG               *bufferOffset,
193     _Out_ MEMORY_CACHING_TYPE *cacheType
194 )
195 {
196     PAGED_CODE ();
197 
198     //
199     // Make sure complete samples fit into the buffer.
200     //
201     if( size <= size % (NumberOfChannels * 2) )
202     {
203         return STATUS_UNSUCCESSFUL;
204     }
205     size -= size % (NumberOfChannels * 2);
206 
207     //
208     // Validate that we're going to actually allocate a real amount of memory.
209     //
210     if (0 == size)
211     {
212         DOUT (DBG_WARNING, ("Zero byte memory allocation attempted."));
213         return STATUS_UNSUCCESSFUL;
214     }
215 
216     //
217     // Allocate the buffer.
218     // The AC97 has problems playing 6ch data on page breaks (a page is 4096 bytes
219     // and doesn't contain complete samples of 6ch 16bit audio data). We therefore
220     // allocate contiguous memory that fits complete 6ch 16bit samples, however,
221     // contiguous memory is a lot more expensive to get and you might not get it
222     // at all. Useing non-contiguous memory (AllocatePagesForMdl) is therefore much
223     // better if your HW does support it. It is highly recommended to build future
224     // HW so that it can map a variable amount of pages, that it can cycle through
225     // the scatter gather list automatically and that it handles that case where
226     // samples are "split" between 2 pages.
227     //
228     PHYSICAL_ADDRESS    low;
229     PHYSICAL_ADDRESS    high;
230 
231     low.QuadPart = 0;
232     high.HighPart = 0, high.LowPart = MAXULONG;
233     PMDL audioBufferMdl = PortStream->AllocateContiguousPagesForMdl (low, high, size);
234 
235     //
236     // Check if the allocation was successful.
237     //
238     if (!audioBufferMdl)
239     {
240         DOUT (DBG_WARNING, ("[AllocateAudioBuffer] Can not allocate RT buffer."));
241         return STATUS_UNSUCCESSFUL;
242     }
243 
244     //
245     // We got our memory. Program the BDL (scatter gather list) now.
246     //
247     //
248     // Note that when you use AllocatePagesForMdl that you might get less memory
249     // back. In this case you need to check the byte count of the Mdl and continue
250     // with that size.
251     //
252 
253     //
254     // Store the information for portcls so that the buffer can be mapped to
255     // the client.
256     //
257     *userModeBuffer = audioBufferMdl;
258     *bufferSize = size;
259     *bufferOffset = 0;
260     *cacheType = MmCached;
261 
262     //
263     // Program the BDL
264     //
265     for (UINT loop = 0; loop < MAX_BDL_ENTRIES; loop++)
266     {
267         BDList[loop].dwPtrToPhyAddress = PortStream->GetPhysicalPageAddress (audioBufferMdl, 0).LowPart;
268         BDList[loop].wLength = (WORD)size/2;
269         if ((loop == MAX_BDL_ENTRIES / 2) || (loop == MAX_BDL_ENTRIES - 1))
270             BDList[loop].wPolicyBits = IOC_ENABLE;
271         else
272             BDList[loop].wPolicyBits = 0;
273     }
274 
275     return STATUS_SUCCESS;
276 }
277 
278 /*****************************************************************************
279  * CAC97MiniportWaveRTStream::FreeAudioBuffer
280  *****************************************************************************
281  * This functions frees the previously allocated audio buffer. We don't do
282  * anything special here. This callback is mainly in the case you would have
283  * to reprogram HW or do something fancy with it. In our case we just delete
284  * the audio buffer MDL and the scatter gather list we allocated.
285  */
286 _Use_decl_annotations_
STDMETHODIMP_(VOID)287 STDMETHODIMP_(VOID) CAC97MiniportWaveRTStream::FreeAudioBuffer
288 (
289       PMDL                Mdl,
290       ULONG               Size
291 )
292 {
293     PAGED_CODE ();
294 
295     UNREFERENCED_PARAMETER(Size);
296 
297     //
298     // Just delete the MDL that was allocated with AllocateContiguousPagesForMdl.
299     //
300     if (NULL != Mdl)
301     {
302         PortStream->FreePagesFromMdl (Mdl);
303     }
304 }
305 
306 /*****************************************************************************
307  * CAC97MiniportWaveRTStream::GetHWLatency
308  *****************************************************************************
309  * Returns the HW latency of the controller + codec.
310  */
STDMETHODIMP_(void)311 STDMETHODIMP_(void) CAC97MiniportWaveRTStream::GetHWLatency
312 (
313     _Out_ PKSRTAUDIO_HWLATENCY    hwLatency
314 )
315 {
316     PAGED_CODE ();
317 
318     hwLatency->FifoSize = 32;       // 32 bytes I think
319     hwLatency->ChipsetDelay = 0;    // PCI
320     hwLatency->CodecDelay = 4;      // Wild guess. Take maximum.
321 }
322 
323 /*****************************************************************************
324  * CAC97MiniportWaveRTStream::GetPositionRegister
325  *****************************************************************************
326  * We can't support this property b/c we don't have a memory mapped position
327  * register.
328  */
STDMETHODIMP_(NTSTATUS)329 STDMETHODIMP_(NTSTATUS) CAC97MiniportWaveRTStream::GetPositionRegister
330 (
331     _Out_ PKSRTAUDIO_HWREGISTER   hwRegister
332 )
333 {
334     PAGED_CODE ();
335 
336     UNREFERENCED_PARAMETER(hwRegister);
337 
338     return STATUS_UNSUCCESSFUL;
339 }
340 
341 /*****************************************************************************
342  * CAC97MiniportWaveRTStream::GetClockRegister
343  *****************************************************************************
344  * We can't support this property b/c we don't have a memory mapped clock
345  * register.
346  */
STDMETHODIMP_(NTSTATUS)347 STDMETHODIMP_(NTSTATUS) CAC97MiniportWaveRTStream::GetClockRegister
348 (
349     _Out_ PKSRTAUDIO_HWREGISTER   hwRegister
350 )
351 {
352     PAGED_CODE ();
353 
354     UNREFERENCED_PARAMETER(hwRegister);
355 
356     return STATUS_UNSUCCESSFUL;
357 }
358 
359 
360 /*****************************************************************************
361  * Non paged code begins here
362  *****************************************************************************
363  */
364 
365 #ifdef _MSC_VER
366 #pragma code_seg()
367 #endif
368 
369 
370 
371 /*****************************************************************************
372  * CAC97MiniportWaveRTStream::GetPosition
373  *****************************************************************************
374  * Gets the stream position. This is a byte count of the current position of
375  * a stream running on a particular DMA engine.  We must return a sample
376  * accurate count or the WaveDrv32 wave drift tests (35.2 & 36.2) will fail.
377  *
378  * The position is the sum of three parts:
379  *     1) The total number of bytes in released buffers
380  *     2) The position in the current buffer.
381  *     3) The total number of bytes in played but not yet released buffers
382  */
STDMETHODIMP_(NTSTATUS)383 STDMETHODIMP_(NTSTATUS) CAC97MiniportWaveRTStream::GetPosition
384 (
385     _Out_ PKSAUDIO_POSITION   Position
386 )
387 {
388     UCHAR   nCurrentIndex = 0;
389     DWORD   bufferPos;
390 
391     ASSERT (Position);
392 
393     if (DMAEngineState == DMA_ENGINE_OFF)
394     {
395         Position->PlayOffset = 0;
396         Position->WriteOffset = 0;
397     }
398     else
399     {
400         nCurrentIndex = GetBuffPos(&bufferPos);
401         Position->PlayOffset = bufferPos;
402         Position->WriteOffset = bufferPos + NumberOfChannels * 2 * 8;
403     }
404 
405     return STATUS_SUCCESS;
406 }
407 
408 +/*****************************************************************************
409 + * Non paged code begins here
410 + *****************************************************************************
411 + */
412 
413 #ifdef _MSC_VER
414 #pragma code_seg()
415 #endif
416 
417 void CMiniportWaveICHStream::InterruptServiceRoutine()
418 {
419     //
420     // Update the LVI so that we cycle around in the scatter gather list.
421     //
422     UpdateLviCyclic();
423 }
424 
425 #endif          // (NTDDI_VERSION >= NTDDI_VISTA)
426