VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/HDAStreamPeriod.cpp@ 87758

Last change on this file since 87758 was 87758, checked in by vboxsync, 4 years ago

Audio/HDA: Lots more code in the hope to resolve issue ticketoem2ref:36, namely:

  • Decoupled async I/O timing from guest driver-specific DMA timing to further reduce EMT workload.
  • Added data transfer heuristics (based on set-up DMA buffers) to detect Windows 10 guests (enabled by default).
  • Also expose and support 16kHz + 22,5kHz streams (16-bit signed).

Only tested on Win10 20H2 and various Ubuntu guests so far.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.8 KB
Line 
1/* $Id: HDAStreamPeriod.cpp 87758 2021-02-15 12:14:09Z vboxsync $ */
2/** @file
3 * HDAStreamPeriod.cpp - Stream period functions for HD Audio.
4 *
5 * Utility functions for handling HDA audio stream periods. Stream period
6 * handling is needed in order to keep track of a stream's timing
7 * and processed audio data.
8 *
9 * As the HDA device only has one bit clock (WALCLK) but audio streams can be
10 * processed at certain points in time, these functions can be used to estimate
11 * and schedule the wall clock (WALCLK) for all streams accordingly.
12 */
13
14/*
15 * Copyright (C) 2017-2020 Oracle Corporation
16 *
17 * This file is part of VirtualBox Open Source Edition (OSE), as
18 * available from http://www.virtualbox.org. This file is free software;
19 * you can redistribute it and/or modify it under the terms of the GNU
20 * General Public License (GPL) as published by the Free Software
21 * Foundation, in version 2 as it comes in the "COPYING" file of the
22 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
23 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
24 */
25
26
27/*********************************************************************************************************************************
28* Header Files *
29*********************************************************************************************************************************/
30#define LOG_GROUP LOG_GROUP_DEV_HDA
31#include <VBox/log.h>
32
33#include <iprt/asm-math.h> /* For ASMMultU64ByU32DivByU32(). */
34
35#include <VBox/vmm/pdmdev.h>
36#include <VBox/vmm/pdmaudioifs.h>
37
38#include "DrvAudio.h"
39#include "HDAStreamPeriod.h"
40
41
42#ifdef IN_RING3 /* entire file currently */
43
44/**
45 * Creates a stream period.
46 *
47 * @return IPRT status code.
48 * @param pPeriod Stream period to initialize.
49 */
50int hdaR3StreamPeriodCreate(PHDASTREAMPERIOD pPeriod)
51{
52 Assert(!(pPeriod->fStatus & HDASTREAMPERIOD_F_VALID));
53
54# ifdef HDA_STREAM_PERIOD_WITH_LOCKING
55 int rc = RTCritSectInit(&pPeriod->CritSect);
56 AssertRCReturnStmt(rc, pPeriod->fStatus = 0, rc);
57# endif
58 pPeriod->fStatus = HDASTREAMPERIOD_F_VALID;
59
60 return VINF_SUCCESS;
61}
62
63/**
64 * Destroys a formerly created stream period.
65 *
66 * @param pPeriod Stream period to destroy.
67 */
68void hdaR3StreamPeriodDestroy(PHDASTREAMPERIOD pPeriod)
69{
70 if (pPeriod->fStatus & HDASTREAMPERIOD_F_VALID)
71 {
72# ifdef HDA_STREAM_PERIOD_WITH_LOCKING
73 RTCritSectDelete(&pPeriod->CritSect);
74# endif
75 pPeriod->fStatus = HDASTREAMPERIOD_F_NONE;
76 }
77}
78
79/**
80 * Initializes a given stream period with needed parameters.
81 *
82 * @return VBox status code.
83 * @param pPeriod Stream period to (re-)initialize. Must be created with hdaR3StreamPeriodCreate() first.
84 * @param u8SD Stream descriptor (serial data #) number to assign this stream period to.
85 * @param u16LVI The HDA stream's LVI value to use for the period calculation.
86 * @param u32CBL The HDA stream's CBL value to use for the period calculation.
87 * @param pStreamCfg Audio stream configuration to use for this period.
88 */
89int hdaR3StreamPeriodInit(PHDASTREAMPERIOD pPeriod,
90 uint8_t u8SD, uint16_t u16LVI, uint32_t u32CBL, PPDMAUDIOSTREAMCFG pStreamCfg)
91{
92 if ( !u16LVI
93 || !u32CBL
94 || !DrvAudioHlpPCMPropsAreValid(&pStreamCfg->Props))
95 {
96 return VERR_INVALID_PARAMETER;
97 }
98
99 /*
100 * Linux guests (at least Ubuntu):
101 * 17632 bytes (CBL) / 4 (frame size) = 4408 frames / 4 (LVI) = 1102 frames per period
102 *
103 * Windows guests (Win10 AU):
104 * 3584 bytes (CBL) / 4 (frame size) = 896 frames / 2 (LVI) = 448 frames per period
105 */
106 unsigned cTotalPeriods = u16LVI + 1;
107
108 if (cTotalPeriods <= 1)
109 cTotalPeriods = 2; /* At least two periods *must* be present (LVI >= 1). */
110
111 uint32_t cFramesToTransfer =
112 (u32CBL / (pStreamCfg->Props.cbSample * pStreamCfg->Props.cChannels /* Frame size */)) / cTotalPeriods;
113
114 pPeriod->u8SD = u8SD;
115 pPeriod->u64StartWalClk = 0;
116 pPeriod->u32Hz = pStreamCfg->Props.uHz;
117 pPeriod->u64DurationWalClk = hdaR3StreamPeriodFramesToWalClk(pPeriod, cFramesToTransfer);
118 pPeriod->u64ElapsedWalClk = 0;
119 pPeriod->i64DelayWalClk = 0;
120 pPeriod->cFramesToTransfer = cFramesToTransfer;
121 pPeriod->cFramesTransferred = 0;
122 pPeriod->cIntPending = 0;
123
124 Log3Func(("[SD%RU8] %RU64 long, Hz=%RU32, CBL=%RU32, LVI=%RU16 -> %u periods, %RU32 frames each\n",
125 pPeriod->u8SD, pPeriod->u64DurationWalClk, pPeriod->u32Hz, u32CBL, u16LVI,
126 cTotalPeriods, pPeriod->cFramesToTransfer));
127
128 return VINF_SUCCESS;
129}
130
131/**
132 * Resets a stream period to its initial state.
133 *
134 * @param pPeriod Stream period to reset.
135 */
136void hdaR3StreamPeriodReset(PHDASTREAMPERIOD pPeriod)
137{
138 Log3Func(("[SD%RU8]\n", pPeriod->u8SD));
139
140 if (pPeriod->cIntPending)
141 LogRelMax(50, ("HDA: Warning: %RU8 interrupts for stream #%RU8 still pending -- so a period reset might trigger audio hangs\n",
142 pPeriod->cIntPending, pPeriod->u8SD));
143
144 pPeriod->fStatus &= ~HDASTREAMPERIOD_F_ACTIVE;
145 pPeriod->u64StartWalClk = 0;
146 pPeriod->u64ElapsedWalClk = 0;
147 pPeriod->cFramesTransferred = 0;
148 pPeriod->cIntPending = 0;
149# ifdef LOG_ENABLED
150 pPeriod->Dbg.tsStartNs = 0;
151# endif
152}
153
154/**
155 * Begins a new period life span of a given period.
156 *
157 * @return IPRT status code.
158 * @param pPeriod Stream period to begin new life span for.
159 * @param u64WalClk Wall clock (WALCLK) value to set for the period's starting point.
160 */
161int hdaR3StreamPeriodBegin(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk)
162{
163 Assert(!(pPeriod->fStatus & HDASTREAMPERIOD_F_ACTIVE)); /* No nested calls. */
164
165 pPeriod->fStatus |= HDASTREAMPERIOD_F_ACTIVE;
166 pPeriod->u64StartWalClk = u64WalClk;
167 pPeriod->u64ElapsedWalClk = 0;
168 pPeriod->cFramesTransferred = 0;
169 pPeriod->cIntPending = 0;
170# ifdef LOG_ENABLED
171 pPeriod->Dbg.tsStartNs = RTTimeNanoTS();
172# endif
173
174 Log3Func(("[SD%RU8] Starting @ %RU64 (%RU64 long)\n", pPeriod->u8SD, pPeriod->u64StartWalClk, pPeriod->u64DurationWalClk));
175 return VINF_SUCCESS;
176}
177
178/**
179 * Ends a formerly begun period life span.
180 *
181 * @param pPeriod Stream period to end life span for.
182 */
183void hdaR3StreamPeriodEnd(PHDASTREAMPERIOD pPeriod)
184{
185 Log3Func(("[SD%RU8] Took %zuus\n", pPeriod->u8SD, (RTTimeNanoTS() - pPeriod->Dbg.tsStartNs) / 1000));
186
187 if (!(pPeriod->fStatus & HDASTREAMPERIOD_F_ACTIVE))
188 return;
189
190 /* Sanity. */
191 AssertMsg(pPeriod->cIntPending == 0,
192 ("%RU8 interrupts for stream #%RU8 still pending -- so ending a period might trigger audio hangs\n",
193 pPeriod->cIntPending, pPeriod->u8SD));
194 Assert(hdaR3StreamPeriodIsComplete(pPeriod));
195
196 pPeriod->fStatus &= ~HDASTREAMPERIOD_F_ACTIVE;
197}
198
199/**
200 * Pauses a period. All values remain intact.
201 *
202 * @param pPeriod Stream period to pause.
203 */
204void hdaR3StreamPeriodPause(PHDASTREAMPERIOD pPeriod)
205{
206 AssertMsg((pPeriod->fStatus & HDASTREAMPERIOD_F_ACTIVE), ("Period %p already in inactive state\n", pPeriod));
207
208 pPeriod->fStatus &= ~HDASTREAMPERIOD_F_ACTIVE;
209
210 Log3Func(("[SD%RU8]\n", pPeriod->u8SD));
211}
212
213/**
214 * Resumes a formerly paused period.
215 *
216 * @param pPeriod Stream period to resume.
217 */
218void hdaR3StreamPeriodResume(PHDASTREAMPERIOD pPeriod)
219{
220 AssertMsg(!(pPeriod->fStatus & HDASTREAMPERIOD_F_ACTIVE), ("Period %p already in active state\n", pPeriod));
221
222 pPeriod->fStatus |= HDASTREAMPERIOD_F_ACTIVE;
223
224 Log3Func(("[SD%RU8]\n", pPeriod->u8SD));
225}
226
227/**
228 * Locks a stream period for serializing access.
229 *
230 * @returns IPRT status code (safe to ignore, asserted).
231 * @param pPeriod Stream period to lock.
232 */
233int hdaR3StreamPeriodLock(PHDASTREAMPERIOD pPeriod)
234{
235# ifdef HDA_STREAM_PERIOD_WITH_LOCKING
236 int rc = RTCritSectEnter(&pPeriod->CritSect);
237 AssertRC(rc);
238 return rc;
239# else
240 RT_NOREF(pPeriod);
241 return VINF_SUCCESS;
242# endif
243}
244
245/**
246 * Unlocks a formerly locked stream period.
247 *
248 * @param pPeriod Stream period to unlock.
249 */
250void hdaR3StreamPeriodUnlock(PHDASTREAMPERIOD pPeriod)
251{
252# ifdef HDA_STREAM_PERIOD_WITH_LOCKING
253 int rc2 = RTCritSectLeave(&pPeriod->CritSect);
254 AssertRC(rc2);
255# else
256 RT_NOREF(pPeriod);
257# endif
258}
259
260/**
261 * Returns the wall clock (WALCLK) value for a given amount of stream period audio frames.
262 *
263 * @return Calculated wall clock value.
264 * @param pPeriod Stream period to calculate wall clock value for.
265 * @param uFrames Number of audio frames to calculate wall clock value for.
266 *
267 * @remark Calculation depends on the given stream period and assumes a 24 MHz wall clock counter (WALCLK).
268 */
269uint64_t hdaR3StreamPeriodFramesToWalClk(PHDASTREAMPERIOD pPeriod, uint32_t uFrames)
270{
271 /* Prevent division by zero. */
272 const uint32_t uHz = pPeriod->u32Hz ? pPeriod->u32Hz : 1;
273
274 /* 24 MHz wall clock (WALCLK): 42ns resolution. */
275 return ASMMultU64ByU32DivByU32(uFrames, 24000000, uHz);
276}
277
278/**
279 * Returns the absolute wall clock (WALCLK) value for the already elapsed time of
280 * a given stream period.
281 *
282 * @return Absolute elapsed time as wall clock (WALCLK) value.
283 * @param pPeriod Stream period to use.
284 */
285uint64_t hdaR3StreamPeriodGetAbsElapsedWalClk(PHDASTREAMPERIOD pPeriod)
286{
287 return pPeriod->u64StartWalClk
288 + pPeriod->u64ElapsedWalClk
289 + pPeriod->i64DelayWalClk;
290}
291
292/**
293 * Returns the absolute wall clock (WALCLK) value for the calculated end time of
294 * a given stream period.
295 *
296 * @return Absolute end time as wall clock (WALCLK) value.
297 * @param pPeriod Stream period to use.
298 */
299uint64_t hdaR3StreamPeriodGetAbsEndWalClk(PHDASTREAMPERIOD pPeriod)
300{
301 return pPeriod->u64StartWalClk + pPeriod->u64DurationWalClk;
302}
303
304/**
305 * Returns the remaining audio frames to process for a given stream period.
306 *
307 * @return Number of remaining audio frames to process. 0 if all were processed.
308 * @param pPeriod Stream period to return value for.
309 */
310uint32_t hdaR3StreamPeriodGetRemainingFrames(PHDASTREAMPERIOD pPeriod)
311{
312 Assert(pPeriod->cFramesToTransfer >= pPeriod->cFramesTransferred);
313 return pPeriod->cFramesToTransfer - pPeriod->cFramesTransferred;
314}
315
316/**
317 * Tells whether a given stream period has elapsed (time-wise) or not.
318 *
319 * @return true if the stream period has elapsed, false if not.
320 * @param pPeriod Stream period to get status for.
321 */
322bool hdaR3StreamPeriodHasElapsed(PHDASTREAMPERIOD pPeriod)
323{
324 return (pPeriod->u64ElapsedWalClk >= pPeriod->u64DurationWalClk);
325}
326
327/**
328 * Tells whether a given stream period has passed the given absolute wall clock (WALCLK)
329 * time or not
330 *
331 * @return true if the stream period has passed the given time, false if not.
332 * @param pPeriod Stream period to get status for.
333 * @param u64WalClk Absolute wall clock (WALCLK) time to check for.
334 */
335bool hdaR3StreamPeriodHasPassedAbsWalClk(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk)
336{
337 /* Period not in use? */
338 if (!(pPeriod->fStatus & HDASTREAMPERIOD_F_ACTIVE))
339 return true; /* ... implies that it has passed. */
340
341 if (hdaR3StreamPeriodHasElapsed(pPeriod))
342 return true; /* Period already has elapsed. */
343
344 return (pPeriod->u64StartWalClk + pPeriod->u64ElapsedWalClk) >= u64WalClk;
345}
346
347/**
348 * Tells whether a given stream period has some required interrupts pending or not.
349 *
350 * @return true if period has interrupts pending, false if not.
351 * @param pPeriod Stream period to get status for.
352 */
353bool hdaR3StreamPeriodNeedsInterrupt(PHDASTREAMPERIOD pPeriod)
354{
355 return pPeriod->cIntPending > 0;
356}
357
358/**
359 * Acquires (references) an (pending) interrupt for a given stream period.
360 *
361 * @param pPeriod Stream period to acquire interrupt for.
362 *
363 * @remark This routine does not do any actual interrupt processing; it only
364 * keeps track of the required (pending) interrupts for a stream period.
365 */
366void hdaR3StreamPeriodAcquireInterrupt(PHDASTREAMPERIOD pPeriod)
367{
368 uint32_t cIntPending = pPeriod->cIntPending;
369 if (cIntPending)
370 {
371 Log3Func(("[SD%RU8] Already pending\n", pPeriod->u8SD));
372 return;
373 }
374
375 pPeriod->cIntPending++;
376
377 Log3Func(("[SD%RU8] %RU32\n", pPeriod->u8SD, pPeriod->cIntPending));
378}
379
380/**
381 * Releases (dereferences) a pending interrupt.
382 *
383 * @param pPeriod Stream period to release pending interrupt for.
384 */
385void hdaR3StreamPeriodReleaseInterrupt(PHDASTREAMPERIOD pPeriod)
386{
387 Assert(pPeriod->cIntPending);
388 pPeriod->cIntPending--;
389
390 Log3Func(("[SD%RU8] %RU32\n", pPeriod->u8SD, pPeriod->cIntPending));
391}
392
393/**
394 * Adds an amount of (processed) audio frames to a given stream period.
395 *
396 * @return IPRT status code.
397 * @param pPeriod Stream period to add audio frames to.
398 * @param framesInc Audio frames to add.
399 */
400void hdaR3StreamPeriodInc(PHDASTREAMPERIOD pPeriod, uint32_t framesInc)
401{
402 pPeriod->cFramesTransferred += framesInc;
403 Assert(pPeriod->cFramesTransferred <= pPeriod->cFramesToTransfer);
404
405 pPeriod->u64ElapsedWalClk = hdaR3StreamPeriodFramesToWalClk(pPeriod, pPeriod->cFramesTransferred);
406 Assert(pPeriod->u64ElapsedWalClk <= pPeriod->u64DurationWalClk);
407
408 Log3Func(("[SD%RU8] cbTransferred=%RU32, u64ElapsedWalClk=%RU64\n",
409 pPeriod->u8SD, pPeriod->cFramesTransferred, pPeriod->u64ElapsedWalClk));
410}
411
412/**
413 * Tells whether a given stream period is considered as complete or not.
414 *
415 * @return true if stream period is complete, false if not.
416 * @param pPeriod Stream period to report status for.
417 *
418 * @remark A stream period is considered complete if it has 1) passed (elapsed) its calculated period time
419 * and 2) processed all required audio frames.
420 */
421bool hdaR3StreamPeriodIsComplete(PHDASTREAMPERIOD pPeriod)
422{
423 const bool fIsComplete = /* Has the period elapsed time-wise? */
424 hdaR3StreamPeriodHasElapsed(pPeriod)
425 /* All frames transferred? */
426 && pPeriod->cFramesTransferred >= pPeriod->cFramesToTransfer;
427# ifdef VBOX_STRICT
428 if (fIsComplete)
429 {
430 Assert(pPeriod->cFramesTransferred == pPeriod->cFramesToTransfer);
431 Assert(pPeriod->u64ElapsedWalClk == pPeriod->u64DurationWalClk);
432 }
433# endif
434
435 Log3Func(("[SD%RU8] Period %s - runtime %RU64 / %RU64 (abs @ %RU64, starts @ %RU64, ends @ %RU64), %RU8 IRQs pending\n",
436 pPeriod->u8SD,
437 fIsComplete ? "COMPLETE" : "NOT COMPLETE YET",
438 pPeriod->u64ElapsedWalClk, pPeriod->u64DurationWalClk,
439 hdaR3StreamPeriodGetAbsElapsedWalClk(pPeriod), pPeriod->u64StartWalClk,
440 hdaR3StreamPeriodGetAbsEndWalClk(pPeriod), pPeriod->cIntPending));
441
442 return fIsComplete;
443}
444
445#endif /* IN_RING3 */
446
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette