diff options
Diffstat (limited to 'src/VBox/Devices/Audio')
42 files changed, 44413 insertions, 0 deletions
diff --git a/src/VBox/Devices/Audio/AudioMixBuffer.cpp b/src/VBox/Devices/Audio/AudioMixBuffer.cpp new file mode 100644 index 00000000..2bd220b2 --- /dev/null +++ b/src/VBox/Devices/Audio/AudioMixBuffer.cpp @@ -0,0 +1,2040 @@ +/* $Id: AudioMixBuffer.cpp $ */ +/** @file + * Audio mixing buffer for converting reading/writing audio data. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ +#define LOG_GROUP LOG_GROUP_AUDIO_MIXER_BUFFER +#include <VBox/log.h> + +#if 0 +/* + * AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA enables dumping the raw PCM data + * to a file on the host. Be sure to adjust AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH + * to your needs before using this! + */ +# define AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA +# ifdef RT_OS_WINDOWS +# define AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "c:\\temp\\" +# else +# define AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "/tmp/" +# endif +/* Warning: Enabling this will generate *huge* logs! */ +//# define AUDIOMIXBUF_DEBUG_MACROS +#endif + +#include <iprt/asm-math.h> +#include <iprt/assert.h> +#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA +# include <iprt/file.h> +#endif +#include <iprt/mem.h> +#include <iprt/string.h> /* For RT_BZERO. */ + +#ifdef VBOX_AUDIO_TESTCASE +# define LOG_ENABLED +# include <iprt/stream.h> +#endif +#include <VBox/err.h> + +#include "AudioMixBuffer.h" + +#ifndef VBOX_AUDIO_TESTCASE +# ifdef DEBUG +# define AUDMIXBUF_LOG(x) LogFlowFunc(x) +# else +# define AUDMIXBUF_LOG(x) do {} while (0) +# endif +#else /* VBOX_AUDIO_TESTCASE */ +# define AUDMIXBUF_LOG(x) RTPrintf x +#endif + +#ifdef DEBUG +DECLINLINE(void) audioMixBufDbgPrintInternal(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc); +DECL_FORCE_INLINE(bool) audioMixBufDbgValidate(PPDMAUDIOMIXBUF pMixBuf); +#endif + +/* + * Soft Volume Control + * + * The external code supplies an 8-bit volume (attenuation) value in the + * 0 .. 255 range. This represents 0 to -96dB attenuation where an input + * value of 0 corresponds to -96dB and 255 corresponds to 0dB (unchanged). + * + * Each step thus corresponds to 96 / 256 or 0.375dB. Every 6dB (16 steps) + * represents doubling the sample value. + * + * For internal use, the volume control needs to be converted to a 16-bit + * (sort of) exponential value between 1 and 65536. This is used with fixed + * point arithmetic such that 65536 means 1.0 and 1 means 1/65536. + * + * For actual volume calculation, 33.31 fixed point is used. Maximum (or + * unattenuated) volume is represented as 0x40000000; conveniently, this + * value fits into a uint32_t. + * + * To enable fast processing, the maximum volume must be a power of two + * and must not have a sign when converted to int32_t. While 0x80000000 + * violates these constraints, 0x40000000 does not. + */ + + +/** Logarithmic/exponential volume conversion table. */ +static uint32_t s_aVolumeConv[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */ + 1, 2, 2, 2, 2, 2, 2, 2, /* 15 */ + 2, 2, 2, 2, 2, 3, 3, 3, /* 23 */ + 3, 3, 3, 3, 4, 4, 4, 4, /* 31 */ + 4, 4, 5, 5, 5, 5, 5, 6, /* 39 */ + 6, 6, 6, 7, 7, 7, 8, 8, /* 47 */ + 8, 9, 9, 10, 10, 10, 11, 11, /* 55 */ + 12, 12, 13, 13, 14, 15, 15, 16, /* 63 */ + 17, 17, 18, 19, 20, 21, 22, 23, /* 71 */ + 24, 25, 26, 27, 28, 29, 31, 32, /* 79 */ + 33, 35, 36, 38, 40, 41, 43, 45, /* 87 */ + 47, 49, 52, 54, 56, 59, 61, 64, /* 95 */ + 67, 70, 73, 76, 79, 83, 87, 91, /* 103 */ + 95, 99, 103, 108, 112, 117, 123, 128, /* 111 */ + 134, 140, 146, 152, 159, 166, 173, 181, /* 119 */ + 189, 197, 206, 215, 225, 235, 245, 256, /* 127 */ + 267, 279, 292, 304, 318, 332, 347, 362, /* 135 */ + 378, 395, 412, 431, 450, 470, 490, 512, /* 143 */ + 535, 558, 583, 609, 636, 664, 693, 724, /* 151 */ + 756, 790, 825, 861, 899, 939, 981, 1024, /* 159 */ + 1069, 1117, 1166, 1218, 1272, 1328, 1387, 1448, /* 167 */ + 1512, 1579, 1649, 1722, 1798, 1878, 1961, 2048, /* 175 */ + 2139, 2233, 2332, 2435, 2543, 2656, 2774, 2896, /* 183 */ + 3025, 3158, 3298, 3444, 3597, 3756, 3922, 4096, /* 191 */ + 4277, 4467, 4664, 4871, 5087, 5312, 5547, 5793, /* 199 */ + 6049, 6317, 6597, 6889, 7194, 7512, 7845, 8192, /* 207 */ + 8555, 8933, 9329, 9742, 10173, 10624, 11094, 11585, /* 215 */ + 12098, 12634, 13193, 13777, 14387, 15024, 15689, 16384, /* 223 */ + 17109, 17867, 18658, 19484, 20347, 21247, 22188, 23170, /* 231 */ + 24196, 25268, 26386, 27554, 28774, 30048, 31379, 32768, /* 239 */ + 34219, 35734, 37316, 38968, 40693, 42495, 44376, 46341, /* 247 */ + 48393, 50535, 52773, 55109, 57549, 60097, 62757, 65536, /* 255 */ +}; + +/* Bit shift for fixed point conversion. */ +#define AUDIOMIXBUF_VOL_SHIFT 30 + +/* Internal representation of 0dB volume (1.0 in fixed point). */ +#define AUDIOMIXBUF_VOL_0DB (1 << AUDIOMIXBUF_VOL_SHIFT) + +AssertCompile(AUDIOMIXBUF_VOL_0DB <= 0x40000000); /* Must always hold. */ +AssertCompile(AUDIOMIXBUF_VOL_0DB == 0x40000000); /* For now -- when only attenuation is used. */ + + +/** + * Peeks for audio frames without any conversion done. + * This will get the raw frame data out of a mixing buffer. + * + * @return IPRT status code or VINF_AUDIO_MORE_DATA_AVAILABLE if more data is available to read. + * + * @param pMixBuf Mixing buffer to acquire audio frames from. + * @param cFramesToRead Number of audio frames to read. + * @param paFrameBuf Buffer where to store the returned audio frames. + * @param cFrameBuf Size (in frames) of the buffer to store audio frames into. + * @param pcFramesRead Returns number of read audio frames. Optional. + * + * @remark This function is not thread safe! + */ +int AudioMixBufPeek(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToRead, + PPDMAUDIOFRAME paFrameBuf, uint32_t cFrameBuf, uint32_t *pcFramesRead) +{ + AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); + AssertPtrReturn(paFrameBuf, VERR_INVALID_POINTER); + AssertReturn(cFrameBuf, VERR_INVALID_PARAMETER); + /* pcRead is optional. */ + + int rc; + + if (!cFramesToRead) + { + if (pcFramesRead) + *pcFramesRead = 0; + return VINF_SUCCESS; + } + + uint32_t cRead; + if (pMixBuf->offRead + cFramesToRead > pMixBuf->cFrames) + { + cRead = pMixBuf->cFrames - pMixBuf->offRead; + rc = VINF_AUDIO_MORE_DATA_AVAILABLE; + } + else + { + cRead = cFramesToRead; + rc = VINF_SUCCESS; + } + + if (cRead > cFrameBuf) + { + cRead = cFrameBuf; + rc = VINF_AUDIO_MORE_DATA_AVAILABLE; + } + + if (cRead) + { + memcpy(paFrameBuf, &pMixBuf->pFrames[pMixBuf->offRead], sizeof(PDMAUDIOFRAME) * cRead); + + pMixBuf->offRead = (pMixBuf->offRead + cRead) % pMixBuf->cFrames; + Assert(pMixBuf->offRead <= pMixBuf->cFrames); + pMixBuf->cUsed -= RT_MIN(cRead, pMixBuf->cUsed); + } + + if (pcFramesRead) + *pcFramesRead = cRead; + + return rc; +} + +/** + * Returns a mutable pointer to the mixing buffer's audio frame buffer for writing raw + * audio frames. + * + * @return IPRT status code. VINF_TRY_AGAIN for getting next pointer at beginning (circular). + * @param pMixBuf Mixing buffer to acquire audio frames from. + * @param cFrames Number of requested audio frames to write. + * @param ppvFrames Returns a mutable pointer to the buffer's audio frame data. + * @param pcFramesToWrite Number of available audio frames to write. + * + * @remark This function is not thread safe! + */ +int AudioMixBufPeekMutable(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFrames, + PPDMAUDIOFRAME *ppvFrames, uint32_t *pcFramesToWrite) +{ + AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); + AssertPtrReturn(ppvFrames, VERR_INVALID_POINTER); + AssertPtrReturn(pcFramesToWrite, VERR_INVALID_POINTER); + + int rc; + + if (!cFrames) + { + *pcFramesToWrite = 0; + return VINF_SUCCESS; + } + + uint32_t cFramesToWrite; + if (pMixBuf->offWrite + cFrames > pMixBuf->cFrames) + { + cFramesToWrite = pMixBuf->cFrames - pMixBuf->offWrite; + rc = VINF_TRY_AGAIN; + } + else + { + cFramesToWrite = cFrames; + rc = VINF_SUCCESS; + } + + *ppvFrames = &pMixBuf->pFrames[pMixBuf->offWrite]; + AssertPtr(ppvFrames); + + pMixBuf->offWrite = (pMixBuf->offWrite + cFramesToWrite) % pMixBuf->cFrames; + Assert(pMixBuf->offWrite <= pMixBuf->cFrames); + pMixBuf->cUsed += RT_MIN(cFramesToWrite, pMixBuf->cUsed); + + *pcFramesToWrite = cFramesToWrite; + + return rc; +} + +/** + * Clears the entire frame buffer. + * + * @param pMixBuf Mixing buffer to clear. + * + */ +void AudioMixBufClear(PPDMAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturnVoid(pMixBuf); + + if (pMixBuf->cFrames) + RT_BZERO(pMixBuf->pFrames, pMixBuf->cFrames * sizeof(PDMAUDIOFRAME)); +} + +/** + * Clears (zeroes) the buffer by a certain amount of (used) frames and + * keeps track to eventually assigned children buffers. + * + * @param pMixBuf Mixing buffer to clear. + * @param cFramesToClear Number of audio frames to clear. + */ +void AudioMixBufFinish(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToClear) +{ + AUDMIXBUF_LOG(("cFramesToClear=%RU32\n", cFramesToClear)); + AUDMIXBUF_LOG(("%s: offRead=%RU32, cUsed=%RU32\n", + pMixBuf->pszName, pMixBuf->offRead, pMixBuf->cUsed)); + + AssertStmt(cFramesToClear <= pMixBuf->cFrames, cFramesToClear = pMixBuf->cFrames); + + PPDMAUDIOMIXBUF pIter; + RTListForEach(&pMixBuf->lstChildren, pIter, PDMAUDIOMIXBUF, Node) + { + AUDMIXBUF_LOG(("\t%s: cMixed=%RU32 -> %RU32\n", + pIter->pszName, pIter->cMixed, pIter->cMixed - cFramesToClear)); + + pIter->cMixed -= RT_MIN(pIter->cMixed, cFramesToClear); + /* Note: Do not increment pIter->cUsed here, as this gets done when reading from that buffer using AudioMixBufReadXXX. */ + } + + uint32_t cClearOff; + uint32_t cClearLen; + + /* Clear end of buffer (wrap around). */ + if (cFramesToClear > pMixBuf->offRead) + { + cClearOff = pMixBuf->cFrames - (cFramesToClear - pMixBuf->offRead); + cClearLen = pMixBuf->cFrames - cClearOff; + + AUDMIXBUF_LOG(("Clearing1: %RU32 - %RU32\n", cClearOff, cClearOff + cClearLen)); + + RT_BZERO(pMixBuf->pFrames + cClearOff, cClearLen * sizeof(PDMAUDIOFRAME)); + + Assert(cFramesToClear >= cClearLen); + cFramesToClear -= cClearLen; + } + + /* Clear beginning of buffer. */ + if ( cFramesToClear + && pMixBuf->offRead) + { + Assert(pMixBuf->offRead >= cFramesToClear); + + cClearOff = pMixBuf->offRead - cFramesToClear; + cClearLen = cFramesToClear; + + Assert(cClearOff + cClearLen <= pMixBuf->cFrames); + + AUDMIXBUF_LOG(("Clearing2: %RU32 - %RU32\n", cClearOff, cClearOff + cClearLen)); + + RT_BZERO(pMixBuf->pFrames + cClearOff, cClearLen * sizeof(PDMAUDIOFRAME)); + } +} + +/** + * Destroys (uninitializes) a mixing buffer. + * + * @param pMixBuf Mixing buffer to destroy. + */ +void AudioMixBufDestroy(PPDMAUDIOMIXBUF pMixBuf) +{ + if (!pMixBuf) + return; + + AudioMixBufUnlink(pMixBuf); + + if (pMixBuf->pszName) + { + AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName)); + + RTStrFree(pMixBuf->pszName); + pMixBuf->pszName = NULL; + } + + if (pMixBuf->pRate) + { + RTMemFree(pMixBuf->pRate); + pMixBuf->pRate = NULL; + } + + if (pMixBuf->pFrames) + { + Assert(pMixBuf->cFrames); + + RTMemFree(pMixBuf->pFrames); + pMixBuf->pFrames = NULL; + } + + pMixBuf->cFrames = 0; +} + +/** + * Returns the size (in audio frames) of free audio buffer space. + * + * @return uint32_t Size (in audio frames) of free audio buffer space. + * @param pMixBuf Mixing buffer to return free size for. + */ +uint32_t AudioMixBufFree(PPDMAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + + uint32_t cFrames, cFramesFree; + if (pMixBuf->pParent) + { + /* + * As a linked child buffer we want to know how many frames + * already have been consumed by the parent. + */ + cFrames = pMixBuf->pParent->cFrames; + + Assert(pMixBuf->cMixed <= cFrames); + cFramesFree = cFrames - pMixBuf->cMixed; + } + else /* As a parent. */ + { + cFrames = pMixBuf->cFrames; + Assert(cFrames >= pMixBuf->cUsed); + cFramesFree = pMixBuf->cFrames - pMixBuf->cUsed; + } + + AUDMIXBUF_LOG(("%s: %RU32 of %RU32\n", pMixBuf->pszName, cFramesFree, cFrames)); + return cFramesFree; +} + +/** + * Returns the size (in bytes) of free audio buffer space. + * + * @return uint32_t Size (in bytes) of free audio buffer space. + * @param pMixBuf Mixing buffer to return free size for. + */ +uint32_t AudioMixBufFreeBytes(PPDMAUDIOMIXBUF pMixBuf) +{ + return AUDIOMIXBUF_F2B(pMixBuf, AudioMixBufFree(pMixBuf)); +} + +/** + * Allocates the internal audio frame buffer. + * + * @return IPRT status code. + * @param pMixBuf Mixing buffer to allocate frame buffer for. + * @param cFrames Number of audio frames to allocate. + */ +static int audioMixBufAlloc(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFrames) +{ + AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); + AssertReturn(cFrames, VERR_INVALID_PARAMETER); + + AUDMIXBUF_LOG(("%s: cFrames=%RU32\n", pMixBuf->pszName, cFrames)); + + size_t cbFrames = cFrames * sizeof(PDMAUDIOFRAME); + pMixBuf->pFrames = (PPDMAUDIOFRAME)RTMemAllocZ(cbFrames); + if (pMixBuf->pFrames) + { + pMixBuf->cFrames = cFrames; + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; +} + +#ifdef AUDIOMIXBUF_DEBUG_MACROS +# define AUDMIXBUF_MACRO_LOG(x) AUDMIXBUF_LOG(x) +#elif defined(VBOX_AUDIO_TESTCASE_VERBOSE) /* Warning: VBOX_AUDIO_TESTCASE_VERBOSE will generate huge logs! */ +# define AUDMIXBUF_MACRO_LOG(x) RTPrintf x +#else +# define AUDMIXBUF_MACRO_LOG(x) do {} while (0) +#endif + +/** + * Macro for generating the conversion routines from/to different formats. + * Be careful what to pass in/out, as most of the macros are optimized for speed and + * thus don't do any bounds checking! + * + * Note: Currently does not handle any endianness conversion yet! + */ +#define AUDMIXBUF_CONVERT(_aName, _aType, _aMin, _aMax, _aSigned, _aShift) \ + /* Clips a specific output value to a single sample value. */ \ + DECLCALLBACK(int64_t) audioMixBufClipFrom##_aName(_aType aVal) \ + { \ + /* left shifting of signed values is not defined, therefore the intermediate uint64_t cast */ \ + if (_aSigned) \ + return (int64_t) (((uint64_t) ((int64_t) aVal )) << (32 - _aShift)); \ + return (int64_t) (((uint64_t) ((int64_t) aVal - ((_aMax >> 1) + 1))) << (32 - _aShift)); \ + } \ + \ + /* Clips a single sample value to a specific output value. */ \ + DECLCALLBACK(_aType) audioMixBufClipTo##_aName(int64_t iVal) \ + { \ + if (iVal >= 0x7fffffff) \ + return _aMax; \ + if (iVal < -INT64_C(0x80000000)) \ + return _aMin; \ + \ + if (_aSigned) \ + return (_aType) (iVal >> (32 - _aShift)); \ + return ((_aType) ((iVal >> (32 - _aShift)) + ((_aMax >> 1) + 1))); \ + } \ + \ + DECLCALLBACK(uint32_t) audioMixBufConvFrom##_aName##Stereo(PPDMAUDIOFRAME paDst, const void *pvSrc, uint32_t cbSrc, \ + PCPDMAUDMIXBUFCONVOPTS pOpts) \ + { \ + _aType const *pSrc = (_aType const *)pvSrc; \ + uint32_t cFrames = RT_MIN(pOpts->cFrames, cbSrc / sizeof(_aType)); \ + AUDMIXBUF_MACRO_LOG(("cFrames=%RU32, BpS=%zu, lVol=%RU32, rVol=%RU32\n", \ + pOpts->cFrames, sizeof(_aType), pOpts->From.Volume.uLeft, pOpts->From.Volume.uRight)); \ + for (uint32_t i = 0; i < cFrames; i++) \ + { \ + paDst->i64LSample = ASMMult2xS32RetS64((int32_t)audioMixBufClipFrom##_aName(*pSrc++), pOpts->From.Volume.uLeft ) >> AUDIOMIXBUF_VOL_SHIFT; \ + paDst->i64RSample = ASMMult2xS32RetS64((int32_t)audioMixBufClipFrom##_aName(*pSrc++), pOpts->From.Volume.uRight) >> AUDIOMIXBUF_VOL_SHIFT; \ + paDst++; \ + } \ + \ + return cFrames; \ + } \ + \ + DECLCALLBACK(uint32_t) audioMixBufConvFrom##_aName##Mono(PPDMAUDIOFRAME paDst, const void *pvSrc, uint32_t cbSrc, \ + PCPDMAUDMIXBUFCONVOPTS pOpts) \ + { \ + _aType const *pSrc = (_aType const *)pvSrc; \ + const uint32_t cFrames = RT_MIN(pOpts->cFrames, cbSrc / sizeof(_aType)); \ + AUDMIXBUF_MACRO_LOG(("cFrames=%RU32, BpS=%zu, lVol=%RU32, rVol=%RU32\n", \ + cFrames, sizeof(_aType), pOpts->From.Volume.uLeft, pOpts->From.Volume.uRight)); \ + for (uint32_t i = 0; i < cFrames; i++) \ + { \ + paDst->i64LSample = ASMMult2xS32RetS64((int32_t)audioMixBufClipFrom##_aName(*pSrc), pOpts->From.Volume.uLeft) >> AUDIOMIXBUF_VOL_SHIFT; \ + paDst->i64RSample = ASMMult2xS32RetS64((int32_t)audioMixBufClipFrom##_aName(*pSrc), pOpts->From.Volume.uRight) >> AUDIOMIXBUF_VOL_SHIFT; \ + pSrc++; \ + paDst++; \ + } \ + \ + return cFrames; \ + } \ + \ + DECLCALLBACK(void) audioMixBufConvTo##_aName##Stereo(void *pvDst, PCPDMAUDIOFRAME paSrc, PCPDMAUDMIXBUFCONVOPTS pOpts) \ + { \ + PCPDMAUDIOFRAME pSrc = paSrc; \ + _aType *pDst = (_aType *)pvDst; \ + _aType l, r; \ + uint32_t cFrames = pOpts->cFrames; \ + while (cFrames--) \ + { \ + AUDMIXBUF_MACRO_LOG(("%p: l=%RI64, r=%RI64\n", pSrc, pSrc->i64LSample, pSrc->i64RSample)); \ + l = audioMixBufClipTo##_aName(pSrc->i64LSample); \ + r = audioMixBufClipTo##_aName(pSrc->i64RSample); \ + AUDMIXBUF_MACRO_LOG(("\t-> l=%RI16, r=%RI16\n", l, r)); \ + *pDst++ = l; \ + *pDst++ = r; \ + pSrc++; \ + } \ + } \ + \ + DECLCALLBACK(void) audioMixBufConvTo##_aName##Mono(void *pvDst, PCPDMAUDIOFRAME paSrc, PCPDMAUDMIXBUFCONVOPTS pOpts) \ + { \ + PCPDMAUDIOFRAME pSrc = paSrc; \ + _aType *pDst = (_aType *)pvDst; \ + uint32_t cFrames = pOpts->cFrames; \ + while (cFrames--) \ + { \ + *pDst++ = audioMixBufClipTo##_aName((pSrc->i64LSample + pSrc->i64RSample) / 2); \ + pSrc++; \ + } \ + } + +/* audioMixBufConvXXXS8: 8 bit, signed. */ +AUDMIXBUF_CONVERT(S8 /* Name */, int8_t, INT8_MIN /* Min */, INT8_MAX /* Max */, true /* fSigned */, 8 /* cShift */) +/* audioMixBufConvXXXU8: 8 bit, unsigned. */ +AUDMIXBUF_CONVERT(U8 /* Name */, uint8_t, 0 /* Min */, UINT8_MAX /* Max */, false /* fSigned */, 8 /* cShift */) +/* audioMixBufConvXXXS16: 16 bit, signed. */ +AUDMIXBUF_CONVERT(S16 /* Name */, int16_t, INT16_MIN /* Min */, INT16_MAX /* Max */, true /* fSigned */, 16 /* cShift */) +/* audioMixBufConvXXXU16: 16 bit, unsigned. */ +AUDMIXBUF_CONVERT(U16 /* Name */, uint16_t, 0 /* Min */, UINT16_MAX /* Max */, false /* fSigned */, 16 /* cShift */) +/* audioMixBufConvXXXS32: 32 bit, signed. */ +AUDMIXBUF_CONVERT(S32 /* Name */, int32_t, INT32_MIN /* Min */, INT32_MAX /* Max */, true /* fSigned */, 32 /* cShift */) +/* audioMixBufConvXXXU32: 32 bit, unsigned. */ +AUDMIXBUF_CONVERT(U32 /* Name */, uint32_t, 0 /* Min */, UINT32_MAX /* Max */, false /* fSigned */, 32 /* cShift */) + +#undef AUDMIXBUF_CONVERT + +#define AUDMIXBUF_MIXOP(_aName, _aOp) \ + static void audioMixBufOp##_aName(PPDMAUDIOFRAME paDst, uint32_t cDstFrames, \ + PPDMAUDIOFRAME paSrc, uint32_t cSrcFrames, \ + PPDMAUDIOSTREAMRATE pRate, \ + uint32_t *pcDstWritten, uint32_t *pcSrcRead) \ + { \ + AUDMIXBUF_MACRO_LOG(("cSrcFrames=%RU32, cDstFrames=%RU32\n", cSrcFrames, cDstFrames)); \ + AUDMIXBUF_MACRO_LOG(("Rate: srcOffset=%RU32, dstOffset=%RU32, dstInc=%RU32\n", \ + pRate->srcOffset, \ + (uint32_t)(pRate->dstOffset >> 32), (uint32_t)(pRate->dstInc >> 32))); \ + \ + if (pRate->dstInc == (UINT64_C(1) + UINT32_MAX)) /* No conversion needed? */ \ + { \ + uint32_t cFrames = RT_MIN(cSrcFrames, cDstFrames); \ + AUDMIXBUF_MACRO_LOG(("cFrames=%RU32\n", cFrames)); \ + for (uint32_t i = 0; i < cFrames; i++) \ + { \ + paDst[i].i64LSample _aOp paSrc[i].i64LSample; \ + paDst[i].i64RSample _aOp paSrc[i].i64RSample; \ + } \ + \ + if (pcDstWritten) \ + *pcDstWritten = cFrames; \ + if (pcSrcRead) \ + *pcSrcRead = cFrames; \ + return; \ + } \ + \ + PPDMAUDIOFRAME paSrcStart = paSrc; \ + PPDMAUDIOFRAME paSrcEnd = paSrc + cSrcFrames; \ + PPDMAUDIOFRAME paDstStart = paDst; \ + PPDMAUDIOFRAME paDstEnd = paDst + cDstFrames; \ + PDMAUDIOFRAME frameCur = { 0 }; \ + PDMAUDIOFRAME frameOut; \ + PDMAUDIOFRAME frameLast = pRate->srcFrameLast; \ + \ + while (paDst < paDstEnd) \ + { \ + Assert(paSrc <= paSrcEnd); \ + Assert(paDst <= paDstEnd); \ + if (paSrc >= paSrcEnd) \ + break; \ + \ + while (pRate->srcOffset <= (pRate->dstOffset >> 32)) \ + { \ + Assert(paSrc <= paSrcEnd); \ + frameLast = *paSrc++; \ + pRate->srcOffset++; \ + if (paSrc == paSrcEnd) \ + break; \ + } \ + \ + Assert(paSrc <= paSrcEnd); \ + if (paSrc == paSrcEnd) \ + break; \ + \ + frameCur = *paSrc; \ + \ + /* Interpolate. */ \ + int64_t iDstOffInt = pRate->dstOffset & UINT32_MAX; \ + \ + frameOut.i64LSample = (frameLast.i64LSample * ((int64_t) (INT64_C(1) << 32) - iDstOffInt) + frameCur.i64LSample * iDstOffInt) >> 32; \ + frameOut.i64RSample = (frameLast.i64RSample * ((int64_t) (INT64_C(1) << 32) - iDstOffInt) + frameCur.i64RSample * iDstOffInt) >> 32; \ + \ + paDst->i64LSample _aOp frameOut.i64LSample; \ + paDst->i64RSample _aOp frameOut.i64RSample; \ + \ + AUDMIXBUF_MACRO_LOG(("\tiDstOffInt=%RI64, l=%RI64, r=%RI64 (cur l=%RI64, r=%RI64)\n", \ + iDstOffInt, \ + paDst->i64LSample >> 32, paDst->i64RSample >> 32, \ + frameCur.i64LSample >> 32, frameCur.i64RSample >> 32)); \ + \ + paDst++; \ + pRate->dstOffset += pRate->dstInc; \ + \ + AUDMIXBUF_MACRO_LOG(("\t\tpRate->dstOffset=%RU32\n", pRate->dstOffset >> 32)); \ + \ + } \ + \ + AUDMIXBUF_MACRO_LOG(("%zu source frames -> %zu dest frames\n", paSrc - paSrcStart, paDst - paDstStart)); \ + \ + pRate->srcFrameLast = frameLast; \ + \ + AUDMIXBUF_MACRO_LOG(("pRate->srcSampleLast l=%RI64, r=%RI64\n", \ + pRate->srcFrameLast.i64LSample, pRate->srcFrameLast.i64RSample)); \ + \ + if (pcDstWritten) \ + *pcDstWritten = paDst - paDstStart; \ + if (pcSrcRead) \ + *pcSrcRead = paSrc - paSrcStart; \ + } + +/* audioMixBufOpAssign: Assigns values from source buffer to destination bufffer, overwriting the destination. */ +AUDMIXBUF_MIXOP(Assign /* Name */, = /* Operation */) +#if 0 /* unused */ +/* audioMixBufOpBlend: Blends together the values from both, the source and the destination buffer. */ +AUDMIXBUF_MIXOP(Blend /* Name */, += /* Operation */) +#endif + +#undef AUDMIXBUF_MIXOP +#undef AUDMIXBUF_MACRO_LOG + +/** Dummy conversion used when the source is muted. */ +static DECLCALLBACK(uint32_t) +audioMixBufConvFromSilence(PPDMAUDIOFRAME paDst, const void *pvSrc, uint32_t cbSrc, PCPDMAUDMIXBUFCONVOPTS pOpts) +{ + RT_NOREF(cbSrc, pvSrc); + + /* Internally zero always corresponds to silence. */ + RT_BZERO(paDst, pOpts->cFrames * sizeof(paDst[0])); + return pOpts->cFrames; +} + +/** + * Looks up the matching conversion (macro) routine for converting + * audio frames from a source format. + * + ** @todo Speed up the lookup by binding it to the actual stream state. + * + * @return PAUDMIXBUF_FN_CONVFROM Function pointer to conversion macro if found, NULL if not supported. + * @param enmFmt Audio format to lookup conversion macro for. + */ +static PFNPDMAUDIOMIXBUFCONVFROM audioMixBufConvFromLookup(PDMAUDIOMIXBUFFMT enmFmt) +{ + if (AUDMIXBUF_FMT_SIGNED(enmFmt)) + { + if (AUDMIXBUF_FMT_CHANNELS(enmFmt) == 2) + { + switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) + { + case 8: return audioMixBufConvFromS8Stereo; + case 16: return audioMixBufConvFromS16Stereo; + case 32: return audioMixBufConvFromS32Stereo; + default: return NULL; + } + } + else + { + switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) + { + case 8: return audioMixBufConvFromS8Mono; + case 16: return audioMixBufConvFromS16Mono; + case 32: return audioMixBufConvFromS32Mono; + default: return NULL; + } + } + } + else /* Unsigned */ + { + if (AUDMIXBUF_FMT_CHANNELS(enmFmt) == 2) + { + switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) + { + case 8: return audioMixBufConvFromU8Stereo; + case 16: return audioMixBufConvFromU16Stereo; + case 32: return audioMixBufConvFromU32Stereo; + default: return NULL; + } + } + else + { + switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) + { + case 8: return audioMixBufConvFromU8Mono; + case 16: return audioMixBufConvFromU16Mono; + case 32: return audioMixBufConvFromU32Mono; + default: return NULL; + } + } + } + /* not reached */ +} + +/** + * Looks up the matching conversion (macro) routine for converting + * audio frames to a destination format. + * + ** @todo Speed up the lookup by binding it to the actual stream state. + * + * @return PAUDMIXBUF_FN_CONVTO Function pointer to conversion macro if found, NULL if not supported. + * @param enmFmt Audio format to lookup conversion macro for. + */ +static PFNPDMAUDIOMIXBUFCONVTO audioMixBufConvToLookup(PDMAUDIOMIXBUFFMT enmFmt) +{ + if (AUDMIXBUF_FMT_SIGNED(enmFmt)) + { + if (AUDMIXBUF_FMT_CHANNELS(enmFmt) == 2) + { + switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) + { + case 8: return audioMixBufConvToS8Stereo; + case 16: return audioMixBufConvToS16Stereo; + case 32: return audioMixBufConvToS32Stereo; + default: return NULL; + } + } + else + { + switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) + { + case 8: return audioMixBufConvToS8Mono; + case 16: return audioMixBufConvToS16Mono; + case 32: return audioMixBufConvToS32Mono; + default: return NULL; + } + } + } + else /* Unsigned */ + { + if (AUDMIXBUF_FMT_CHANNELS(enmFmt) == 2) + { + switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) + { + case 8: return audioMixBufConvToU8Stereo; + case 16: return audioMixBufConvToU16Stereo; + case 32: return audioMixBufConvToU32Stereo; + default: return NULL; + } + } + else + { + switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt)) + { + case 8: return audioMixBufConvToU8Mono; + case 16: return audioMixBufConvToU16Mono; + case 32: return audioMixBufConvToU32Mono; + default: return NULL; + } + } + } + /* not reached */ +} + +/** + * Converts a PDM audio volume to an internal mixing buffer volume. + * + * @returns IPRT status code. + * @param pVolDst Where to store the converted mixing buffer volume. + * @param pVolSrc Volume to convert. + */ +static int audioMixBufConvVol(PPDMAUDMIXBUFVOL pVolDst, PPDMAUDIOVOLUME pVolSrc) +{ + if (!pVolSrc->fMuted) /* Only change/convert the volume value if we're not muted. */ + { + uint8_t uVolL = pVolSrc->uLeft & 0xFF; + uint8_t uVolR = pVolSrc->uRight & 0xFF; + + /** @todo Ensure that the input is in the correct range/initialized! */ + pVolDst->uLeft = s_aVolumeConv[uVolL] * (AUDIOMIXBUF_VOL_0DB >> 16); + pVolDst->uRight = s_aVolumeConv[uVolR] * (AUDIOMIXBUF_VOL_0DB >> 16); + } + + pVolDst->fMuted = pVolSrc->fMuted; + + return VINF_SUCCESS; +} + +/** + * Initializes a mixing buffer. + * + * @return IPRT status code. + * @param pMixBuf Mixing buffer to initialize. + * @param pszName Name of mixing buffer for easier identification. Optional. + * @param pProps PCM audio properties to use for the mixing buffer. + * @param cFrames Maximum number of audio frames the mixing buffer can hold. + */ +int AudioMixBufInit(PPDMAUDIOMIXBUF pMixBuf, const char *pszName, PPDMAUDIOPCMPROPS pProps, uint32_t cFrames) +{ + AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertPtrReturn(pProps, VERR_INVALID_POINTER); + + pMixBuf->pParent = NULL; + + RTListInit(&pMixBuf->lstChildren); + pMixBuf->cChildren = 0; + + pMixBuf->pFrames = NULL; + pMixBuf->cFrames = 0; + + pMixBuf->offRead = 0; + pMixBuf->offWrite = 0; + pMixBuf->cMixed = 0; + pMixBuf->cUsed = 0; + + /* Set initial volume to max. */ + pMixBuf->Volume.fMuted = false; + pMixBuf->Volume.uLeft = AUDIOMIXBUF_VOL_0DB; + pMixBuf->Volume.uRight = AUDIOMIXBUF_VOL_0DB; + + /* Prevent division by zero. + * Do a 1:1 conversion according to AUDIOMIXBUF_S2B_RATIO. */ + pMixBuf->iFreqRatio = 1 << 20; + + pMixBuf->pRate = NULL; + + pMixBuf->AudioFmt = AUDMIXBUF_AUDIO_FMT_MAKE(pProps->uHz, + pProps->cChannels, + pProps->cBytes * 8 /* Bit */, + pProps->fSigned); + + pMixBuf->pfnConvFrom = audioMixBufConvFromLookup(pMixBuf->AudioFmt); + pMixBuf->pfnConvTo = audioMixBufConvToLookup(pMixBuf->AudioFmt); + + pMixBuf->cShift = pProps->cShift; + pMixBuf->pszName = RTStrDup(pszName); + if (!pMixBuf->pszName) + return VERR_NO_MEMORY; + + AUDMIXBUF_LOG(("%s: uHz=%RU32, cChan=%RU8, cBits=%RU8, fSigned=%RTbool\n", + pMixBuf->pszName, + AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->AudioFmt), + AUDMIXBUF_FMT_CHANNELS(pMixBuf->AudioFmt), + AUDMIXBUF_FMT_BITS_PER_SAMPLE(pMixBuf->AudioFmt), + RT_BOOL(AUDMIXBUF_FMT_SIGNED(pMixBuf->AudioFmt)))); + + return audioMixBufAlloc(pMixBuf, cFrames); +} + +/** + * Returns @c true if there are any audio frames available for processing, + * @c false if not. + * + * @return bool @c true if there are any audio frames available for processing, @c false if not. + * @param pMixBuf Mixing buffer to return value for. + */ +bool AudioMixBufIsEmpty(PPDMAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, true); + + if (pMixBuf->pParent) + return (pMixBuf->cMixed == 0); + return (pMixBuf->cUsed == 0); +} + +/** + * Calculates the frequency (sample rate) ratio of mixing buffer A in relation to mixing buffer B. + * + * @returns Calculated frequency ratio. + * @param pMixBufA First mixing buffer. + * @param pMixBufB Second mixing buffer. + */ +static int64_t audioMixBufCalcFreqRatio(PPDMAUDIOMIXBUF pMixBufA, PPDMAUDIOMIXBUF pMixBufB) +{ + int64_t iRatio = ((int64_t)AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBufA->AudioFmt) << 32) + / AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBufB->AudioFmt); + + if (iRatio == 0) /* Catch division by zero. */ + iRatio = 1 << 20; /* Do a 1:1 conversion instead. */ + + return iRatio; +} + +/** + * Links an audio mixing buffer to a parent mixing buffer. A parent mixing + * buffer can have multiple children mixing buffers [1:N], whereas a child only can + * have one parent mixing buffer [N:1]. + * + * The mixing direction always goes from the child/children buffer(s) to the + * parent buffer. + * + * For guest audio output the host backend owns the parent mixing buffer, the + * device emulation owns the child/children. + * + * The audio format of each mixing buffer can vary; the internal mixing code + * then will automatically do the (needed) conversion. + * + * @return IPRT status code. + * @param pMixBuf Mixing buffer to link parent to. + * @param pParent Parent mixing buffer to use for linking. + * + * @remark Circular linking is not allowed. + */ +int AudioMixBufLinkTo(PPDMAUDIOMIXBUF pMixBuf, PPDMAUDIOMIXBUF pParent) +{ + AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pParent, VERR_INVALID_POINTER); + + AssertMsgReturn(AUDMIXBUF_FMT_SAMPLE_FREQ(pParent->AudioFmt), + ("Parent frame frequency (Hz) not set\n"), VERR_INVALID_PARAMETER); + AssertMsgReturn(AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->AudioFmt), + ("Buffer sample frequency (Hz) not set\n"), VERR_INVALID_PARAMETER); + AssertMsgReturn(pMixBuf != pParent, + ("Circular linking not allowed\n"), VERR_INVALID_PARAMETER); + + if (pMixBuf->pParent) /* Already linked? */ + { + AUDMIXBUF_LOG(("%s: Already linked to parent '%s'\n", + pMixBuf->pszName, pMixBuf->pParent->pszName)); + return VERR_ACCESS_DENIED; + } + + RTListAppend(&pParent->lstChildren, &pMixBuf->Node); + pParent->cChildren++; + + /* Set the parent. */ + pMixBuf->pParent = pParent; + + /* Calculate the frequency ratios. */ + pMixBuf->iFreqRatio = audioMixBufCalcFreqRatio(pParent, pMixBuf); + + int rc = VINF_SUCCESS; +#if 0 + uint32_t cFrames = (uint32_t)RT_MIN( ((uint64_t)pParent->cFrames << 32) + / pMixBuf->iFreqRatio, _64K /* 64K frames max. */); + if (!cFrames) + cFrames = pParent->cFrames; + + int rc = VINF_SUCCESS; + + if (cFrames != pMixBuf->cFrames) + { + AUDMIXBUF_LOG(("%s: Reallocating frames %RU32 -> %RU32\n", + pMixBuf->pszName, pMixBuf->cFrames, cFrames)); + + uint32_t cbSamples = cFrames * sizeof(PDMAUDIOSAMPLE); + Assert(cbSamples); + pMixBuf->pSamples = (PPDMAUDIOSAMPLE)RTMemRealloc(pMixBuf->pSamples, cbSamples); + if (!pMixBuf->pSamples) + rc = VERR_NO_MEMORY; + + if (RT_SUCCESS(rc)) + { + pMixBuf->cFrames = cFrames; + + /* Make sure to zero the reallocated buffer so that it can be + * used properly when blending with another buffer later. */ + RT_BZERO(pMixBuf->pSamples, cbSamples); + } + } +#endif + + if (RT_SUCCESS(rc)) + { + if (!pMixBuf->pRate) + { + /* Create rate conversion. */ + pMixBuf->pRate = (PPDMAUDIOSTREAMRATE)RTMemAllocZ(sizeof(PDMAUDIOSTREAMRATE)); + if (!pMixBuf->pRate) + return VERR_NO_MEMORY; + } + else + RT_BZERO(pMixBuf->pRate, sizeof(PDMAUDIOSTREAMRATE)); + + pMixBuf->pRate->dstInc = ((uint64_t)AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->AudioFmt) << 32) + / AUDMIXBUF_FMT_SAMPLE_FREQ(pParent->AudioFmt); + + AUDMIXBUF_LOG(("uThisHz=%RU32, uParentHz=%RU32, iFreqRatio=0x%RX64 (%RI64), uRateInc=0x%RX64 (%RU64), cFrames=%RU32 (%RU32 parent)\n", + AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->AudioFmt), + AUDMIXBUF_FMT_SAMPLE_FREQ(pParent->AudioFmt), + pMixBuf->iFreqRatio, pMixBuf->iFreqRatio, + pMixBuf->pRate->dstInc, pMixBuf->pRate->dstInc, + pMixBuf->cFrames, + pParent->cFrames)); + AUDMIXBUF_LOG(("%s (%RU32Hz) -> %s (%RU32Hz)\n", + pMixBuf->pszName, AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->AudioFmt), + pMixBuf->pParent->pszName, AUDMIXBUF_FMT_SAMPLE_FREQ(pParent->AudioFmt))); + } + + return rc; +} + +/** + * Returns number of available live frames, that is, frames that + * have been written into the mixing buffer but not have been processed yet. + * + * For a parent buffer, this simply returns the currently used number of frames + * in the buffer. + * + * For a child buffer, this returns the number of frames which have been mixed + * to the parent and were not processed by the parent yet. + * + * @return uint32_t Number of live frames available. + * @param pMixBuf Mixing buffer to return value for. + */ +uint32_t AudioMixBufLive(PPDMAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + +#ifdef RT_STRICT + uint32_t cFrames; +#endif + uint32_t cAvail; + if (pMixBuf->pParent) /* Is this a child buffer? */ + { +#ifdef RT_STRICT + /* Use the frame count from the parent, as + * pMixBuf->cMixed specifies the frame count + * in parent frames. */ + cFrames = pMixBuf->pParent->cFrames; +#endif + cAvail = pMixBuf->cMixed; + } + else + { +#ifdef RT_STRICT + cFrames = pMixBuf->cFrames; +#endif + cAvail = pMixBuf->cUsed; + } + + Assert(cAvail <= cFrames); + return cAvail; +} + +/** + * Mixes audio frames from a source mixing buffer to a destination mixing buffer. + * + * @return IPRT status code. + * VERR_BUFFER_UNDERFLOW if the source did not have enough audio data. + * VERR_BUFFER_OVERFLOW if the destination did not have enough space to store the converted source audio data. + * + * @param pDst Destination mixing buffer. + * @param pSrc Source mixing buffer. + * @param cSrcOff Offset of source audio frames to mix. + * @param cSrcFrames Number of source audio frames to mix. + * @param pcSrcMixed Number of source audio frames successfully mixed. Optional. + */ +static int audioMixBufMixTo(PPDMAUDIOMIXBUF pDst, PPDMAUDIOMIXBUF pSrc, uint32_t cSrcOff, uint32_t cSrcFrames, + uint32_t *pcSrcMixed) +{ + AssertPtrReturn(pDst, VERR_INVALID_POINTER); + AssertPtrReturn(pSrc, VERR_INVALID_POINTER); + /* pcSrcMixed is optional. */ + + AssertMsgReturn(pDst == pSrc->pParent, ("Source buffer '%s' is not a child of destination '%s'\n", + pSrc->pszName, pDst->pszName), VERR_INVALID_PARAMETER); + uint32_t cReadTotal = 0; + uint32_t cWrittenTotal = 0; + + Assert(pSrc->cMixed <= pDst->cFrames); + + Assert(pSrc->cUsed >= pDst->cMixed); + Assert(pDst->cUsed <= pDst->cFrames); + + uint32_t offSrcRead = cSrcOff; + + uint32_t offDstWrite = pDst->offWrite; + uint32_t cDstMixed = pSrc->cMixed; + + uint32_t cSrcAvail = RT_MIN(cSrcFrames, pSrc->cUsed); + uint32_t cDstAvail = pDst->cFrames - pDst->cUsed; /** @todo Use pDst->cMixed later? */ + + AUDMIXBUF_LOG(("%s (%RU32 available) -> %s (%RU32 available)\n", + pSrc->pszName, cSrcAvail, pDst->pszName, cDstAvail)); +#ifdef DEBUG + audioMixBufDbgPrintInternal(pDst, __FUNCTION__); +#endif + + if (!cSrcAvail) + return VERR_BUFFER_UNDERFLOW; + + if (!cDstAvail) + return VERR_BUFFER_OVERFLOW; + + uint32_t cSrcToRead = 0; + uint32_t cSrcRead; + + uint32_t cDstToWrite; + uint32_t cDstWritten; + + int rc = VINF_SUCCESS; + + while (cSrcAvail && cDstAvail) + { + cSrcToRead = RT_MIN(cSrcAvail, pSrc->cFrames - offSrcRead); + cDstToWrite = RT_MIN(cDstAvail, pDst->cFrames - offDstWrite); + + AUDMIXBUF_LOG(("\tSource: %RU32 @ %RU32 -> reading %RU32\n", cSrcAvail, offSrcRead, cSrcToRead)); + AUDMIXBUF_LOG(("\tDest : %RU32 @ %RU32 -> writing %RU32\n", cDstAvail, offDstWrite, cDstToWrite)); + + if ( !cDstToWrite + || !cSrcToRead) + { + break; + } + + cDstWritten = cSrcRead = 0; + + Assert(offSrcRead < pSrc->cFrames); + Assert(offSrcRead + cSrcToRead <= pSrc->cFrames); + + Assert(offDstWrite < pDst->cFrames); + Assert(offDstWrite + cDstToWrite <= pDst->cFrames); + + audioMixBufOpAssign(pDst->pFrames + offDstWrite, cDstToWrite, + pSrc->pFrames + offSrcRead, cSrcToRead, + pSrc->pRate, &cDstWritten, &cSrcRead); + + cReadTotal += cSrcRead; + cWrittenTotal += cDstWritten; + + offSrcRead = (offSrcRead + cSrcRead) % pSrc->cFrames; + offDstWrite = (offDstWrite + cDstWritten) % pDst->cFrames; + + cDstMixed += cDstWritten; + + Assert(cSrcAvail >= cSrcRead); + cSrcAvail -= cSrcRead; + + Assert(cDstAvail >= cDstWritten); + cDstAvail -= cDstWritten; + + AUDMIXBUF_LOG(("\t%RU32 read (%RU32 left @ %RU32), %RU32 written (%RU32 left @ %RU32)\n", + cSrcRead, cSrcAvail, offSrcRead, + cDstWritten, cDstAvail, offDstWrite)); + } + + pSrc->offRead = offSrcRead; + Assert(pSrc->cUsed >= cReadTotal); + pSrc->cUsed -= RT_MIN(pSrc->cUsed, cReadTotal); + + /* Note: Always count in parent frames, as the rate can differ! */ + pSrc->cMixed = RT_MIN(cDstMixed, pDst->cFrames); + + pDst->offWrite = offDstWrite; + Assert(pDst->offWrite <= pDst->cFrames); + Assert((pDst->cUsed + cWrittenTotal) <= pDst->cFrames); + pDst->cUsed += cWrittenTotal; + + /* If there are more used frames than fitting in the destination buffer, + * adjust the values accordingly. + * + * This can happen if this routine has been called too often without + * actually processing the destination buffer in between. */ + if (pDst->cUsed > pDst->cFrames) + { + LogFunc(("%s: Warning: Destination buffer used %RU32 / %RU32 frames\n", pDst->pszName, pDst->cUsed, pDst->cFrames)); + pDst->offWrite = 0; + pDst->cUsed = pDst->cFrames; + + rc = VERR_BUFFER_OVERFLOW; + } + +#ifdef DEBUG + audioMixBufDbgValidate(pSrc); + audioMixBufDbgValidate(pDst); + + Assert(pSrc->cMixed <= pDst->cFrames); +#endif + +#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA + uint32_t offRead = pDst->offRead; + + uint32_t cLeft = cWrittenTotal; + while (cLeft) + { + uint8_t auBuf[256]; + RT_ZERO(auBuf); + + Assert(sizeof(auBuf) >= 4); + Assert(sizeof(auBuf) % 4 == 0); + + uint32_t cToRead = RT_MIN(AUDIOMIXBUF_B2F(pDst, sizeof(auBuf)), RT_MIN(cLeft, pDst->cFrames - offRead)); + Assert(cToRead <= pDst->cUsed); + + PDMAUDMIXBUFCONVOPTS convOpts; + RT_ZERO(convOpts); + convOpts.cFrames = cToRead; + + pDst->pfnConvTo(auBuf, pDst->pFrames + offRead, &convOpts); + + RTFILE fh; + int rc2 = RTFileOpen(&fh, AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "mixbuf_mixto.pcm", + RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc2)) + { + RTFileWrite(fh, auBuf, AUDIOMIXBUF_F2B(pDst, cToRead), NULL); + RTFileClose(fh); + } + + offRead = (offRead + cToRead) % pDst->cFrames; + cLeft -= cToRead; + } +#endif /* AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA */ + +#ifdef DEBUG + audioMixBufDbgPrintInternal(pDst, __FUNCTION__); +#endif + + if (pcSrcMixed) + *pcSrcMixed = cReadTotal; + + AUDMIXBUF_LOG(("cReadTotal=%RU32, cWrittenTotal=%RU32, cSrcMixed=%RU32, cDstUsed=%RU32, rc=%Rrc\n", + cReadTotal, cWrittenTotal, pSrc->cMixed, pDst->cUsed, rc)); + return rc; +} + +/** + * Mixes audio frames down to the parent mixing buffer, extended version. + * + * @return IPRT status code. See audioMixBufMixTo() for a more detailed explanation. + * @param pMixBuf Source mixing buffer to mix to its parent. + * @param cSrcOffset Offset (in frames) of source mixing buffer. + * @param cSrcFrames Number of source audio frames to mix to its parent. + * @param pcSrcMixed Number of source audio frames successfully mixed. Optional. + */ +int AudioMixBufMixToParentEx(PPDMAUDIOMIXBUF pMixBuf, uint32_t cSrcOffset, uint32_t cSrcFrames, uint32_t *pcSrcMixed) +{ + AssertMsgReturn(VALID_PTR(pMixBuf->pParent), + ("Buffer is not linked to a parent buffer\n"), + VERR_INVALID_PARAMETER); + + return audioMixBufMixTo(pMixBuf->pParent, pMixBuf, cSrcOffset, cSrcFrames, pcSrcMixed); +} + +/** + * Mixes audio frames down to the parent mixing buffer. + * + * @return IPRT status code. See audioMixBufMixTo() for a more detailed explanation. + * @param pMixBuf Source mixing buffer to mix to its parent. + * @param cSrcFrames Number of source audio frames to mix to its parent. + * @param pcSrcMixed Number of source audio frames successfully mixed. Optional. + */ +int AudioMixBufMixToParent(PPDMAUDIOMIXBUF pMixBuf, uint32_t cSrcFrames, uint32_t *pcSrcMixed) +{ + return audioMixBufMixTo(pMixBuf->pParent, pMixBuf, pMixBuf->offRead, cSrcFrames, pcSrcMixed); +} + +#ifdef DEBUG +/** + * Prints a single mixing buffer. + * Internal helper function for debugging. Do not use directly. + * + * @return IPRT status code. + * @param pMixBuf Mixing buffer to print. + * @param pszFunc Function name to log this for. + * @param fIsParent Whether this is a parent buffer or not. + * @param uIdtLvl Indention level to use. + */ +DECL_FORCE_INLINE(void) audioMixBufDbgPrintSingle(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc, bool fIsParent, uint16_t uIdtLvl) +{ + Log(("%s: %*s[%s] %s: offRead=%RU32, offWrite=%RU32, cMixed=%RU32 -> %RU32/%RU32\n", + pszFunc, uIdtLvl * 4, "", fIsParent ? "PARENT" : "CHILD", + pMixBuf->pszName, pMixBuf->offRead, pMixBuf->offWrite, pMixBuf->cMixed, pMixBuf->cUsed, pMixBuf->cFrames)); +} + +/** + * Validates a single mixing buffer. + * + * @return @true if the buffer state is valid or @false if not. + * @param pMixBuf Mixing buffer to validate. + */ +DECL_FORCE_INLINE(bool) audioMixBufDbgValidate(PPDMAUDIOMIXBUF pMixBuf) +{ + //const uint32_t offReadEnd = (pMixBuf->offRead + pMixBuf->cUsed) % pMixBuf->cFrames; + //const uint32_t offWriteEnd = (pMixBuf->offWrite + (pMixBuf->cFrames - pMixBuf->cUsed)) % pMixBuf->cFrames; + + bool fValid = true; + + AssertStmt(pMixBuf->offRead <= pMixBuf->cFrames, fValid = false); + AssertStmt(pMixBuf->offWrite <= pMixBuf->cFrames, fValid = false); + AssertStmt(pMixBuf->cUsed <= pMixBuf->cFrames, fValid = false); + + if (pMixBuf->offWrite > pMixBuf->offRead) + { + if (pMixBuf->offWrite - pMixBuf->offRead != pMixBuf->cUsed) + fValid = false; + } + else if (pMixBuf->offWrite < pMixBuf->offRead) + { + if (pMixBuf->offWrite + pMixBuf->cFrames - pMixBuf->offRead != pMixBuf->cUsed) + fValid = false; + } + + if (!fValid) + { + audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__); + AssertFailed(); + } + + return fValid; +} + +/** + * Internal helper function for audioMixBufPrintChain(). + * Do not use directly. + * + * @return IPRT status code. + * @param pMixBuf Mixing buffer to print. + * @param pszFunc Function name to print the chain for. + * @param uIdtLvl Indention level to use. + * @param pcChildren Pointer to children counter. + */ +DECL_FORCE_INLINE(void) audioMixBufDbgPrintChainHelper(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc, uint16_t uIdtLvl, + size_t *pcChildren) +{ + PPDMAUDIOMIXBUF pIter; + RTListForEach(&pMixBuf->lstChildren, pIter, PDMAUDIOMIXBUF, Node) + { + audioMixBufDbgPrintSingle(pIter, pszFunc, false /* ifIsParent */, uIdtLvl + 1); + *pcChildren++; + } +} + +DECL_FORCE_INLINE(void) audioMixBufDbgPrintChainInternal(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc) +{ + PPDMAUDIOMIXBUF pParent = pMixBuf->pParent; + while (pParent) + { + if (!pParent->pParent) + break; + + pParent = pParent->pParent; + } + + if (!pParent) + pParent = pMixBuf; + + audioMixBufDbgPrintSingle(pParent, pszFunc, true /* fIsParent */, 0 /* uIdtLvl */); + + /* Recursively iterate children. */ + size_t cChildren = 0; + audioMixBufDbgPrintChainHelper(pParent, pszFunc, 0 /* uIdtLvl */, &cChildren); + + Log(("%s: Children: %zu\n", pszFunc, cChildren)); +} + +/** + * Prints statistics and status of the full chain of a mixing buffer to the logger, + * starting from the top root mixing buffer. + * For debug versions only. + * + * @return IPRT status code. + * @param pMixBuf Mixing buffer to print. + */ +void AudioMixBufDbgPrintChain(PPDMAUDIOMIXBUF pMixBuf) +{ + audioMixBufDbgPrintChainInternal(pMixBuf, __FUNCTION__); +} + +DECL_FORCE_INLINE(void) audioMixBufDbgPrintInternal(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc) +{ + PPDMAUDIOMIXBUF pParent = pMixBuf; + if (pMixBuf->pParent) + pParent = pMixBuf->pParent; + + audioMixBufDbgPrintSingle(pMixBuf, pszFunc, pParent == pMixBuf /* fIsParent */, 0 /* iIdtLevel */); + + PPDMAUDIOMIXBUF pIter; + RTListForEach(&pMixBuf->lstChildren, pIter, PDMAUDIOMIXBUF, Node) + { + if (pIter == pMixBuf) + continue; + audioMixBufDbgPrintSingle(pIter, pszFunc, false /* fIsParent */, 1 /* iIdtLevel */); + } +} + +/** + * Prints statistics and status of a mixing buffer to the logger. + * For debug versions only. + * + * @return IPRT status code. + * @param pMixBuf Mixing buffer to print. + */ +void AudioMixBufDbgPrint(PPDMAUDIOMIXBUF pMixBuf) +{ + audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__); +} +#endif /* DEBUG */ + +/** + * Returns the total number of audio frames used. + * + * @return uint32_t + * @param pMixBuf + */ +uint32_t AudioMixBufUsed(PPDMAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + return pMixBuf->cUsed; +} + +/** + * Returns the total number of bytes used. + * + * @return uint32_t + * @param pMixBuf + */ +uint32_t AudioMixBufUsedBytes(PPDMAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + return AUDIOMIXBUF_F2B(pMixBuf, pMixBuf->cUsed); +} + +/** + * Reads audio frames at a specific offset. + * + * @return IPRT status code. + * @param pMixBuf Mixing buffer to read audio frames from. + * @param offFrames Offset (in audio frames) to start reading from. + * @param pvBuf Pointer to buffer to write output to. + * @param cbBuf Size (in bytes) of buffer to write to. + * @param pcbRead Size (in bytes) of data read. Optional. + */ +int AudioMixBufReadAt(PPDMAUDIOMIXBUF pMixBuf, + uint32_t offFrames, + void *pvBuf, uint32_t cbBuf, + uint32_t *pcbRead) +{ + return AudioMixBufReadAtEx(pMixBuf, pMixBuf->AudioFmt, + offFrames, pvBuf, cbBuf, pcbRead); +} + +/** + * Reads audio frames at a specific offset. + * If the audio format of the mixing buffer and the requested audio format do + * not match the output will be converted accordingly. + * + * @return IPRT status code. + * @param pMixBuf Mixing buffer to read audio frames from. + * @param enmFmt Audio format to use for output. + * @param offFrames Offset (in audio frames) to start reading from. + * @param pvBuf Pointer to buffer to write output to. + * @param cbBuf Size (in bytes) of buffer to write to. + * @param pcbRead Size (in bytes) of data read. Optional. + */ +int AudioMixBufReadAtEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, + uint32_t offFrames, + void *pvBuf, uint32_t cbBuf, + uint32_t *pcbRead) +{ + AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + /* pcbRead is optional. */ + + uint32_t cDstFrames = pMixBuf->cFrames; + uint32_t cLive = pMixBuf->cUsed; + + uint32_t cDead = cDstFrames - cLive; + uint32_t cToProcess = (uint32_t)AUDIOMIXBUF_F2F_RATIO(pMixBuf, cDead); + cToProcess = RT_MIN(cToProcess, AUDIOMIXBUF_B2F(pMixBuf, cbBuf)); + + AUDMIXBUF_LOG(("%s: offFrames=%RU32, cLive=%RU32, cDead=%RU32, cToProcess=%RU32\n", + pMixBuf->pszName, offFrames, cLive, cDead, cToProcess)); + + int rc; + if (cToProcess) + { + PFNPDMAUDIOMIXBUFCONVTO pfnConvTo = NULL; + if (pMixBuf->AudioFmt != enmFmt) + pfnConvTo = audioMixBufConvToLookup(enmFmt); + else + pfnConvTo = pMixBuf->pfnConvTo; + + if (pfnConvTo) + { + PDMAUDMIXBUFCONVOPTS convOpts; + RT_ZERO(convOpts); + /* Note: No volume handling/conversion done in the conversion-to macros (yet). */ + + convOpts.cFrames = cToProcess; + + pfnConvTo(pvBuf, pMixBuf->pFrames + offFrames, &convOpts); + +#ifdef DEBUG + AudioMixBufDbgPrint(pMixBuf); +#endif + rc = VINF_SUCCESS; + } + else + { + AssertFailed(); + rc = VERR_NOT_SUPPORTED; + } + } + else + rc = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + { + if (pcbRead) + *pcbRead = AUDIOMIXBUF_F2B(pMixBuf, cToProcess); + } + + AUDMIXBUF_LOG(("cbRead=%RU32, rc=%Rrc\n", AUDIOMIXBUF_F2B(pMixBuf, cToProcess), rc)); + return rc; +} + +/** + * Reads audio frames. The audio format of the mixing buffer will be used. + * + * @return IPRT status code. + * @param pMixBuf Mixing buffer to read audio frames from. + * @param pvBuf Pointer to buffer to write output to. + * @param cbBuf Size (in bytes) of buffer to write to. + * @param pcBlock Returns acquired block to read (in audio frames). + */ +int AudioMixBufAcquireReadBlock(PPDMAUDIOMIXBUF pMixBuf, void *pvBuf, uint32_t cbBuf, uint32_t *pcBlock) +{ + return AudioMixBufAcquireReadBlockEx(pMixBuf, pMixBuf->AudioFmt, pvBuf, cbBuf, pcBlock); +} + +/** + * Reads audio frames in a specific audio format. + * If the audio format of the mixing buffer and the requested audio format do + * not match the output will be converted accordingly. + * + * @return IPRT status code. + * @param pMixBuf Mixing buffer to read audio frames from. + * @param enmFmt Audio format to use for output. + * @param pvBuf Pointer to buffer to write output to. + * @param cbBuf Size (in bytes) of buffer to write to. + * @param pcBlock Returns acquired block to read (in audio frames). + */ +int AudioMixBufAcquireReadBlockEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, void *pvBuf, uint32_t cbBuf, + uint32_t *pcBlock) +{ + AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pcBlock, VERR_INVALID_POINTER); + + /* Make sure that we at least have space for a full audio frame. */ + AssertReturn(AUDIOMIXBUF_B2F(pMixBuf, cbBuf), VERR_INVALID_PARAMETER); + + uint32_t cToRead = RT_MIN(pMixBuf->cUsed, AUDIOMIXBUF_B2F(pMixBuf, cbBuf)); + + AUDMIXBUF_LOG(("%s: cbBuf=%RU32 (%RU32 frames), cToRead=%RU32, fmtSrc=0x%x, fmtDst=0x%x\n", + pMixBuf->pszName, cbBuf, AUDIOMIXBUF_B2F(pMixBuf, cbBuf), cToRead, pMixBuf->AudioFmt, enmFmt)); + + if (!cToRead) + { +#ifdef DEBUG + audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__); +#endif + *pcBlock = 0; + return VINF_SUCCESS; + } + + PFNPDMAUDIOMIXBUFCONVTO pfnConvTo = NULL; + if (pMixBuf->AudioFmt != enmFmt) + pfnConvTo = audioMixBufConvToLookup(enmFmt); + else + pfnConvTo = pMixBuf->pfnConvTo; + + if (!pfnConvTo) /* Audio format not supported. */ + { + AssertFailed(); + return VERR_NOT_SUPPORTED; + } + + cToRead = RT_MIN(cToRead, pMixBuf->cFrames - pMixBuf->offRead); + if (cToRead) + { + PDMAUDMIXBUFCONVOPTS convOpts; + RT_ZERO(convOpts); + convOpts.cFrames = cToRead; + + AUDMIXBUF_LOG(("cToRead=%RU32\n", cToRead)); + + pfnConvTo(pvBuf, pMixBuf->pFrames + pMixBuf->offRead, &convOpts); + +#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA + RTFILE fh; + int rc2 = RTFileOpen(&fh, AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "mixbuf_readcirc.pcm", + RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc2)) + { + RTFileWrite(fh, pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToRead), NULL); + RTFileClose(fh); + } +#endif + } + + *pcBlock = cToRead; + +#ifdef DEBUG + audioMixBufDbgValidate(pMixBuf); +#endif + + AUDMIXBUF_LOG(("cRead=%RU32 (%RU32 bytes)\n", cToRead, AUDIOMIXBUF_F2B(pMixBuf, cToRead))); + return VINF_SUCCESS; +} + +/** + * Releases a formerly acquired read block again. + * + * @param pMixBuf Mixing buffer to release acquired read block for. + * @param cBlock Size of the block to release (in audio frames). + */ +void AudioMixBufReleaseReadBlock(PPDMAUDIOMIXBUF pMixBuf, uint32_t cBlock) +{ + AssertPtrReturnVoid(pMixBuf); + + if (!cBlock) + return; + + pMixBuf->offRead = (pMixBuf->offRead + cBlock) % pMixBuf->cFrames; + Assert(pMixBuf->cUsed >= cBlock); + pMixBuf->cUsed -= RT_MIN(cBlock, pMixBuf->cUsed); +} + +/** + * Returns the current read position of a mixing buffer. + * + * @returns IPRT status code. + * @param pMixBuf Mixing buffer to return position for. + */ +uint32_t AudioMixBufReadPos(PPDMAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + + return pMixBuf->offRead; +} + +/** + * Resets a mixing buffer. + * + * @param pMixBuf Mixing buffer to reset. + */ +void AudioMixBufReset(PPDMAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturnVoid(pMixBuf); + + AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName)); + + pMixBuf->offRead = 0; + pMixBuf->offWrite = 0; + pMixBuf->cMixed = 0; + pMixBuf->cUsed = 0; + + AudioMixBufClear(pMixBuf); +} + +/** + * Sets the overall (master) volume. + * + * @param pMixBuf Mixing buffer to set volume for. + * @param pVol Pointer to volume structure to set. + */ +void AudioMixBufSetVolume(PPDMAUDIOMIXBUF pMixBuf, PPDMAUDIOVOLUME pVol) +{ + AssertPtrReturnVoid(pMixBuf); + AssertPtrReturnVoid(pVol); + + LogFlowFunc(("%s: lVol=%RU8, rVol=%RU8, fMuted=%RTbool\n", pMixBuf->pszName, pVol->uLeft, pVol->uRight, pVol->fMuted)); + + int rc2 = audioMixBufConvVol(&pMixBuf->Volume /* Dest */, pVol /* Source */); + AssertRC(rc2); +} + +/** + * Returns the maximum amount of audio frames this buffer can hold. + * + * @return uint32_t Size (in audio frames) the mixing buffer can hold. + * @param pMixBuf Mixing buffer to retrieve maximum for. + */ +uint32_t AudioMixBufSize(PPDMAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + return pMixBuf->cFrames; +} + +/** + * Returns the maximum amount of bytes this buffer can hold. + * + * @return uint32_t Size (in bytes) the mixing buffer can hold. + * @param pMixBuf Mixing buffer to retrieve maximum for. + */ +uint32_t AudioMixBufSizeBytes(PPDMAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + return AUDIOMIXBUF_F2B(pMixBuf, pMixBuf->cFrames); +} + +/** + * Unlinks a mixing buffer from its parent, if any. + * + * @return IPRT status code. + * @param pMixBuf Mixing buffer to unlink from parent. + */ +void AudioMixBufUnlink(PPDMAUDIOMIXBUF pMixBuf) +{ + if (!pMixBuf || !pMixBuf->pszName) + return; + + AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName)); + + if (pMixBuf->pParent) /* IS this a children buffer? */ + { + AUDMIXBUF_LOG(("%s: Unlinking from parent \"%s\"\n", + pMixBuf->pszName, pMixBuf->pParent->pszName)); + + RTListNodeRemove(&pMixBuf->Node); + + /* Decrease the paren't children count. */ + Assert(pMixBuf->pParent->cChildren); + pMixBuf->pParent->cChildren--; + + /* Make sure to reset the parent mixing buffer each time it gets linked + * to a new child. */ + AudioMixBufReset(pMixBuf->pParent); + pMixBuf->pParent = NULL; + } + + PPDMAUDIOMIXBUF pChild, pChildNext; + RTListForEachSafe(&pMixBuf->lstChildren, pChild, pChildNext, PDMAUDIOMIXBUF, Node) + { + AUDMIXBUF_LOG(("\tUnlinking \"%s\"\n", pChild->pszName)); + + AudioMixBufReset(pChild); + + Assert(pChild->pParent == pMixBuf); + pChild->pParent = NULL; + + RTListNodeRemove(&pChild->Node); + + /* Decrease the children count. */ + Assert(pMixBuf->cChildren); + pMixBuf->cChildren--; + } + + Assert(RTListIsEmpty(&pMixBuf->lstChildren)); + Assert(pMixBuf->cChildren == 0); + + AudioMixBufReset(pMixBuf); + + if (pMixBuf->pRate) + { + pMixBuf->pRate->dstOffset = pMixBuf->pRate->srcOffset = 0; + pMixBuf->pRate->dstInc = 0; + } + + pMixBuf->iFreqRatio = 1; /* Prevent division by zero. */ +} + +/** + * Writes audio frames at a specific offset. + * The sample format being written must match the format of the mixing buffer. + * + * @return IPRT status code. + * @param pMixBuf Pointer to mixing buffer to write to. + * @param offFrames Offset (in frames) starting to write at. + * @param pvBuf Pointer to audio buffer to be written. + * @param cbBuf Size (in bytes) of audio buffer. + * @param pcWritten Returns number of audio frames written. Optional. + */ +int AudioMixBufWriteAt(PPDMAUDIOMIXBUF pMixBuf, uint32_t offFrames, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten) +{ + return AudioMixBufWriteAtEx(pMixBuf, pMixBuf->AudioFmt, offFrames, pvBuf, cbBuf, pcWritten); +} + +/** + * Writes audio frames at a specific offset. + * + * Note that this operation also modifies the current read and write position + * to \a offFrames + written frames on success. + * + * The audio sample format to be written can be different from the audio format + * the mixing buffer operates on. + * + * @return IPRT status code. + * @param pMixBuf Pointer to mixing buffer to write to. + * @param enmFmt Audio format supplied in the buffer. + * @param offFrames Offset (in frames) starting to write at. + * @param pvBuf Pointer to audio buffer to be written. + * @param cbBuf Size (in bytes) of audio buffer. + * @param pcWritten Returns number of audio frames written. Optional. + */ +int AudioMixBufWriteAtEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, + uint32_t offFrames, const void *pvBuf, uint32_t cbBuf, + uint32_t *pcWritten) +{ + AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + /* pcbWritten is optional. */ + + if (offFrames >= pMixBuf->cFrames) + { + if (pcWritten) + *pcWritten = 0; + return VERR_BUFFER_OVERFLOW; + } + + /* + * Adjust cToWrite so we don't overflow our buffers. + */ + uint32_t cToWrite = RT_MIN(AUDIOMIXBUF_B2F(pMixBuf, cbBuf), pMixBuf->cFrames - offFrames); + +#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA + /* + * Now that we know how much we'll be converting we can log it. + */ + RTFILE hFile; + int rc2 = RTFileOpen(&hFile, AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "mixbuf_writeat.pcm", + RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc2)) + { + RTFileWrite(hFile, pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), NULL); + RTFileClose(hFile); + } +#endif + + /* + * Pick the conversion function and do the conversion. + */ + PFNPDMAUDIOMIXBUFCONVFROM pfnConvFrom = NULL; + if (!pMixBuf->Volume.fMuted) + { + if (pMixBuf->AudioFmt != enmFmt) + pfnConvFrom = audioMixBufConvFromLookup(enmFmt); + else + pfnConvFrom = pMixBuf->pfnConvFrom; + } + else + pfnConvFrom = &audioMixBufConvFromSilence; + + int rc = VINF_SUCCESS; + + uint32_t cWritten; + if ( pfnConvFrom + && cToWrite) + { + PDMAUDMIXBUFCONVOPTS convOpts; + + convOpts.cFrames = cToWrite; + convOpts.From.Volume.fMuted = pMixBuf->Volume.fMuted; + convOpts.From.Volume.uLeft = pMixBuf->Volume.uLeft; + convOpts.From.Volume.uRight = pMixBuf->Volume.uRight; + + cWritten = pfnConvFrom(pMixBuf->pFrames + offFrames, pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), &convOpts); + } + else + { + cWritten = 0; + if (!pfnConvFrom) + { + AssertFailed(); + rc = VERR_NOT_SUPPORTED; + } + } + + AUDMIXBUF_LOG(("%s: offFrames=%RU32, cbBuf=%RU32, cToWrite=%RU32 (%zu bytes), cWritten=%RU32 (%zu bytes), rc=%Rrc\n", + pMixBuf->pszName, offFrames, cbBuf, + cToWrite, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), + cWritten, AUDIOMIXBUF_F2B(pMixBuf, cWritten), rc)); + + if (RT_SUCCESS(rc)) + { + pMixBuf->offRead = offFrames % pMixBuf->cFrames; + pMixBuf->offWrite = (offFrames + cWritten) % pMixBuf->cFrames; + pMixBuf->cUsed = cWritten; + pMixBuf->cMixed = 0; + +#ifdef DEBUG + audioMixBufDbgValidate(pMixBuf); +#endif + if (pcWritten) + *pcWritten = cWritten; + } + else + AUDMIXBUF_LOG(("%s: Failed with %Rrc\n", pMixBuf->pszName, rc)); + + return rc; +} + +/** + * Writes audio frames. + * + * The sample format being written must match the format of the mixing buffer. + * + * @return IPRT status code, or VERR_BUFFER_OVERFLOW if frames which not have + * been processed yet have been overwritten (due to cyclic buffer). + * @param pMixBuf Pointer to mixing buffer to write to. + * @param pvBuf Pointer to audio buffer to be written. + * @param cbBuf Size (in bytes) of audio buffer. + * @param pcWritten Returns number of audio frames written. Optional. + */ +int AudioMixBufWriteCirc(PPDMAUDIOMIXBUF pMixBuf, + const void *pvBuf, uint32_t cbBuf, + uint32_t *pcWritten) +{ + return AudioMixBufWriteCircEx(pMixBuf, pMixBuf->AudioFmt, pvBuf, cbBuf, pcWritten); +} + +/** + * Writes audio frames of a specific format. + * This function might write less data at once than requested. + * + * @return IPRT status code, or VERR_BUFFER_OVERFLOW no space is available for writing anymore. + * @param pMixBuf Pointer to mixing buffer to write to. + * @param enmFmt Audio format supplied in the buffer. + * @param pvBuf Pointer to audio buffer to be written. + * @param cbBuf Size (in bytes) of audio buffer. + * @param pcWritten Returns number of audio frames written. Optional. + */ +int AudioMixBufWriteCircEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten) +{ + AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + /* pcbWritten is optional. */ + + if (!cbBuf) + { + if (pcWritten) + *pcWritten = 0; + return VINF_SUCCESS; + } + + /* Make sure that we at least write a full audio frame. */ + AssertReturn(AUDIOMIXBUF_B2F(pMixBuf, cbBuf), VERR_INVALID_PARAMETER); + + Assert(pMixBuf->cFrames); + AssertPtr(pMixBuf->pFrames); + + PFNPDMAUDIOMIXBUFCONVFROM pfnConvFrom = NULL; + if (!pMixBuf->Volume.fMuted) + { + if (pMixBuf->AudioFmt != enmFmt) + pfnConvFrom = audioMixBufConvFromLookup(enmFmt); + else + pfnConvFrom = pMixBuf->pfnConvFrom; + } + else + pfnConvFrom = &audioMixBufConvFromSilence; + + if (!pfnConvFrom) + { + AssertFailed(); + return VERR_NOT_SUPPORTED; + } + + int rc = VINF_SUCCESS; + + uint32_t cWritten = 0; + + uint32_t cFree = pMixBuf->cFrames - pMixBuf->cUsed; + if (cFree) + { + if ((pMixBuf->cFrames - pMixBuf->offWrite) == 0) + pMixBuf->offWrite = 0; + + uint32_t cToWrite = RT_MIN(AUDIOMIXBUF_B2F(pMixBuf, cbBuf), RT_MIN(pMixBuf->cFrames - pMixBuf->offWrite, cFree)); + Assert(cToWrite); + + PDMAUDMIXBUFCONVOPTS convOpts; + RT_ZERO(convOpts); + + convOpts.From.Volume.fMuted = pMixBuf->Volume.fMuted; + convOpts.From.Volume.uLeft = pMixBuf->Volume.uLeft; + convOpts.From.Volume.uRight = pMixBuf->Volume.uRight; + + convOpts.cFrames = cToWrite; + + cWritten = pfnConvFrom(pMixBuf->pFrames + pMixBuf->offWrite, + pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), &convOpts); + Assert(cWritten == cToWrite); + +#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA + RTFILE fh; + RTFileOpen(&fh, AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "mixbuf_writecirc_ex.pcm", + RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + RTFileWrite(fh, pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), NULL); + RTFileClose(fh); +#endif + pMixBuf->cUsed += cWritten; + Assert(pMixBuf->cUsed <= pMixBuf->cFrames); + + pMixBuf->offWrite = (pMixBuf->offWrite + cWritten) % pMixBuf->cFrames; + Assert(pMixBuf->offWrite <= pMixBuf->cFrames); + } + else + rc = VERR_BUFFER_OVERFLOW; + +#ifdef DEBUG + audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__); + audioMixBufDbgValidate(pMixBuf); +#endif + + if (pcWritten) + *pcWritten = cWritten; + + AUDMIXBUF_LOG(("%s: enmFmt=0x%x, cbBuf=%RU32 (%RU32 frames), cWritten=%RU32, rc=%Rrc\n", + pMixBuf->pszName, enmFmt, cbBuf, AUDIOMIXBUF_B2F(pMixBuf, cbBuf), cWritten, rc)); + return rc; +} + +/** + * Returns the current write position of a mixing buffer. + * + * @returns IPRT status code. + * @param pMixBuf Mixing buffer to return position for. + */ +uint32_t AudioMixBufWritePos(PPDMAUDIOMIXBUF pMixBuf) +{ + AssertPtrReturn(pMixBuf, 0); + + return pMixBuf->offWrite; +} + diff --git a/src/VBox/Devices/Audio/AudioMixBuffer.h b/src/VBox/Devices/Audio/AudioMixBuffer.h new file mode 100644 index 00000000..f2c77688 --- /dev/null +++ b/src/VBox/Devices/Audio/AudioMixBuffer.h @@ -0,0 +1,93 @@ +/* $Id: AudioMixBuffer.h $ */ +/** @file + * Audio Mixing bufer convert audio samples to/from different rates / formats. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_AudioMixBuffer_h +#define VBOX_INCLUDED_SRC_Audio_AudioMixBuffer_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <VBox/vmm/pdmaudioifs.h> + +/** Constructs 32 bit value for given frequency, number of channels, bits per sample and signed bit. + * Note: This currently matches 1:1 the VRDE encoding -- this might change in the future, so better don't rely on this fact! */ +#define AUDMIXBUF_AUDIO_FMT_MAKE(freq, c, bps, s) ((((s) & 0x1) << 28) + (((bps) & 0xFF) << 20) + (((c) & 0xF) << 16) + ((freq) & 0xFFFF)) + +/** Decodes frequency (Hz). */ +#define AUDMIXBUF_FMT_SAMPLE_FREQ(a) ((a) & 0xFFFF) +/** Decodes number of channels. */ +#define AUDMIXBUF_FMT_CHANNELS(a) (((a) >> 16) & 0xF) +/** Decodes signed bit. */ +#define AUDMIXBUF_FMT_SIGNED(a) (((a) >> 28) & 0x1) +/** Decodes number of bits per sample. */ +#define AUDMIXBUF_FMT_BITS_PER_SAMPLE(a) (((a) >> 20) & 0xFF) +/** Decodes number of bytes per sample. */ +#define AUDMIXBUF_FMT_BYTES_PER_SAMPLE(a) ((AUDMIXBUF_AUDIO_FMT_BITS_PER_SAMPLE(a) + 7) / 8) + +/** Converts frames to bytes. */ +#define AUDIOMIXBUF_F2B(pBuf, frames) ((frames) << (pBuf)->cShift) +/** Converts frames to bytes, respecting the conversion ratio to + * a linked buffer. */ +#define AUDIOMIXBUF_F2B_RATIO(pBuf, frames) ((((int64_t) frames << 32) / (pBuf)->iFreqRatio) << (pBuf)->cShift) +/** Converts bytes to frames, *not* taking the conversion ratio + * into account. */ +#define AUDIOMIXBUF_B2F(pBuf, cb) (cb >> (pBuf)->cShift) +/** Converts number of frames according to the buffer's ratio. */ +#define AUDIOMIXBUF_F2F_RATIO(pBuf, frames) (((int64_t) frames << 32) / (pBuf)->iFreqRatio) + + +inline uint32_t AudioMixBufBytesToSamples(PPDMAUDIOMIXBUF pMixBuf); +void AudioMixBufClear(PPDMAUDIOMIXBUF pMixBuf); +void AudioMixBufDestroy(PPDMAUDIOMIXBUF pMixBuf); +void AudioMixBufFinish(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToClear); +uint32_t AudioMixBufFree(PPDMAUDIOMIXBUF pMixBuf); +uint32_t AudioMixBufFreeBytes(PPDMAUDIOMIXBUF pMixBuf); +int AudioMixBufInit(PPDMAUDIOMIXBUF pMixBuf, const char *pszName, PPDMAUDIOPCMPROPS pProps, uint32_t cFrames); +bool AudioMixBufIsEmpty(PPDMAUDIOMIXBUF pMixBuf); +int AudioMixBufLinkTo(PPDMAUDIOMIXBUF pMixBuf, PPDMAUDIOMIXBUF pParent); +uint32_t AudioMixBufLive(PPDMAUDIOMIXBUF pMixBuf); +int AudioMixBufMixToParent(PPDMAUDIOMIXBUF pMixBuf, uint32_t cSrcFrames, uint32_t *pcSrcMixed); +int AudioMixBufMixToParentEx(PPDMAUDIOMIXBUF pMixBuf, uint32_t cSrcOffset, uint32_t cSrcFrames, uint32_t *pcSrcMixed); +int AudioMixBufPeek(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToRead, PPDMAUDIOFRAME paSampleBuf, uint32_t cSampleBuf, uint32_t *pcFramesRead); +int AudioMixBufPeekMutable(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToRead, PPDMAUDIOFRAME *ppvSamples, uint32_t *pcFramesRead); +uint32_t AudioMixBufUsed(PPDMAUDIOMIXBUF pMixBuf); +uint32_t AudioMixBufUsedBytes(PPDMAUDIOMIXBUF pMixBuf); +int AudioMixBufReadAt(PPDMAUDIOMIXBUF pMixBuf, uint32_t offSamples, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead); +int AudioMixBufReadAtEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, uint32_t offSamples, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead); +int AudioMixBufAcquireReadBlock(PPDMAUDIOMIXBUF pMixBuf, void *pvBuf, uint32_t cbBuf, uint32_t *pcBlock); +int AudioMixBufAcquireReadBlockEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, void *pvBuf, uint32_t cbBuf, uint32_t *pcBlock); +void AudioMixBufReleaseReadBlock(PPDMAUDIOMIXBUF pMixBuf, uint32_t cBlock); +uint32_t AudioMixBufReadPos(PPDMAUDIOMIXBUF pMixBuf); +void AudioMixBufReset(PPDMAUDIOMIXBUF pMixBuf); +void AudioMixBufSetVolume(PPDMAUDIOMIXBUF pMixBuf, PPDMAUDIOVOLUME pVol); +uint32_t AudioMixBufSize(PPDMAUDIOMIXBUF pMixBuf); +uint32_t AudioMixBufSizeBytes(PPDMAUDIOMIXBUF pMixBuf); +void AudioMixBufUnlink(PPDMAUDIOMIXBUF pMixBuf); +int AudioMixBufWriteAt(PPDMAUDIOMIXBUF pMixBuf, uint32_t offSamples, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten); +int AudioMixBufWriteAtEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, uint32_t offSamples, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten); +int AudioMixBufWriteCirc(PPDMAUDIOMIXBUF pMixBuf, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten); +int AudioMixBufWriteCircEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten); +uint32_t AudioMixBufWritePos(PPDMAUDIOMIXBUF pMixBuf); + +#ifdef DEBUG +void AudioMixBufDbgPrint(PPDMAUDIOMIXBUF pMixBuf); +void AudioMixBufDbgPrintChain(PPDMAUDIOMIXBUF pMixBuf); +#endif + +#endif /* !VBOX_INCLUDED_SRC_Audio_AudioMixBuffer_h */ + diff --git a/src/VBox/Devices/Audio/AudioMixer.cpp b/src/VBox/Devices/Audio/AudioMixer.cpp new file mode 100644 index 00000000..a56099fb --- /dev/null +++ b/src/VBox/Devices/Audio/AudioMixer.cpp @@ -0,0 +1,2236 @@ +/* $Id: AudioMixer.cpp $ */ +/** @file + * Audio mixing routines for multiplexing audio sources in device emulations. + * + * == Overview + * + * This mixer acts as a layer between the audio connector interface and + * the actual device emulation, providing mechanisms for audio sources (input) + * and audio sinks (output). + * + * Think of this mixer as kind of a high(er) level interface for the audio + * connector interface, abstracting common tasks such as creating and managing + * various audio sources and sinks. This mixer class is purely optional and can + * be left out when implementing a new device emulation, using only the audi + * connector interface instead. For example, the SB16 emulation does not use + * this mixer and does all its stream management on its own. + * + * As audio driver instances are handled as LUNs on the device level, this + * audio mixer then can take care of e.g. mixing various inputs/outputs to/from + * a specific source/sink. + * + * How and which audio streams are connected to sinks/sources depends on how + * the audio mixer has been set up. + * + * A sink can connect multiple output streams together, whereas a source + * does this with input streams. Each sink / source consists of one or more + * so-called mixer streams, which then in turn have pointers to the actual + * PDM audio input/output streams. + * + * == Playback + * + * For output sinks there can be one or more mixing stream attached. + * As the host sets the overall pace for the device emulation (virtual time + * in the guest OS vs. real time on the host OS), an output mixing sink + * needs to make sure that all connected output streams are able to accept + * all the same amount of data at a time. + * + * This is called synchronous multiplexing. + * + * A mixing sink employs an own audio mixing buffer, which in turn can convert + * the audio (output) data supplied from the device emulation into the sink's + * audio format. As all connected mixing streams in theory could have the same + * audio format as the mixing sink (parent), this can save processing time when + * it comes to serving a lot of mixing streams at once. That way only one + * conversion must be done, instead of each stream having to iterate over the + * data. + * + * == Recording + * + * For input sinks only one mixing stream at a time can be the recording + * source currently. A recording source is optional, e.g. it is possible to + * have no current recording source set. Switching to a different recording + * source at runtime is possible. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_AUDIO_MIXER +#include <VBox/log.h> +#include "AudioMixer.h" +#include "AudioMixBuffer.h" +#include "DrvAudio.h" + +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <VBox/vmm/mm.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include <iprt/alloc.h> +#include <iprt/asm-math.h> +#include <iprt/assert.h> +#include <iprt/string.h> + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink); + +static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink); +static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster); +static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink); +static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream); +static void audioMixerSinkReset(PAUDMIXSINK pSink); +static int audioMixerSinkSetRecSourceInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream); +static int audioMixerSinkUpdateInternal(PAUDMIXSINK pSink); +static int audioMixerSinkMultiplexSync(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWrittenMin); +static int audioMixerSinkWriteToStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream); +static int audioMixerSinkWriteToStreamEx(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream, uint32_t cbToWrite, uint32_t *pcbWritten); + +int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl); +static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pStream); + + +#ifdef LOG_ENABLED +/** + * Converts a mixer sink status to a string. + * + * @returns Stringified mixer sink flags. Must be free'd with RTStrFree(). + * "NONE" if no flags set. + * @param fFlags Mixer sink flags to convert. + */ +static char *dbgAudioMixerSinkStatusToStr(AUDMIXSINKSTS fStatus) +{ +#define APPEND_FLAG_TO_STR(_aFlag) \ + if (fStatus & AUDMIXSINK_STS_##_aFlag) \ + { \ + if (pszFlags) \ + { \ + rc2 = RTStrAAppend(&pszFlags, " "); \ + if (RT_FAILURE(rc2)) \ + break; \ + } \ + \ + rc2 = RTStrAAppend(&pszFlags, #_aFlag); \ + if (RT_FAILURE(rc2)) \ + break; \ + } \ + + char *pszFlags = NULL; + int rc2 = VINF_SUCCESS; + + do + { + APPEND_FLAG_TO_STR(NONE); + APPEND_FLAG_TO_STR(RUNNING); + APPEND_FLAG_TO_STR(PENDING_DISABLE); + APPEND_FLAG_TO_STR(DIRTY); + + } while (0); + + if ( RT_FAILURE(rc2) + && pszFlags) + { + RTStrFree(pszFlags); + pszFlags = NULL; + } + +#undef APPEND_FLAG_TO_STR + + return pszFlags; +} +#endif /* DEBUG */ + +/** + * Creates an audio sink and attaches it to the given mixer. + * + * @returns IPRT status code. + * @param pMixer Mixer to attach created sink to. + * @param pszName Name of the sink to create. + * @param enmDir Direction of the sink to create. + * @param ppSink Pointer which returns the created sink on success. + */ +int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, AUDMIXSINKDIR enmDir, PAUDMIXSINK *ppSink) +{ + AssertPtrReturn(pMixer, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + /* ppSink is optional. */ + + int rc = RTCritSectEnter(&pMixer->CritSect); + if (RT_FAILURE(rc)) + return rc; + + PAUDMIXSINK pSink = (PAUDMIXSINK)RTMemAllocZ(sizeof(AUDMIXSINK)); + if (pSink) + { + pSink->pszName = RTStrDup(pszName); + if (!pSink->pszName) + rc = VERR_NO_MEMORY; + + if (RT_SUCCESS(rc)) + rc = RTCritSectInit(&pSink->CritSect); + + if (RT_SUCCESS(rc)) + { + pSink->pParent = pMixer; + pSink->enmDir = enmDir; + RTListInit(&pSink->lstStreams); + + /* Set initial volume to max. */ + pSink->Volume.fMuted = false; + pSink->Volume.uLeft = PDMAUDIO_VOLUME_MAX; + pSink->Volume.uRight = PDMAUDIO_VOLUME_MAX; + + /* Ditto for the combined volume. */ + pSink->VolumeCombined.fMuted = false; + pSink->VolumeCombined.uLeft = PDMAUDIO_VOLUME_MAX; + pSink->VolumeCombined.uRight = PDMAUDIO_VOLUME_MAX; + + RTListAppend(&pMixer->lstSinks, &pSink->Node); + pMixer->cSinks++; + + LogFlowFunc(("pMixer=%p, pSink=%p, cSinks=%RU8\n", + pMixer, pSink, pMixer->cSinks)); + + if (ppSink) + *ppSink = pSink; + } + + if (RT_FAILURE(rc)) + { + RTCritSectDelete(&pSink->CritSect); + + if (pSink) + { + RTMemFree(pSink); + pSink = NULL; + } + } + } + else + rc = VERR_NO_MEMORY; + + int rc2 = RTCritSectLeave(&pMixer->CritSect); + AssertRC(rc2); + + return rc; +} + +/** + * Creates an audio mixer. + * + * @returns IPRT status code. + * @param pszName Name of the audio mixer. + * @param fFlags Creation flags. Not used at the moment and must be 0. + * @param ppMixer Pointer which returns the created mixer object. + */ +int AudioMixerCreate(const char *pszName, uint32_t fFlags, PAUDIOMIXER *ppMixer) +{ + RT_NOREF(fFlags); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + /** @todo Add fFlags validation. */ + AssertPtrReturn(ppMixer, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + PAUDIOMIXER pMixer = (PAUDIOMIXER)RTMemAllocZ(sizeof(AUDIOMIXER)); + if (pMixer) + { + pMixer->pszName = RTStrDup(pszName); + if (!pMixer->pszName) + rc = VERR_NO_MEMORY; + + if (RT_SUCCESS(rc)) + rc = RTCritSectInit(&pMixer->CritSect); + + if (RT_SUCCESS(rc)) + { + pMixer->cSinks = 0; + RTListInit(&pMixer->lstSinks); + + /* Set master volume to the max. */ + pMixer->VolMaster.fMuted = false; + pMixer->VolMaster.uLeft = PDMAUDIO_VOLUME_MAX; + pMixer->VolMaster.uRight = PDMAUDIO_VOLUME_MAX; + + LogFlowFunc(("Created mixer '%s'\n", pMixer->pszName)); + + *ppMixer = pMixer; + } + else + RTMemFree(pMixer); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Helper function for the internal debugger to print the mixer's current + * state, along with the attached sinks. + * + * @param pMixer Mixer to print debug output for. + * @param pHlp Debug info helper to use. + * @param pszArgs Optional arguments. Not being used at the moment. + */ +void AudioMixerDebug(PAUDIOMIXER pMixer, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF(pszArgs); + PAUDMIXSINK pSink; + unsigned iSink = 0; + + int rc2 = RTCritSectEnter(&pMixer->CritSect); + if (RT_FAILURE(rc2)) + return; + + pHlp->pfnPrintf(pHlp, "[Master] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", pMixer->pszName, + pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight, pMixer->VolMaster.fMuted); + + RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node) + { + pHlp->pfnPrintf(pHlp, "[Sink %u] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", iSink, pSink->pszName, + pSink->Volume.uLeft, pSink->Volume.uRight, pSink->Volume.fMuted); + ++iSink; + } + + rc2 = RTCritSectLeave(&pMixer->CritSect); + AssertRC(rc2); +} + +/** + * Destroys an audio mixer. + * + * @param pMixer Audio mixer to destroy. + */ +void AudioMixerDestroy(PAUDIOMIXER pMixer) +{ + if (!pMixer) + return; + + int rc2 = RTCritSectEnter(&pMixer->CritSect); + AssertRC(rc2); + + LogFlowFunc(("Destroying %s ...\n", pMixer->pszName)); + + PAUDMIXSINK pSink, pSinkNext; + RTListForEachSafe(&pMixer->lstSinks, pSink, pSinkNext, AUDMIXSINK, Node) + { + /* Save a pointer to the sink to remove, as pSink + * will not be valid anymore after calling audioMixerRemoveSinkInternal(). */ + PAUDMIXSINK pSinkToRemove = pSink; + + audioMixerRemoveSinkInternal(pMixer, pSinkToRemove); + audioMixerSinkDestroyInternal(pSinkToRemove); + } + + pMixer->cSinks = 0; + + if (pMixer->pszName) + { + RTStrFree(pMixer->pszName); + pMixer->pszName = NULL; + } + + rc2 = RTCritSectLeave(&pMixer->CritSect); + AssertRC(rc2); + + RTCritSectDelete(&pMixer->CritSect); + + RTMemFree(pMixer); + pMixer = NULL; +} + +/** + * Invalidates all internal data, internal version. + * + * @returns IPRT status code. + * @param pMixer Mixer to invalidate data for. + */ +int audioMixerInvalidateInternal(PAUDIOMIXER pMixer) +{ + AssertPtrReturn(pMixer, VERR_INVALID_POINTER); + + LogFlowFunc(("[%s]\n", pMixer->pszName)); + + /* Propagate new master volume to all connected sinks. */ + PAUDMIXSINK pSink; + RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node) + { + int rc2 = audioMixerSinkUpdateVolume(pSink, &pMixer->VolMaster); + AssertRC(rc2); + } + + return VINF_SUCCESS; +} + +/** + * Invalidates all internal data. + * + * @returns IPRT status code. + * @param pMixer Mixer to invalidate data for. + */ +void AudioMixerInvalidate(PAUDIOMIXER pMixer) +{ + AssertPtrReturnVoid(pMixer); + + int rc2 = RTCritSectEnter(&pMixer->CritSect); + AssertRC(rc2); + + LogFlowFunc(("[%s]\n", pMixer->pszName)); + + rc2 = audioMixerInvalidateInternal(pMixer); + AssertRC(rc2); + + rc2 = RTCritSectLeave(&pMixer->CritSect); + AssertRC(rc2); +} + +/** + * Removes a formerly attached audio sink for an audio mixer, internal version. + * + * @returns IPRT status code. + * @param pMixer Mixer to remove sink from. + * @param pSink Sink to remove. + */ +static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink) +{ + AssertPtrReturn(pMixer, VERR_INVALID_POINTER); + if (!pSink) + return VERR_NOT_FOUND; + + AssertMsgReturn(pSink->pParent == pMixer, ("%s: Is not part of mixer '%s'\n", + pSink->pszName, pMixer->pszName), VERR_NOT_FOUND); + + LogFlowFunc(("[%s] pSink=%s, cSinks=%RU8\n", + pMixer->pszName, pSink->pszName, pMixer->cSinks)); + + /* Remove sink from mixer. */ + RTListNodeRemove(&pSink->Node); + Assert(pMixer->cSinks); + + /* Set mixer to NULL so that we know we're not part of any mixer anymore. */ + pSink->pParent = NULL; + + return VINF_SUCCESS; +} + +/** + * Removes a formerly attached audio sink for an audio mixer. + * + * @returns IPRT status code. + * @param pMixer Mixer to remove sink from. + * @param pSink Sink to remove. + */ +void AudioMixerRemoveSink(PAUDIOMIXER pMixer, PAUDMIXSINK pSink) +{ + int rc2 = RTCritSectEnter(&pMixer->CritSect); + AssertRC(rc2); + + audioMixerSinkRemoveAllStreamsInternal(pSink); + audioMixerRemoveSinkInternal(pMixer, pSink); + + rc2 = RTCritSectLeave(&pMixer->CritSect); +} + +/** + * Sets the mixer's master volume. + * + * @returns IPRT status code. + * @param pMixer Mixer to set master volume for. + * @param pVol Volume to set. + */ +int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PPDMAUDIOVOLUME pVol) +{ + AssertPtrReturn(pMixer, VERR_INVALID_POINTER); + AssertPtrReturn(pVol, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pMixer->CritSect); + if (RT_FAILURE(rc)) + return rc; + + memcpy(&pMixer->VolMaster, pVol, sizeof(PDMAUDIOVOLUME)); + + LogFlowFunc(("[%s] lVol=%RU32, rVol=%RU32 => fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n", + pMixer->pszName, pVol->uLeft, pVol->uRight, + pMixer->VolMaster.fMuted, pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight)); + + rc = audioMixerInvalidateInternal(pMixer); + + int rc2 = RTCritSectLeave(&pMixer->CritSect); + AssertRC(rc2); + + return rc; +} + +/********************************************************************************************************************************* + * Mixer Sink implementation. + ********************************************************************************************************************************/ + +/** + * Adds an audio stream to a specific audio sink. + * + * @returns IPRT status code. + * @param pSink Sink to add audio stream to. + * @param pStream Stream to add. + */ +int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return rc; + + if (pSink->cStreams == UINT8_MAX) /* 255 streams per sink max. */ + { + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return VERR_NO_MORE_HANDLES; + } + + LogFlowFuncEnter(); + + /** @todo Check if stream already is assigned to (another) sink. */ + + /* If the sink is running and not in pending disable mode, + * make sure that the added stream also is enabled. */ + if ( (pSink->fStatus & AUDMIXSINK_STS_RUNNING) + && !(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE)) + { + rc = audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_ENABLE, AUDMIXSTRMCTL_FLAG_NONE); + } + + if (RT_SUCCESS(rc)) + { + /* Apply the sink's combined volume to the stream. */ + rc = pStream->pConn->pfnStreamSetVolume(pStream->pConn, pStream->pStream, &pSink->VolumeCombined); + AssertRC(rc); + } + + if (RT_SUCCESS(rc)) + { + /* Save pointer to sink the stream is attached to. */ + pStream->pSink = pSink; + + /* Append stream to sink's list. */ + RTListAppend(&pSink->lstStreams, &pStream->Node); + pSink->cStreams++; + } + + LogFlowFunc(("[%s] cStreams=%RU8, rc=%Rrc\n", pSink->pszName, pSink->cStreams, rc)); + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return rc; +} + +/** + * Creates an audio mixer stream. + * + * @returns IPRT status code. + * @param pSink Sink to use for creating the stream. + * @param pConn Audio connector interface to use. + * @param pCfg Audio stream configuration to use. + * @param fFlags Stream flags. Currently unused, set to 0. + * @param ppStream Pointer which receives the newly created audio stream. + */ +int AudioMixerSinkCreateStream(PAUDMIXSINK pSink, + PPDMIAUDIOCONNECTOR pConn, PPDMAUDIOSTREAMCFG pCfg, AUDMIXSTREAMFLAGS fFlags, PAUDMIXSTREAM *ppStream) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + AssertPtrReturn(pConn, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + /** @todo Validate fFlags. */ + /* ppStream is optional. */ + + if (pConn->pfnGetStatus(pConn, PDMAUDIODIR_ANY) == PDMAUDIOBACKENDSTS_NOT_ATTACHED) + return VERR_AUDIO_BACKEND_NOT_ATTACHED; + + PAUDMIXSTREAM pMixStream = (PAUDMIXSTREAM)RTMemAllocZ(sizeof(AUDMIXSTREAM)); + if (!pMixStream) + return VERR_NO_MEMORY; + + pMixStream->pszName = RTStrDup(pCfg->szName); + if (!pMixStream->pszName) + { + RTMemFree(pMixStream); + return VERR_NO_MEMORY; + } + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return rc; + + LogFlowFunc(("[%s] fFlags=0x%x (enmDir=%ld, %RU8 bits, %RU8 channels, %RU32Hz)\n", + pSink->pszName, fFlags, pCfg->enmDir, pCfg->Props.cBytes * 8, pCfg->Props.cChannels, pCfg->Props.uHz)); + + /* + * Initialize the host-side configuration for the stream to be created. + * Always use the sink's PCM audio format as the host side when creating a stream for it. + */ + AssertMsg(DrvAudioHlpPCMPropsAreValid(&pSink->PCMProps), + ("%s: Does not (yet) have a format set when it must\n", pSink->pszName)); + + PDMAUDIOSTREAMCFG CfgHost; + rc = DrvAudioHlpPCMPropsToStreamCfg(&pSink->PCMProps, &CfgHost); + AssertRCReturn(rc, rc); + + /* Apply the sink's direction for the configuration to use to + * create the stream. */ + if (pSink->enmDir == AUDMIXSINKDIR_INPUT) + { + CfgHost.DestSource.Source = pCfg->DestSource.Source; + CfgHost.enmDir = PDMAUDIODIR_IN; + CfgHost.enmLayout = pCfg->enmLayout; + } + else + { + CfgHost.DestSource.Dest = pCfg->DestSource.Dest; + CfgHost.enmDir = PDMAUDIODIR_OUT; + CfgHost.enmLayout = pCfg->enmLayout; + } + + RTStrPrintf(CfgHost.szName, sizeof(CfgHost.szName), "%s", pCfg->szName); + + rc = RTCritSectInit(&pMixStream->CritSect); + if (RT_SUCCESS(rc)) + { + PPDMAUDIOSTREAM pStream; + rc = pConn->pfnStreamCreate(pConn, &CfgHost, pCfg, &pStream); + if (RT_SUCCESS(rc)) + { + /* Save the audio stream pointer to this mixing stream. */ + pMixStream->pStream = pStream; + + /* Increase the stream's reference count to let others know + * we're reyling on it to be around now. */ + pConn->pfnStreamRetain(pConn, pStream); + } + } + + if (RT_SUCCESS(rc)) + { + rc = RTCircBufCreate(&pMixStream->pCircBuf, DrvAudioHlpMilliToBytes(100 /* ms */, &pSink->PCMProps)); /** @todo Make this configurable. */ + AssertRC(rc); + } + + if (RT_SUCCESS(rc)) + { + pMixStream->fFlags = fFlags; + pMixStream->pConn = pConn; + + if (ppStream) + *ppStream = pMixStream; + } + else if (pMixStream) + { + int rc2 = RTCritSectDelete(&pMixStream->CritSect); + AssertRC(rc2); + + if (pMixStream->pszName) + { + RTStrFree(pMixStream->pszName); + pMixStream->pszName = NULL; + } + + RTMemFree(pMixStream); + pMixStream = NULL; + } + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return rc; +} + +/** + * Static helper function to translate a sink command + * to a PDM audio stream command. + * + * @returns PDM audio stream command, or PDMAUDIOSTREAMCMD_UNKNOWN if not found. + * @param enmCmd Mixer sink command to translate. + */ +static PDMAUDIOSTREAMCMD audioMixerSinkToStreamCmd(AUDMIXSINKCMD enmCmd) +{ + switch (enmCmd) + { + case AUDMIXSINKCMD_ENABLE: return PDMAUDIOSTREAMCMD_ENABLE; + case AUDMIXSINKCMD_DISABLE: return PDMAUDIOSTREAMCMD_DISABLE; + case AUDMIXSINKCMD_PAUSE: return PDMAUDIOSTREAMCMD_PAUSE; + case AUDMIXSINKCMD_RESUME: return PDMAUDIOSTREAMCMD_RESUME; + case AUDMIXSINKCMD_DROP: return PDMAUDIOSTREAMCMD_DROP; + default: break; + } + + AssertMsgFailed(("Unsupported sink command %d\n", enmCmd)); + return PDMAUDIOSTREAMCMD_UNKNOWN; +} + +/** + * Controls a mixer sink. + * + * @returns IPRT status code. + * @param pSink Mixer sink to control. + * @param enmSinkCmd Sink command to set. + */ +int AudioMixerSinkCtl(PAUDMIXSINK pSink, AUDMIXSINKCMD enmSinkCmd) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + + PDMAUDIOSTREAMCMD enmCmdStream = audioMixerSinkToStreamCmd(enmSinkCmd); + if (enmCmdStream == PDMAUDIOSTREAMCMD_UNKNOWN) + return VERR_NOT_SUPPORTED; + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return rc; + + /* Input sink and no recording source set? Bail out early. */ + if ( pSink->enmDir == AUDMIXSINKDIR_INPUT + && pSink->In.pStreamRecSource == NULL) + { + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return rc; + } + + PAUDMIXSTREAM pStream; + if ( pSink->enmDir == AUDMIXSINKDIR_INPUT + && pSink->In.pStreamRecSource) /* Any recording source set? */ + { + RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node) + { + if (pStream == pSink->In.pStreamRecSource) + { + int rc2 = audioMixerStreamCtlInternal(pStream, enmCmdStream, AUDMIXSTRMCTL_FLAG_NONE); + if (rc2 == VERR_NOT_SUPPORTED) + rc2 = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + rc = rc2; + /* Keep going. Flag? */ + } + } + } + else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT) + { + RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node) + { + int rc2 = audioMixerStreamCtlInternal(pStream, enmCmdStream, AUDMIXSTRMCTL_FLAG_NONE); + if (rc2 == VERR_NOT_SUPPORTED) + rc2 = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + rc = rc2; + /* Keep going. Flag? */ + } + } + + switch (enmSinkCmd) + { + case AUDMIXSINKCMD_ENABLE: + { + /* Make sure to clear any other former flags again by assigning AUDMIXSINK_STS_RUNNING directly. */ + pSink->fStatus = AUDMIXSINK_STS_RUNNING; + break; + } + + case AUDMIXSINKCMD_DISABLE: + { + if (pSink->fStatus & AUDMIXSINK_STS_RUNNING) + { + /* Set the sink in a pending disable state first. + * The final status (disabled) will be set in the sink's iteration. */ + pSink->fStatus |= AUDMIXSINK_STS_PENDING_DISABLE; + } + break; + } + + case AUDMIXSINKCMD_DROP: + { + AudioMixBufReset(&pSink->MixBuf); + + /* Clear dirty bit, keep others. */ + pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY; + break; + } + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + +#ifdef LOG_ENABLED + char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus); + LogFlowFunc(("[%s] enmCmd=%d, fStatus=%s, rc=%Rrc\n", pSink->pszName, enmSinkCmd, pszStatus, rc)); + RTStrFree(pszStatus); +#endif + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return rc; +} + +/** + * Destroys a mixer sink and removes it from the attached mixer (if any). + * + * @param pSink Mixer sink to destroy. + */ +void AudioMixerSinkDestroy(PAUDMIXSINK pSink) +{ + if (!pSink) + return; + + int rc2 = RTCritSectEnter(&pSink->CritSect); + AssertRC(rc2); + + if (pSink->pParent) + { + /* Save mixer pointer, as after audioMixerRemoveSinkInternal() the + * pointer will be gone from the stream. */ + PAUDIOMIXER pMixer = pSink->pParent; + AssertPtr(pMixer); + + audioMixerRemoveSinkInternal(pMixer, pSink); + + Assert(pMixer->cSinks); + pMixer->cSinks--; + } + + rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + audioMixerSinkDestroyInternal(pSink); +} + +/** + * Destroys a mixer sink. + * + * @param pSink Mixer sink to destroy. + */ +static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink) +{ + AssertPtrReturnVoid(pSink); + + LogFunc(("%s\n", pSink->pszName)); + + PAUDMIXSTREAM pStream, pStreamNext; + RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node) + { + /* Save a pointer to the stream to remove, as pStream + * will not be valid anymore after calling audioMixerSinkRemoveStreamInternal(). */ + PAUDMIXSTREAM pStreamToRemove = pStream; + + audioMixerSinkRemoveStreamInternal(pSink, pStreamToRemove); + audioMixerStreamDestroyInternal(pStreamToRemove); + } + +#ifdef VBOX_AUDIO_MIXER_DEBUG + DrvAudioHlpFileDestroy(pSink->Dbg.pFile); + pSink->Dbg.pFile = NULL; +#endif + + if (pSink->pszName) + { + RTStrFree(pSink->pszName); + pSink->pszName = NULL; + } + + RTCritSectDelete(&pSink->CritSect); + + RTMemFree(pSink); + pSink = NULL; +} + +/** + * Returns the amount of bytes ready to be read from a sink since the last call + * to AudioMixerSinkUpdate(). + * + * @returns Amount of bytes ready to be read from the sink. + * @param pSink Sink to return number of available bytes for. + */ +uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink) +{ + AssertPtrReturn(pSink, 0); + + AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("%s: Can't read from a non-input sink\n", pSink->pszName)); + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return 0; + + uint32_t cbReadable = 0; + + if (pSink->fStatus & AUDMIXSINK_STS_RUNNING) + { +#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF_IN +# error "Implement me!" +#else + PAUDMIXSTREAM pStreamRecSource = pSink->In.pStreamRecSource; + if (!pStreamRecSource) + { + Log3Func(("[%s] No recording source specified, skipping ...\n", pSink->pszName)); + } + else + { + AssertPtr(pStreamRecSource->pConn); + cbReadable = pStreamRecSource->pConn->pfnStreamGetReadable(pStreamRecSource->pConn, pStreamRecSource->pStream); + } +#endif + } + + Log3Func(("[%s] cbReadable=%RU32\n", pSink->pszName, cbReadable)); + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return cbReadable; +} + +/** + * Returns the sink's current recording source. + * + * @return Mixer stream which currently is set as current recording source, NULL if none is set. + * @param pSink Audio mixer sink to return current recording source for. + */ +PAUDMIXSTREAM AudioMixerSinkGetRecordingSource(PAUDMIXSINK pSink) +{ + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return NULL; + + AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("Specified sink is not an input sink\n")); + + PAUDMIXSTREAM pStream = pSink->In.pStreamRecSource; + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return pStream; +} + +/** + * Returns the amount of bytes ready to be written to a sink since the last call + * to AudioMixerSinkUpdate(). + * + * @returns Amount of bytes ready to be written to the sink. + * @param pSink Sink to return number of available bytes for. + */ +uint32_t AudioMixerSinkGetWritable(PAUDMIXSINK pSink) +{ + AssertPtrReturn(pSink, 0); + + AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT, ("%s: Can't write to a non-output sink\n", pSink->pszName)); + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return 0; + + uint32_t cbWritable = 0; + + if ( (pSink->fStatus & AUDMIXSINK_STS_RUNNING) + && !(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE)) + { + cbWritable = AudioMixBufFreeBytes(&pSink->MixBuf); + } + + Log3Func(("[%s] cbWritable=%RU32 (%RU64ms)\n", + pSink->pszName, cbWritable, DrvAudioHlpBytesToMilli(cbWritable, &pSink->PCMProps))); + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return cbWritable; +} + +/** + * Returns the sink's mixing direction. + * + * @returns Mixing direction. + * @param pSink Sink to return direction for. + */ +AUDMIXSINKDIR AudioMixerSinkGetDir(PAUDMIXSINK pSink) +{ + AssertPtrReturn(pSink, AUDMIXSINKDIR_UNKNOWN); + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return AUDMIXSINKDIR_UNKNOWN; + + AUDMIXSINKDIR enmDir = pSink->enmDir; + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return enmDir; +} + +/** + * Returns the sink's (friendly) name. + * + * @returns The sink's (friendly) name. + */ +const char *AudioMixerSinkGetName(const PAUDMIXSINK pSink) +{ + AssertPtrReturn(pSink, "<Unknown>"); + + return pSink->pszName; +} + +/** + * Returns a specific mixer stream from a sink, based on its index. + * + * @returns Mixer stream if found, or NULL if not found. + * @param pSink Sink to retrieve mixer stream from. + * @param uIndex Index of the mixer stream to return. + */ +PAUDMIXSTREAM AudioMixerSinkGetStream(PAUDMIXSINK pSink, uint8_t uIndex) +{ + AssertPtrReturn(pSink, NULL); + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return NULL; + + AssertMsgReturn(uIndex < pSink->cStreams, + ("Index %RU8 exceeds stream count (%RU8)", uIndex, pSink->cStreams), NULL); + + /* Slow lookup, d'oh. */ + PAUDMIXSTREAM pStream = RTListGetFirst(&pSink->lstStreams, AUDMIXSTREAM, Node); + while (uIndex) + { + pStream = RTListGetNext(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node); + uIndex--; + } + + /** @todo Do we need to raise the stream's reference count here? */ + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + AssertPtr(pStream); + return pStream; +} + +/** + * Returns the current status of a mixer sink. + * + * @returns The sink's current status. + * @param pSink Mixer sink to return status for. + */ +AUDMIXSINKSTS AudioMixerSinkGetStatus(PAUDMIXSINK pSink) +{ + if (!pSink) + return AUDMIXSINK_STS_NONE; + + int rc2 = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc2)) + return AUDMIXSINK_STS_NONE; + + /* If the dirty flag is set, there is unprocessed data in the sink. */ + AUDMIXSINKSTS stsSink = pSink->fStatus; + + rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return stsSink; +} + +/** + * Returns the number of attached mixer streams to a mixer sink. + * + * @returns The number of attached mixer streams. + * @param pSink Mixer sink to return number for. + */ +uint8_t AudioMixerSinkGetStreamCount(PAUDMIXSINK pSink) +{ + if (!pSink) + return 0; + + int rc2 = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc2)) + return 0; + + uint8_t cStreams = pSink->cStreams; + + rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return cStreams; +} + +/** + * Returns whether the sink is in an active state or not. + * Note: The pending disable state also counts as active. + * + * @returns True if active, false if not. + * @param pSink Sink to return active state for. + */ +bool AudioMixerSinkIsActive(PAUDMIXSINK pSink) +{ + if (!pSink) + return false; + + int rc2 = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc2)) + return false; + + bool fIsActive = pSink->fStatus & AUDMIXSINK_STS_RUNNING; + /* Note: AUDMIXSINK_STS_PENDING_DISABLE implies AUDMIXSINK_STS_RUNNING. */ + + Log3Func(("[%s] fActive=%RTbool\n", pSink->pszName, fIsActive)); + + rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return fIsActive; +} + +/** + * Reads audio data from a mixer sink. + * + * @returns IPRT status code. + * @param pSink Mixer sink to read data from. + * @param enmOp Mixer operation to use for reading the data. + * @param pvBuf Buffer where to store the read data. + * @param cbBuf Buffer size (in bytes) where to store the data. + * @param pcbRead Number of bytes read. Optional. + */ +int AudioMixerSinkRead(PAUDMIXSINK pSink, AUDMIXOP enmOp, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + RT_NOREF(enmOp); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + /* pcbRead is optional. */ + + /** @todo Handle mixing operation enmOp! */ + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return rc; + + AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, + ("Can't read from a sink which is not an input sink\n")); + + uint32_t cbRead = 0; + + /* Flag indicating whether this sink is in a 'clean' state, + * e.g. there is no more data to read from. */ + bool fClean = true; + + PAUDMIXSTREAM pStreamRecSource = pSink->In.pStreamRecSource; + if (!pStreamRecSource) + { + Log3Func(("[%s] No recording source specified, skipping ...\n", pSink->pszName)); + } + else if (!DrvAudioHlpStreamStatusCanRead( + pStreamRecSource->pConn->pfnStreamGetStatus(pStreamRecSource->pConn, pStreamRecSource->pStream))) + { + Log3Func(("[%s] Stream '%s' disabled, skipping ...\n", pSink->pszName, pStreamRecSource->pszName)); + } + else + { + uint32_t cbToRead = cbBuf; + while (cbToRead) + { + uint32_t cbReadStrm; + AssertPtr(pStreamRecSource->pConn); +#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF_IN +# error "Implement me!" +#else + rc = pStreamRecSource->pConn->pfnStreamRead(pStreamRecSource->pConn, pStreamRecSource->pStream, + (uint8_t *)pvBuf + cbRead, cbToRead, &cbReadStrm); +#endif + if (RT_FAILURE(rc)) + LogFunc(("[%s] Failed reading from stream '%s': %Rrc\n", pSink->pszName, pStreamRecSource->pszName, rc)); + + Log3Func(("[%s] Stream '%s': Read %RU32 bytes\n", pSink->pszName, pStreamRecSource->pszName, cbReadStrm)); + + if ( RT_FAILURE(rc) + || !cbReadStrm) + break; + + AssertBreakStmt(cbReadStrm <= cbToRead, rc = VERR_BUFFER_OVERFLOW); + cbToRead -= cbReadStrm; + cbRead += cbReadStrm; + Assert(cbRead <= cbBuf); + } + + uint32_t cbReadable = pStreamRecSource->pConn->pfnStreamGetReadable(pStreamRecSource->pConn, pStreamRecSource->pStream); + + /* Still some data available? Then sink is not clean (yet). */ + if (cbReadable) + fClean = false; + + if (RT_SUCCESS(rc)) + { + if (fClean) + pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY; + + /* Update our last read time stamp. */ + pSink->tsLastReadWrittenNs = RTTimeNanoTS(); + +#ifdef VBOX_AUDIO_MIXER_DEBUG + int rc2 = DrvAudioHlpFileWrite(pSink->Dbg.pFile, pvBuf, cbRead, 0 /* fFlags */); + AssertRC(rc2); +#endif + } + } + +#ifdef LOG_ENABLED + char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus); + Log2Func(("[%s] cbRead=%RU32, fClean=%RTbool, fStatus=%s, rc=%Rrc\n", pSink->pszName, cbRead, fClean, pszStatus, rc)); + RTStrFree(pszStatus); +#endif + + if (pcbRead) + *pcbRead = cbRead; + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return rc; +} + +/** + * Removes a mixer stream from a mixer sink, internal version. + * + * @returns IPRT status code. + * @param pSink Sink to remove mixer stream from. + * @param pStream Stream to remove. + */ +static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) +{ + AssertPtrReturn(pSink, VERR_INVALID_PARAMETER); + if ( !pStream + || !pStream->pSink) /* Not part of a sink anymore? */ + { + return VERR_NOT_FOUND; + } + + AssertMsgReturn(pStream->pSink == pSink, ("Stream '%s' is not part of sink '%s'\n", + pStream->pszName, pSink->pszName), VERR_NOT_FOUND); + + LogFlowFunc(("[%s] (Stream = %s), cStreams=%RU8\n", + pSink->pszName, pStream->pStream->szName, pSink->cStreams)); + + /* Remove stream from sink. */ + RTListNodeRemove(&pStream->Node); + + int rc = VINF_SUCCESS; + + if (pSink->enmDir == AUDMIXSINKDIR_INPUT) + { + /* Make sure to also un-set the recording source if this stream was set + * as the recording source before. */ + if (pStream == pSink->In.pStreamRecSource) + rc = audioMixerSinkSetRecSourceInternal(pSink, NULL); + } + + /* Set sink to NULL so that we know we're not part of any sink anymore. */ + pStream->pSink = NULL; + + return rc; +} + +/** + * Removes a mixer stream from a mixer sink. + * + * @param pSink Sink to remove mixer stream from. + * @param pStream Stream to remove. + */ +void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) +{ + int rc2 = RTCritSectEnter(&pSink->CritSect); + AssertRC(rc2); + + rc2 = audioMixerSinkRemoveStreamInternal(pSink, pStream); + if (RT_SUCCESS(rc2)) + { + Assert(pSink->cStreams); + pSink->cStreams--; + } + + rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); +} + +/** + * Removes all attached streams from a given sink. + * + * @param pSink Sink to remove attached streams from. + */ +static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink) +{ + if (!pSink) + return; + + LogFunc(("%s\n", pSink->pszName)); + + PAUDMIXSTREAM pStream, pStreamNext; + RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node) + audioMixerSinkRemoveStreamInternal(pSink, pStream); +} + +/** + * Resets the sink's state. + * + * @param pSink Sink to reset. + */ +static void audioMixerSinkReset(PAUDMIXSINK pSink) +{ + if (!pSink) + return; + + LogFunc(("[%s]\n", pSink->pszName)); + + AudioMixBufReset(&pSink->MixBuf); + + /* Update last updated timestamp. */ + pSink->tsLastUpdatedMs = 0; + + /* Reset status. */ + pSink->fStatus = AUDMIXSINK_STS_NONE; +} + +/** + * Removes all attached streams from a given sink. + * + * @param pSink Sink to remove attached streams from. + */ +void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink) +{ + if (!pSink) + return; + + int rc2 = RTCritSectEnter(&pSink->CritSect); + AssertRC(rc2); + + audioMixerSinkRemoveAllStreamsInternal(pSink); + + pSink->cStreams = 0; + + rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); +} + +/** + * Resets a sink. This will immediately stop all processing. + * + * @param pSink Sink to reset. + */ +void AudioMixerSinkReset(PAUDMIXSINK pSink) +{ + if (!pSink) + return; + + int rc2 = RTCritSectEnter(&pSink->CritSect); + AssertRC(rc2); + + LogFlowFunc(("[%s]\n", pSink->pszName)); + + audioMixerSinkReset(pSink); + + rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); +} + +/** + * Returns the audio format of a mixer sink. + * + * @param pSink Sink to retrieve audio format for. + * @param pPCMProps Where to the returned audio format. + */ +void AudioMixerSinkGetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps) +{ + AssertPtrReturnVoid(pSink); + AssertPtrReturnVoid(pPCMProps); + + int rc2 = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc2)) + return; + + memcpy(pPCMProps, &pSink->PCMProps, sizeof(PDMAUDIOPCMPROPS)); + + rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); +} + +/** + * Sets the audio format of a mixer sink. + * + * @returns IPRT status code. + * @param pSink Sink to set audio format for. + * @param pPCMProps Audio format (PCM properties) to set. + */ +int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + AssertPtrReturn(pPCMProps, VERR_INVALID_POINTER); + AssertReturn(DrvAudioHlpPCMPropsAreValid(pPCMProps), VERR_INVALID_PARAMETER); + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return rc; + + if (DrvAudioHlpPCMPropsAreEqual(&pSink->PCMProps, pPCMProps)) /* Bail out early if PCM properties are equal. */ + { + rc = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc); + + return rc; + } + + if (pSink->PCMProps.uHz) + LogFlowFunc(("[%s] Old format: %RU8 bit, %RU8 channels, %RU32Hz\n", + pSink->pszName, pSink->PCMProps.cBytes * 8, pSink->PCMProps.cChannels, pSink->PCMProps.uHz)); + + memcpy(&pSink->PCMProps, pPCMProps, sizeof(PDMAUDIOPCMPROPS)); + + LogFlowFunc(("[%s] New format %RU8 bit, %RU8 channels, %RU32Hz\n", + pSink->pszName, pSink->PCMProps.cBytes * 8, pSink->PCMProps.cChannels, pSink->PCMProps.uHz)); + + /* Also update the sink's mixing buffer format. */ + AudioMixBufDestroy(&pSink->MixBuf); + rc = AudioMixBufInit(&pSink->MixBuf, pSink->pszName, &pSink->PCMProps, + DrvAudioHlpMilliToFrames(100 /* ms */, &pSink->PCMProps)); /** @todo Make this configurable? */ + if (RT_SUCCESS(rc)) + { + PAUDMIXSTREAM pStream; + RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node) + { + /** @todo Invalidate mix buffers! */ + } + } + +#ifdef VBOX_AUDIO_MIXER_DEBUG + if (RT_SUCCESS(rc)) + { + DrvAudioHlpFileClose(pSink->Dbg.pFile); + + char szTemp[RTPATH_MAX]; + int rc2 = RTPathTemp(szTemp, sizeof(szTemp)); + if (RT_SUCCESS(rc2)) + { + /** @todo Sanitize sink name. */ + + char szName[64]; + RTStrPrintf(szName, sizeof(szName), "MixerSink-%s", pSink->pszName); + + char szFile[RTPATH_MAX + 1]; + rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), szTemp, szName, + 0 /* Instance */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE); + if (RT_SUCCESS(rc2)) + { + rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE, + &pSink->Dbg.pFile); + if (RT_SUCCESS(rc2)) + rc2 = DrvAudioHlpFileOpen(pSink->Dbg.pFile, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, &pSink->PCMProps); + } + } + } +#endif + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Set the current recording source of an input mixer sink, internal version. + * + * @return IPRT status code. + * @param pSink Input mixer sink to set recording source for. + * @param pStream Mixer stream to set as current recording source. Must be an input stream. + * Specify NULL to un-set the current recording source. + */ +static int audioMixerSinkSetRecSourceInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) +{ + AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("Specified sink is not an input sink\n")); + + int rc; + + if (pSink->In.pStreamRecSource) /* Disable old recording source, if any set. */ + { + const PPDMIAUDIOCONNECTOR pConn = pSink->In.pStreamRecSource->pConn; + AssertPtr(pConn); + rc = pConn->pfnEnable(pConn, PDMAUDIODIR_IN, false /* Disable */); + } + else + rc = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + { + if (pStream) /* Can be NULL if un-setting. */ + { + AssertPtr(pStream->pStream); + AssertMsg(pStream->pStream->enmDir == PDMAUDIODIR_IN, ("Specified stream is not an input stream\n")); + } + + pSink->In.pStreamRecSource = pStream; + + if (pSink->In.pStreamRecSource) + { + const PPDMIAUDIOCONNECTOR pConn = pSink->In.pStreamRecSource->pConn; + AssertPtr(pConn); + rc = pConn->pfnEnable(pConn, PDMAUDIODIR_IN, true /* Enable */); + } + } + + LogFunc(("[%s] Recording source is now '%s', rc=%Rrc\n", + pSink->pszName, pSink->In.pStreamRecSource ? pSink->In.pStreamRecSource->pszName : "<None>", rc)); + + return rc; +} + +/** + * Set the current recording source of an input mixer sink. + * + * @return IPRT status code. + * @param pSink Input mixer sink to set recording source for. + * @param pStream Mixer stream to set as current recording source. Must be an input stream. + * Set to NULL to un-set the current recording source. + */ +int AudioMixerSinkSetRecordingSource(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return rc; + + rc = audioMixerSinkSetRecSourceInternal(pSink, pStream); + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return rc; +} + +/** + * Sets the volume of an individual sink. + * + * @returns IPRT status code. + * @param pSink Sink to set volume for. + * @param pVol Volume to set. + */ +int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PPDMAUDIOVOLUME pVol) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + AssertPtrReturn(pVol, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return rc; + + memcpy(&pSink->Volume, pVol, sizeof(PDMAUDIOVOLUME)); + + LogFlowFunc(("[%s] fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n", + pSink->pszName, pSink->Volume.fMuted, pSink->Volume.uLeft, pSink->Volume.uRight)); + + LogRel2(("Mixer: Setting volume of sink '%s' to %RU8/%RU8 (%s)\n", + pSink->pszName, pVol->uLeft, pVol->uRight, pVol->fMuted ? "Muted" : "Unmuted")); + + AssertPtr(pSink->pParent); + rc = audioMixerSinkUpdateVolume(pSink, &pSink->pParent->VolMaster); + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return rc; +} + +/** + * Updates a mixer sink, internal version. + * + * @returns IPRT status code. + * @param pSink Mixer sink to update. + */ +static int audioMixerSinkUpdateInternal(PAUDMIXSINK pSink) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + +#ifdef LOG_ENABLED + char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus); + Log3Func(("[%s] fStatus=%s\n", pSink->pszName, pszStatus)); + RTStrFree(pszStatus); +#endif + + /* Sink disabled? Take a shortcut. */ + if (!(pSink->fStatus & AUDMIXSINK_STS_RUNNING)) + return rc; + + /* Input sink and no recording source set? Bail out early. */ + if ( pSink->enmDir == AUDMIXSINKDIR_INPUT + && pSink->In.pStreamRecSource == NULL) + return rc; + + /* Number of disabled streams of this sink. */ + uint8_t cStreamsDisabled = pSink->cStreams; + + /* Next, try to write (multiplex) as much audio data as possible to all connected mixer streams. */ + uint32_t cbToWriteToStreams = AudioMixBufUsedBytes(&pSink->MixBuf); + + uint8_t arrChunkBuf[_1K]; /** @todo Hm ... some zero copy / shared buffers would be nice! */ + while (cbToWriteToStreams) + { + uint32_t cfChunk; + rc = AudioMixBufAcquireReadBlock(&pSink->MixBuf, arrChunkBuf, RT_MIN(cbToWriteToStreams, sizeof(arrChunkBuf)), &cfChunk); + if (RT_FAILURE(rc)) + break; + + const uint32_t cbChunk = DrvAudioHlpFramesToBytes(cfChunk, &pSink->PCMProps); + Assert(cbChunk <= sizeof(arrChunkBuf)); + + /* Multiplex the current chunk in a synchronized fashion to all connected streams. */ + uint32_t cbChunkWrittenMin = 0; + rc = audioMixerSinkMultiplexSync(pSink, AUDMIXOP_COPY, arrChunkBuf, cbChunk, &cbChunkWrittenMin); + if (RT_SUCCESS(rc)) + { + PAUDMIXSTREAM pMixStream; + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + { + int rc2 = audioMixerSinkWriteToStream(pSink, pMixStream); + AssertRC(rc2); + } + } + + Log3Func(("[%s] cbChunk=%RU32, cbChunkWrittenMin=%RU32\n", pSink->pszName, cbChunk, cbChunkWrittenMin)); + + AudioMixBufReleaseReadBlock(&pSink->MixBuf, AUDIOMIXBUF_B2F(&pSink->MixBuf, cbChunkWrittenMin)); + + if ( RT_FAILURE(rc) + || cbChunkWrittenMin == 0) + break; + + Assert(cbToWriteToStreams >= cbChunkWrittenMin); + cbToWriteToStreams -= cbChunkWrittenMin; + } + + if ( !(pSink->fStatus & AUDMIXSINK_STS_DIRTY) + && AudioMixBufUsed(&pSink->MixBuf)) /* Still audio output data left? Consider the sink as being "dirty" then. */ + { + /* Set dirty bit. */ + pSink->fStatus |= AUDMIXSINK_STS_DIRTY; + } + + PAUDMIXSTREAM pMixStream, pMixStreamNext; + RTListForEachSafe(&pSink->lstStreams, pMixStream, pMixStreamNext, AUDMIXSTREAM, Node) + { + /* Input sink and not the recording source? Skip. */ + if ( pSink->enmDir == AUDMIXSINKDIR_INPUT + && pSink->In.pStreamRecSource != pMixStream) + continue; + + PPDMAUDIOSTREAM pStream = pMixStream->pStream; + AssertPtr(pStream); + + PPDMIAUDIOCONNECTOR pConn = pMixStream->pConn; + AssertPtr(pConn); + + uint32_t cfProc = 0; + + if (!DrvAudioHlpStreamStatusIsReady(pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream))) + continue; + + int rc2 = pConn->pfnStreamIterate(pConn, pStream); + if (RT_SUCCESS(rc2)) + { + if (pSink->enmDir == AUDMIXSINKDIR_INPUT) + { + rc2 = pConn->pfnStreamCapture(pConn, pStream, &cfProc); + if (RT_FAILURE(rc2)) + { + LogFunc(("%s: Failed capturing stream '%s', rc=%Rrc\n", pSink->pszName, pStream->szName, rc2)); + continue; + } + + if (cfProc) + pSink->fStatus |= AUDMIXSINK_STS_DIRTY; + } + else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT) + { + rc2 = pConn->pfnStreamPlay(pConn, pStream, &cfProc); + if (RT_FAILURE(rc2)) + { + LogFunc(("%s: Failed playing stream '%s', rc=%Rrc\n", pSink->pszName, pStream->szName, rc2)); + continue; + } + } + else + { + AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + continue; + } + } + + PDMAUDIOSTREAMSTS strmSts = pConn->pfnStreamGetStatus(pConn, pStream); + + /* Is the stream enabled or in pending disable state? + * Don't consider this stream as being disabled then. */ + if ( (strmSts & PDMAUDIOSTREAMSTS_FLAG_ENABLED) + || (strmSts & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE)) + { + cStreamsDisabled--; + } + + Log3Func(("\t%s: cPlayed/cCaptured=%RU32, rc2=%Rrc\n", pStream->szName, cfProc, rc2)); + } + + Log3Func(("[%s] fPendingDisable=%RTbool, %RU8/%RU8 streams disabled\n", + pSink->pszName, RT_BOOL(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE), cStreamsDisabled, pSink->cStreams)); + + /* Update last updated timestamp. */ + pSink->tsLastUpdatedMs = RTTimeMilliTS(); + + /* All streams disabled and the sink is in pending disable mode? */ + if ( cStreamsDisabled == pSink->cStreams + && (pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE)) + { + audioMixerSinkReset(pSink); + } + + return rc; +} + +/** + * Updates (invalidates) a mixer sink. + * + * @returns IPRT status code. + * @param pSink Mixer sink to update. + */ +int AudioMixerSinkUpdate(PAUDMIXSINK pSink) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return rc; + + rc = audioMixerSinkUpdateInternal(pSink); + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return rc; +} + +/** + * Updates the (master) volume of a mixer sink. + * + * @returns IPRT status code. + * @param pSink Mixer sink to update volume for. + * @param pVolMaster Master volume to set. + */ +static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + AssertPtrReturn(pVolMaster, VERR_INVALID_POINTER); + + LogFlowFunc(("[%s] Master fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n", + pSink->pszName, pVolMaster->fMuted, pVolMaster->uLeft, pVolMaster->uRight)); + LogFlowFunc(("[%s] fMuted=%RTbool, lVol=%RU32, rVol=%RU32 ", + pSink->pszName, pSink->Volume.fMuted, pSink->Volume.uLeft, pSink->Volume.uRight)); + + /** @todo Very crude implementation for now -- needs more work! */ + + pSink->VolumeCombined.fMuted = pVolMaster->fMuted || pSink->Volume.fMuted; + + pSink->VolumeCombined.uLeft = ( (pSink->Volume.uLeft ? pSink->Volume.uLeft : 1) + * (pVolMaster->uLeft ? pVolMaster->uLeft : 1)) / PDMAUDIO_VOLUME_MAX; + + pSink->VolumeCombined.uRight = ( (pSink->Volume.uRight ? pSink->Volume.uRight : 1) + * (pVolMaster->uRight ? pVolMaster->uRight : 1)) / PDMAUDIO_VOLUME_MAX; + + LogFlow(("-> fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n", + pSink->VolumeCombined.fMuted, pSink->VolumeCombined.uLeft, pSink->VolumeCombined.uRight)); + + /* Propagate new sink volume to all streams in the sink. */ + PAUDMIXSTREAM pMixStream; + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + { + int rc2 = pMixStream->pConn->pfnStreamSetVolume(pMixStream->pConn, pMixStream->pStream, &pSink->VolumeCombined); + AssertRC(rc2); + } + + return VINF_SUCCESS; +} + +/** + * Writes (buffered) output data of a sink's stream to the bound audio connector stream. + * + * @returns IPRT status code. + * @param pSink Sink of stream that contains the mixer stream. + * @param pMixStream Mixer stream to write output data for. + */ +static int audioMixerSinkWriteToStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream) +{ + if (!pMixStream->pCircBuf) + return VINF_SUCCESS; + + return audioMixerSinkWriteToStreamEx(pSink, pMixStream, (uint32_t)RTCircBufUsed(pMixStream->pCircBuf), NULL /* pcbWritten */); +} + +/** + * Writes (buffered) output data of a sink's stream to the bound audio connector stream, extended version. + * + * @returns IPRT status code. + * @param pSink Sink of stream that contains the mixer stream. + * @param pMixStream Mixer stream to write output data for. + * @param cbToWrite Size (in bytes) to write. + * @param pcbWritten Size (in bytes) written on success. Optional. + */ +static int audioMixerSinkWriteToStreamEx(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream, uint32_t cbToWrite, uint32_t *pcbWritten) +{ + /* pcbWritten is optional. */ + + if ( !cbToWrite + || !DrvAudioHlpStreamStatusCanWrite(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream))) + { + if (pcbWritten) + *pcbWritten = 0; + + return VINF_SUCCESS; + } + + PRTCIRCBUF pCircBuf = pMixStream->pCircBuf; + + const uint32_t cbWritableStream = pMixStream->pConn->pfnStreamGetWritable(pMixStream->pConn, pMixStream->pStream); + cbToWrite = RT_MIN(cbToWrite, RT_MIN((uint32_t)RTCircBufUsed(pCircBuf), cbWritableStream)); + + Log3Func(("[%s] cbWritableStream=%RU32, cbToWrite=%RU32\n", + pMixStream->pszName, cbWritableStream, cbToWrite)); + + uint32_t cbWritten = 0; + + int rc = VINF_SUCCESS; + + while (cbToWrite) + { + void *pvChunk; + size_t cbChunk; + RTCircBufAcquireReadBlock(pCircBuf, cbToWrite, &pvChunk, &cbChunk); + + Log3Func(("[%s] cbChunk=%RU32\n", pMixStream->pszName, cbChunk)); + + uint32_t cbChunkWritten = 0; + if (cbChunk) + { + rc = pMixStream->pConn->pfnStreamWrite(pMixStream->pConn, pMixStream->pStream, pvChunk, (uint32_t)cbChunk, + &cbChunkWritten); + if (RT_FAILURE(rc)) + { + if (rc == VERR_BUFFER_OVERFLOW) + { + LogRel2(("Mixer: Buffer overrun for mixer stream '%s' (sink '%s')\n", pMixStream->pszName, pSink->pszName)); + break; + } + else if (rc == VERR_AUDIO_STREAM_NOT_READY) + { + /* Stream is not enabled, just skip. */ + rc = VINF_SUCCESS; + } + else + LogRel2(("Mixer: Writing to mixer stream '%s' (sink '%s') failed, rc=%Rrc\n", + pMixStream->pszName, pSink->pszName, rc)); + + if (RT_FAILURE(rc)) + LogFunc(("[%s] Failed writing to stream '%s': %Rrc\n", pSink->pszName, pMixStream->pszName, rc)); + } + } + + RTCircBufReleaseReadBlock(pCircBuf, cbChunkWritten); + + if ( RT_FAILURE(rc) + || !cbChunkWritten) + break; + + Assert(cbToWrite >= cbChunkWritten); + cbToWrite -= (uint32_t)cbChunkWritten; + + cbWritten += (uint32_t)cbChunkWritten; + } + + Log3Func(("[%s] cbWritten=%RU32\n", pMixStream->pszName, cbWritten)); + + if (pcbWritten) + *pcbWritten = cbWritten; + +#ifdef DEBUG_andy + AssertRC(rc); +#endif + + return rc; +} + +/** + * Multiplexes audio output data to all connected mixer streams in a synchronized fashion, e.g. + * only multiplex as much data as all streams can handle at this time. + * + * @returns IPRT status code. + * @param pSink Sink to write audio output to. + * @param enmOp What mixing operation to use. Currently not implemented. + * @param pvBuf Pointer to audio data to write. + * @param cbBuf Size (in bytes) of audio data to write. + * @param pcbWrittenMin Returns minimum size (in bytes) successfully written to all mixer streams. Optional. + */ +static int audioMixerSinkMultiplexSync(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, + uint32_t *pcbWrittenMin) +{ + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + RT_NOREF(enmOp); + + AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT, + ("%s: Can't multiplex to a sink which is not an output sink\n", pSink->pszName)); + + int rc = VINF_SUCCESS; + + uint32_t cbToWriteMin = UINT32_MAX; + + Log3Func(("[%s] cbBuf=%RU32\n", pSink->pszName, cbBuf)); + + PAUDMIXSTREAM pMixStream; + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + { + if (!DrvAudioHlpStreamStatusCanWrite(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream))) + continue; + + cbToWriteMin = RT_MIN(cbBuf, RT_MIN(cbToWriteMin, (uint32_t)RTCircBufFree(pMixStream->pCircBuf))); + } + + if (cbToWriteMin == UINT32_MAX) /* No space at all? */ + cbToWriteMin = 0; + + if (cbToWriteMin) + { + RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node) + { + PRTCIRCBUF pCircBuf = pMixStream->pCircBuf; + void *pvChunk; + size_t cbChunk; + + uint32_t cbWrittenBuf = 0; + uint32_t cbToWriteBuf = cbToWriteMin; + + while (cbToWriteBuf) + { + RTCircBufAcquireWriteBlock(pCircBuf, cbToWriteBuf, &pvChunk, &cbChunk); + + if (cbChunk) + memcpy(pvChunk, (uint8_t *)pvBuf + cbWrittenBuf, cbChunk); + + RTCircBufReleaseWriteBlock(pCircBuf, cbChunk); + + cbWrittenBuf += (uint32_t)cbChunk; + Assert(cbWrittenBuf <= cbBuf); + + Assert(cbToWriteBuf >= cbChunk); + cbToWriteBuf -= (uint32_t)cbChunk; + } + + if (cbWrittenBuf) /* Update the mixer stream's last written time stamp. */ + pMixStream->tsLastReadWrittenNs = RTTimeNanoTS(); + + Log3Func(("[%s] Mixer stream '%s' -> cbWrittenBuf=%RU32\n", pSink->pszName, pMixStream->pszName, cbWrittenBuf)); + } + } + + Log3Func(("[%s] cbBuf=%RU32, cbToWriteMin=%RU32\n", pSink->pszName, cbBuf, cbToWriteMin)); + + if (pcbWrittenMin) + *pcbWrittenMin = cbToWriteMin; + + return rc; +} + +/** + * Writes data to a mixer sink. + * + * @returns IPRT status code. + * @param pSink Sink to write data to. + * @param enmOp Mixer operation to use when writing data to the sink. + * @param pvBuf Buffer containing the audio data to write. + * @param cbBuf Size (in bytes) of the buffer containing the audio data. + * @param pcbWritten Number of bytes written. Optional. + */ +int AudioMixerSinkWrite(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + RT_NOREF(enmOp); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn (cbBuf, VERR_INVALID_PARAMETER); + /* pcbWritten is optional. */ + + int rc = RTCritSectEnter(&pSink->CritSect); + if (RT_FAILURE(rc)) + return rc; + + AssertMsg(pSink->fStatus & AUDMIXSINK_STS_RUNNING, + ("%s: Can't write to a sink which is not running (anymore) (status 0x%x)\n", pSink->pszName, pSink->fStatus)); + AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT, + ("%s: Can't write to a sink which is not an output sink\n", pSink->pszName)); + + Assert(cbBuf <= AudioMixBufFreeBytes(&pSink->MixBuf)); + + uint32_t cbWritten = 0; + uint32_t cbToWrite = cbBuf; + while (cbToWrite) + { + /* First, write the data to the mixer sink's own mixing buffer. + * Here the audio data can be transformed into the mixer sink's format. */ + uint32_t cfWritten = 0; + rc = AudioMixBufWriteCirc(&pSink->MixBuf, (uint8_t *)pvBuf + cbWritten, cbToWrite, &cfWritten); + if (RT_FAILURE(rc)) + break; + + const uint32_t cbWrittenChunk = DrvAudioHlpFramesToBytes(cfWritten, &pSink->PCMProps); + + Assert(cbToWrite >= cbWrittenChunk); + cbToWrite -= cbWrittenChunk; + cbWritten += cbWrittenChunk; + } + + Assert(cbWritten == cbBuf); + + /* Update the sink's last written time stamp. */ + pSink->tsLastReadWrittenNs = RTTimeNanoTS(); + + if (pcbWritten) + *pcbWritten = cbWritten; + + int rc2 = RTCritSectLeave(&pSink->CritSect); + AssertRC(rc2); + + return rc; +} + +/********************************************************************************************************************************* + * Mixer Stream implementation. + ********************************************************************************************************************************/ + +/** + * Controls a mixer stream, internal version. + * + * @returns IPRT status code. + * @param pMixStream Mixer stream to control. + * @param enmCmd Mixer stream command to use. + * @param fCtl Additional control flags. Pass 0. + */ +int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl) +{ + AssertPtr(pMixStream->pConn); + AssertPtr(pMixStream->pStream); + + RT_NOREF(fCtl); + + int rc = pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, enmCmd); + + LogFlowFunc(("[%s] enmCmd=%ld, rc=%Rrc\n", pMixStream->pszName, enmCmd, rc)); + + return rc; +} + +/** + * Controls a mixer stream. + * + * @returns IPRT status code. + * @param pMixStream Mixer stream to control. + * @param enmCmd Mixer stream command to use. + * @param fCtl Additional control flags. Pass 0. + */ +int AudioMixerStreamCtl(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl) +{ + RT_NOREF(fCtl); + AssertPtrReturn(pMixStream, VERR_INVALID_POINTER); + /** @todo Validate fCtl. */ + + int rc = RTCritSectEnter(&pMixStream->CritSect); + if (RT_FAILURE(rc)) + return rc; + + rc = audioMixerStreamCtlInternal(pMixStream, enmCmd, fCtl); + + int rc2 = RTCritSectLeave(&pMixStream->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + return rc; +} + +/** + * Destroys a mixer stream, internal version. + * + * @param pMixStream Mixer stream to destroy. + */ +static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pMixStream) +{ + AssertPtrReturnVoid(pMixStream); + + LogFunc(("%s\n", pMixStream->pszName)); + + if (pMixStream->pConn) /* Stream has a connector interface present? */ + { + if (pMixStream->pStream) + { + pMixStream->pConn->pfnStreamRelease(pMixStream->pConn, pMixStream->pStream); + pMixStream->pConn->pfnStreamDestroy(pMixStream->pConn, pMixStream->pStream); + + pMixStream->pStream = NULL; + } + + pMixStream->pConn = NULL; + } + + if (pMixStream->pszName) + { + RTStrFree(pMixStream->pszName); + pMixStream->pszName = NULL; + } + + if (pMixStream->pCircBuf) + { + RTCircBufDestroy(pMixStream->pCircBuf); + pMixStream->pCircBuf = NULL; + } + + int rc2 = RTCritSectDelete(&pMixStream->CritSect); + AssertRC(rc2); + + RTMemFree(pMixStream); + pMixStream = NULL; +} + +/** + * Destroys a mixer stream. + * + * @param pMixStream Mixer stream to destroy. + */ +void AudioMixerStreamDestroy(PAUDMIXSTREAM pMixStream) +{ + if (!pMixStream) + return; + + int rc2 = RTCritSectEnter(&pMixStream->CritSect); + AssertRC(rc2); + + LogFunc(("%s\n", pMixStream->pszName)); + + if (pMixStream->pSink) /* Is the stream part of a sink? */ + { + /* Save sink pointer, as after audioMixerSinkRemoveStreamInternal() the + * pointer will be gone from the stream. */ + PAUDMIXSINK pSink = pMixStream->pSink; + + rc2 = audioMixerSinkRemoveStreamInternal(pSink, pMixStream); + if (RT_SUCCESS(rc2)) + { + Assert(pSink->cStreams); + pSink->cStreams--; + } + } + else + rc2 = VINF_SUCCESS; + + int rc3 = RTCritSectLeave(&pMixStream->CritSect); + AssertRC(rc3); + + if (RT_SUCCESS(rc2)) + { + audioMixerStreamDestroyInternal(pMixStream); + pMixStream = NULL; + } + + LogFlowFunc(("Returning %Rrc\n", rc2)); +} + +/** + * Returns whether a mixer stream currently is active (playing/recording) or not. + * + * @returns @c true if playing/recording, @c false if not. + * @param pMixStream Mixer stream to return status for. + */ +bool AudioMixerStreamIsActive(PAUDMIXSTREAM pMixStream) +{ + int rc2 = RTCritSectEnter(&pMixStream->CritSect); + if (RT_FAILURE(rc2)) + return false; + + AssertPtr(pMixStream->pConn); + AssertPtr(pMixStream->pStream); + + bool fIsActive; + + if ( pMixStream->pConn + && pMixStream->pStream + && RT_BOOL(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTREAMSTS_FLAG_ENABLED)) + { + fIsActive = true; + } + else + fIsActive = false; + + rc2 = RTCritSectLeave(&pMixStream->CritSect); + AssertRC(rc2); + + return fIsActive; +} + +/** + * Returns whether a mixer stream is valid (e.g. initialized and in a working state) or not. + * + * @returns @c true if valid, @c false if not. + * @param pMixStream Mixer stream to return status for. + */ +bool AudioMixerStreamIsValid(PAUDMIXSTREAM pMixStream) +{ + if (!pMixStream) + return false; + + int rc2 = RTCritSectEnter(&pMixStream->CritSect); + if (RT_FAILURE(rc2)) + return false; + + bool fIsValid; + + if ( pMixStream->pConn + && pMixStream->pStream + && RT_BOOL(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED)) + { + fIsValid = true; + } + else + fIsValid = false; + + rc2 = RTCritSectLeave(&pMixStream->CritSect); + AssertRC(rc2); + + return fIsValid; +} + diff --git a/src/VBox/Devices/Audio/AudioMixer.h b/src/VBox/Devices/Audio/AudioMixer.h new file mode 100644 index 00000000..7f62d3c7 --- /dev/null +++ b/src/VBox/Devices/Audio/AudioMixer.h @@ -0,0 +1,263 @@ +/* $Id: AudioMixer.h $ */ +/** @file + * VBox audio - Mixing routines. + * + * The mixing routines are mainly used by the various audio device emulations + * to achieve proper multiplexing from/to attached devices LUNs. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_AudioMixer_h +#define VBOX_INCLUDED_SRC_Audio_AudioMixer_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/critsect.h> + +#include <VBox/vmm/pdmaudioifs.h> + +/** + * Structure for maintaining an audio mixer instance. + */ +typedef struct AUDIOMIXER +{ + /** The mixer's name. */ + char *pszName; + /** The mixer's critical section. */ + RTCRITSECT CritSect; + /** The master volume of this mixer. */ + PDMAUDIOVOLUME VolMaster; + /** List of audio mixer sinks. */ + RTLISTANCHOR lstSinks; + /** Number of used audio sinks. */ + uint8_t cSinks; +} AUDIOMIXER, *PAUDIOMIXER; + +/** Defines an audio mixer stream's flags. */ +#define AUDMIXSTREAMFLAGS uint32_t + +/** No flags specified. */ +#define AUDMIXSTREAM_FLAG_NONE 0 + +/** Prototype needed for AUDMIXSTREAM struct definition. */ +typedef struct AUDMIXSINK *PAUDMIXSINK; + +/** + * Structure for maintaining an audio mixer stream. + */ +typedef struct AUDMIXSTREAM +{ + /** List node. */ + RTLISTNODE Node; + /** Name of this stream. */ + char *pszName; + /** The streams's critical section. */ + RTCRITSECT CritSect; + /** Sink this stream is attached to. */ + PAUDMIXSINK pSink; + /** Stream flags of type AUDMIXSTREAM_FLAG_. */ + uint32_t fFlags; + /** Pointer to audio connector being used. */ + PPDMIAUDIOCONNECTOR pConn; + /** Pointer to PDM audio stream this mixer stream handles. */ + PPDMAUDIOSTREAM pStream; + /** Last read (recording) / written (playback) timestamp (in ns). */ + uint64_t tsLastReadWrittenNs; + /** The stream's circular buffer for temporarily + * holding (raw) device audio data. */ + PRTCIRCBUF pCircBuf; +} AUDMIXSTREAM, *PAUDMIXSTREAM; + +/** Defines an audio sink's current status. */ +#define AUDMIXSINKSTS uint32_t + +/** No status specified. */ +#define AUDMIXSINK_STS_NONE 0 +/** The sink is active and running. */ +#define AUDMIXSINK_STS_RUNNING RT_BIT(0) +/** The sink is in a pending disable state. */ +#define AUDMIXSINK_STS_PENDING_DISABLE RT_BIT(1) +/** Dirty flag. + * For output sinks this means that there is data in the + * sink which has not been played yet. + * For input sinks this means that there is data in the + * sink which has been recorded but not transferred to the + * destination yet. */ +#define AUDMIXSINK_STS_DIRTY RT_BIT(2) + +/** + * Audio mixer sink direction. + */ +typedef enum AUDMIXSINKDIR +{ + /** Unknown direction. */ + AUDMIXSINKDIR_UNKNOWN = 0, + /** Input (capturing from a device). */ + AUDMIXSINKDIR_INPUT, + /** Output (playing to a device). */ + AUDMIXSINKDIR_OUTPUT, + /** The usual 32-bit hack. */ + AUDMIXSINKDIR_32BIT_HACK = 0x7fffffff +} AUDMIXSINKDIR; + +/** + * Audio mixer sink command. + */ +typedef enum AUDMIXSINKCMD +{ + /** Unknown command, do not use. */ + AUDMIXSINKCMD_UNKNOWN = 0, + /** Enables the sink. */ + AUDMIXSINKCMD_ENABLE, + /** Disables the sink. */ + AUDMIXSINKCMD_DISABLE, + /** Pauses the sink. */ + AUDMIXSINKCMD_PAUSE, + /** Resumes the sink. */ + AUDMIXSINKCMD_RESUME, + /** Tells the sink's streams to drop all (buffered) data immediately. */ + AUDMIXSINKCMD_DROP, + /** Hack to blow the type up to 32-bit. */ + AUDMIXSINKCMD_32BIT_HACK = 0x7fffffff +} AUDMIXSINKCMD; + +/** + * Structure for keeping audio input sink specifics. + * Do not use directly. Instead, use AUDMIXSINK. + */ +typedef struct AUDMIXSINKIN +{ + /** The current recording source. Can be NULL if not set. */ + PAUDMIXSTREAM pStreamRecSource; +} AUDMIXSINKIN; + +/** + * Structure for keeping audio output sink specifics. + * Do not use directly. Instead, use AUDMIXSINK. + */ +typedef struct AUDMIXSINKOUT +{ +} AUDMIXSINKOUT; + +/** + * Structure for maintaining an audio mixer sink. + */ +typedef struct AUDMIXSINK +{ + RTLISTNODE Node; + /** Pointer to mixer object this sink is bound to. */ + PAUDIOMIXER pParent; + /** Name of this sink. */ + char *pszName; + /** The sink direction, that is, + * if this sink handles input or output. */ + AUDMIXSINKDIR enmDir; + /** The sink's critical section. */ + RTCRITSECT CritSect; + /** This sink's mixing buffer, acting as + * a parent buffer for all streams this sink owns. */ + PDMAUDIOMIXBUF MixBuf; + /** Union for input/output specifics. */ + union + { + AUDMIXSINKIN In; + AUDMIXSINKOUT Out; + }; + /** Sink status of type AUDMIXSINK_STS_XXX. */ + AUDMIXSINKSTS fStatus; + /** The sink's PCM format. */ + PDMAUDIOPCMPROPS PCMProps; + /** Number of streams assigned. */ + uint8_t cStreams; + /** List of assigned streams. + * Note: All streams have the same PCM properties, so the + * mixer does not do any conversion. */ + /** @todo Use something faster -- vector maybe? */ + RTLISTANCHOR lstStreams; + /** The volume of this sink. The volume always will + * be combined with the mixer's master volume. */ + PDMAUDIOVOLUME Volume; + /** The volume of this sink, combined with the last set master volume. */ + PDMAUDIOVOLUME VolumeCombined; + /** Timestamp since last update (in ms). */ + uint64_t tsLastUpdatedMs; + /** Last read (recording) / written (playback) timestamp (in ns). */ + uint64_t tsLastReadWrittenNs; +#ifdef VBOX_AUDIO_MIXER_DEBUG + struct + { + PPDMAUDIOFILE pFile; + } Dbg; +#endif +} AUDMIXSINK, *PAUDMIXSINK; + +/** + * Audio mixer operation. + */ +typedef enum AUDMIXOP +{ + /** Invalid operation, do not use. */ + AUDMIXOP_INVALID = 0, + /** Copy data from A to B, overwriting data in B. */ + AUDMIXOP_COPY, + /** Blend data from A with (existing) data in B. */ + AUDMIXOP_BLEND, + /** The usual 32-bit hack. */ + AUDMIXOP_32BIT_HACK = 0x7fffffff +} AUDMIXOP; + +/** No flags specified. */ +#define AUDMIXSTRMCTL_FLAG_NONE 0 + +int AudioMixerCreate(const char *pszName, uint32_t uFlags, PAUDIOMIXER *ppMixer); +int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, AUDMIXSINKDIR enmDir, PAUDMIXSINK *ppSink); +void AudioMixerDestroy(PAUDIOMIXER pMixer); +void AudioMixerInvalidate(PAUDIOMIXER pMixer); +void AudioMixerRemoveSink(PAUDIOMIXER pMixer, PAUDMIXSINK pSink); +int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PPDMAUDIOVOLUME pVol); +void AudioMixerDebug(PAUDIOMIXER pMixer, PCDBGFINFOHLP pHlp, const char *pszArgs); + +int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream); +int AudioMixerSinkCreateStream(PAUDMIXSINK pSink, PPDMIAUDIOCONNECTOR pConnector, PPDMAUDIOSTREAMCFG pCfg, AUDMIXSTREAMFLAGS fFlags, PAUDMIXSTREAM *ppStream); +int AudioMixerSinkCtl(PAUDMIXSINK pSink, AUDMIXSINKCMD enmCmd); +void AudioMixerSinkDestroy(PAUDMIXSINK pSink); +uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink); +uint32_t AudioMixerSinkGetWritable(PAUDMIXSINK pSink); +AUDMIXSINKDIR AudioMixerSinkGetDir(PAUDMIXSINK pSink); +const char *AudioMixerSinkGetName(const PAUDMIXSINK pSink); +PAUDMIXSTREAM AudioMixerSinkGetRecordingSource(PAUDMIXSINK pSink); +PAUDMIXSTREAM AudioMixerSinkGetStream(PAUDMIXSINK pSink, uint8_t uIndex); +AUDMIXSINKSTS AudioMixerSinkGetStatus(PAUDMIXSINK pSink); +uint8_t AudioMixerSinkGetStreamCount(PAUDMIXSINK pSink); +bool AudioMixerSinkIsActive(PAUDMIXSINK pSink); +int AudioMixerSinkRead(PAUDMIXSINK pSink, AUDMIXOP enmOp, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead); +void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream); +void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink); +void AudioMixerSinkReset(PAUDMIXSINK pSink); +void AudioMixerSinkGetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps); +int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps); +int AudioMixerSinkSetRecordingSource(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream); +int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PPDMAUDIOVOLUME pVol); +int AudioMixerSinkWrite(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten); +int AudioMixerSinkUpdate(PAUDMIXSINK pSink); + +int AudioMixerStreamCtl(PAUDMIXSTREAM pStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl); +void AudioMixerStreamDestroy(PAUDMIXSTREAM pStream); +bool AudioMixerStreamIsActive(PAUDMIXSTREAM pStream); +bool AudioMixerStreamIsValid(PAUDMIXSTREAM pStream); + +#endif /* !VBOX_INCLUDED_SRC_Audio_AudioMixer_h */ + diff --git a/src/VBox/Devices/Audio/DevHDA.cpp b/src/VBox/Devices/Audio/DevHDA.cpp new file mode 100644 index 00000000..45679ac2 --- /dev/null +++ b/src/VBox/Devices/Audio/DevHDA.cpp @@ -0,0 +1,5414 @@ +/* $Id: DevHDA.cpp $ */ +/** @file + * DevHDA.cpp - VBox Intel HD Audio Controller. + * + * Implemented against the specifications found in "High Definition Audio + * Specification", Revision 1.0a June 17, 2010, and "Intel I/O Controller + * HUB 6 (ICH6) Family, Datasheet", document number 301473-002. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef DEBUG_bird +# define RT_NO_STRICT /* I'm tried of this crap asserting on save and restore of Maverics guests. */ +#endif +#define LOG_GROUP LOG_GROUP_DEV_HDA +#include <VBox/log.h> + +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdmaudioifs.h> +#include <VBox/version.h> +#include <VBox/AssertGuest.h> + +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/asm-math.h> +#include <iprt/file.h> +#include <iprt/list.h> +# include <iprt/string.h> +#ifdef IN_RING3 +# include <iprt/mem.h> +# include <iprt/semaphore.h> +# include <iprt/uuid.h> +#endif + +#include "VBoxDD.h" + +#include "AudioMixBuffer.h" +#include "AudioMixer.h" + +#include "DevHDA.h" +#include "DevHDACommon.h" + +#include "HDACodec.h" +#include "HDAStream.h" +#include "HDAStreamMap.h" +#include "HDAStreamPeriod.h" + +#include "DrvAudio.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +//#define HDA_AS_PCI_EXPRESS + +/* Installs a DMA access handler (via PGM callback) to monitor + * HDA's DMA operations, that is, writing / reading audio stream data. + * + * !!! Note: Certain guests are *that* timing sensitive that when enabling !!! + * !!! such a handler will mess up audio completely (e.g. Windows 7). !!! */ +//#define HDA_USE_DMA_ACCESS_HANDLER +#ifdef HDA_USE_DMA_ACCESS_HANDLER +# include <VBox/vmm/pgm.h> +#endif + +/* Uses the DMA access handler to read the written DMA audio (output) data. + * Only valid if HDA_USE_DMA_ACCESS_HANDLER is set. + * + * Also see the note / warning for HDA_USE_DMA_ACCESS_HANDLER. */ +//# define HDA_USE_DMA_ACCESS_HANDLER_WRITING + +/* Useful to debug the device' timing. */ +//#define HDA_DEBUG_TIMING + +/* To debug silence coming from the guest in form of audio gaps. + * Very crude implementation for now. */ +//#define HDA_DEBUG_SILENCE + +#if defined(VBOX_WITH_HP_HDA) +/* HP Pavilion dv4t-1300 */ +# define HDA_PCI_VENDOR_ID 0x103c +# define HDA_PCI_DEVICE_ID 0x30f7 +#elif defined(VBOX_WITH_INTEL_HDA) +/* Intel HDA controller */ +# define HDA_PCI_VENDOR_ID 0x8086 +# define HDA_PCI_DEVICE_ID 0x2668 +#elif defined(VBOX_WITH_NVIDIA_HDA) +/* nVidia HDA controller */ +# define HDA_PCI_VENDOR_ID 0x10de +# define HDA_PCI_DEVICE_ID 0x0ac0 +#else +# error "Please specify your HDA device vendor/device IDs" +#endif + +/** + * Acquires the HDA lock. + */ +#define DEVHDA_LOCK(a_pThis) \ + do { \ + int rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, VERR_IGNORED); \ + AssertRC(rcLock); \ + } while (0) + +/** + * Acquires the HDA lock or returns. + */ +# define DEVHDA_LOCK_RETURN(a_pThis, a_rcBusy) \ + do { \ + int rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, a_rcBusy); \ + if (rcLock != VINF_SUCCESS) \ + { \ + AssertRC(rcLock); \ + return rcLock; \ + } \ + } while (0) + +/** + * Acquires the HDA lock or returns. + */ +# define DEVHDA_LOCK_RETURN_VOID(a_pThis) \ + do { \ + int rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, VERR_IGNORED); \ + if (rcLock != VINF_SUCCESS) \ + { \ + AssertRC(rcLock); \ + return; \ + } \ + } while (0) + +/** + * Releases the HDA lock. + */ +#define DEVHDA_UNLOCK(a_pThis) \ + do { PDMCritSectLeave(&(a_pThis)->CritSect); } while (0) + +/** + * Acquires the TM lock and HDA lock, returns on failure. + */ +#define DEVHDA_LOCK_BOTH_RETURN_VOID(a_pThis, a_SD) \ + do { \ + int rcLock = TMTimerLock((a_pThis)->pTimer[a_SD], VERR_IGNORED); \ + if (rcLock != VINF_SUCCESS) \ + { \ + AssertRC(rcLock); \ + return; \ + } \ + rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, VERR_IGNORED); \ + if (rcLock != VINF_SUCCESS) \ + { \ + AssertRC(rcLock); \ + TMTimerUnlock((a_pThis)->pTimer[a_SD]); \ + return; \ + } \ + } while (0) + +/** + * Acquires the TM lock and HDA lock, returns on failure. + */ +#define DEVHDA_LOCK_BOTH_RETURN(a_pThis, a_SD, a_rcBusy) \ + do { \ + int rcLock = TMTimerLock((a_pThis)->pTimer[a_SD], (a_rcBusy)); \ + if (rcLock != VINF_SUCCESS) \ + return rcLock; \ + rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, (a_rcBusy)); \ + if (rcLock != VINF_SUCCESS) \ + { \ + AssertRC(rcLock); \ + TMTimerUnlock((a_pThis)->pTimer[a_SD]); \ + return rcLock; \ + } \ + } while (0) + +/** + * Releases the HDA lock and TM lock. + */ +#define DEVHDA_UNLOCK_BOTH(a_pThis, a_SD) \ + do { \ + PDMCritSectLeave(&(a_pThis)->CritSect); \ + TMTimerUnlock((a_pThis)->pTimer[a_SD]); \ + } while (0) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Structure defining a (host backend) driver stream. + * Each driver has its own instances of audio mixer streams, which then + * can go into the same (or even different) audio mixer sinks. + */ +typedef struct HDADRIVERSTREAM +{ + /** Associated mixer handle. */ + R3PTRTYPE(PAUDMIXSTREAM) pMixStrm; +} HDADRIVERSTREAM, *PHDADRIVERSTREAM; + +#ifdef HDA_USE_DMA_ACCESS_HANDLER +/** + * Struct for keeping an HDA DMA access handler context. + */ +typedef struct HDADMAACCESSHANDLER +{ + /** Node for storing this handler in our list in HDASTREAMSTATE. */ + RTLISTNODER3 Node; + /** Pointer to stream to which this access handler is assigned to. */ + R3PTRTYPE(PHDASTREAM) pStream; + /** Access handler type handle. */ + PGMPHYSHANDLERTYPE hAccessHandlerType; + /** First address this handler uses. */ + RTGCPHYS GCPhysFirst; + /** Last address this handler uses. */ + RTGCPHYS GCPhysLast; + /** Actual BDLE address to handle. */ + RTGCPHYS BDLEAddr; + /** Actual BDLE buffer size to handle. */ + RTGCPHYS BDLESize; + /** Whether the access handler has been registered or not. */ + bool fRegistered; + uint8_t Padding[3]; +} HDADMAACCESSHANDLER, *PHDADMAACCESSHANDLER; +#endif + +/** + * Struct for maintaining a host backend driver. + * This driver must be associated to one, and only one, + * HDA codec. The HDA controller does the actual multiplexing + * of HDA codec data to various host backend drivers then. + * + * This HDA device uses a timer in order to synchronize all + * read/write accesses across all attached LUNs / backends. + */ +typedef struct HDADRIVER +{ + /** Node for storing this driver in our device driver list of HDASTATE. */ + RTLISTNODER3 Node; + /** Pointer to HDA controller (state). */ + R3PTRTYPE(PHDASTATE) pHDAState; + /** Driver flags. */ + PDMAUDIODRVFLAGS fFlags; + uint8_t u32Padding0[2]; + /** LUN to which this driver has been assigned. */ + uint8_t uLUN; + /** Whether this driver is in an attached state or not. */ + bool fAttached; + /** Pointer to attached driver base interface. */ + R3PTRTYPE(PPDMIBASE) pDrvBase; + /** Audio connector interface to the underlying host backend. */ + R3PTRTYPE(PPDMIAUDIOCONNECTOR) pConnector; + /** Mixer stream for line input. */ + HDADRIVERSTREAM LineIn; +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + /** Mixer stream for mic input. */ + HDADRIVERSTREAM MicIn; +#endif + /** Mixer stream for front output. */ + HDADRIVERSTREAM Front; +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + /** Mixer stream for center/LFE output. */ + HDADRIVERSTREAM CenterLFE; + /** Mixer stream for rear output. */ + HDADRIVERSTREAM Rear; +#endif +} HDADRIVER; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifndef VBOX_DEVICE_STRUCT_TESTCASE +#ifdef IN_RING3 +static void hdaR3GCTLReset(PHDASTATE pThis); +#endif + +/** @name Register read/write stubs. + * @{ + */ +static int hdaRegReadUnimpl(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value); +static int hdaRegWriteUnimpl(PHDASTATE pThis, uint32_t iReg, uint32_t pu32Value); +/** @} */ + +/** @name Global register set read/write functions. + * @{ + */ +static int hdaRegWriteGCTL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegReadLPIB(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value); +static int hdaRegReadWALCLK(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value); +static int hdaRegWriteCORBWP(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteCORBRP(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteCORBCTL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteCORBSIZE(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteCORBSTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteRINTCNT(PHDASTATE pThis, uint32_t iReg, uint32_t pu32Value); +static int hdaRegWriteRIRBWP(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteRIRBSTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteSTATESTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteIRS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegReadIRS(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value); +static int hdaRegWriteBase(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +/** @} */ + +/** @name {IOB}SDn write functions. + * @{ + */ +static int hdaRegWriteSDCBL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteSDCTL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteSDSTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteSDLVI(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteSDFIFOW(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteSDFIFOS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteSDFMT(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteSDBDPL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegWriteSDBDPU(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +/** @} */ + +/** @name Generic register read/write functions. + * @{ + */ +static int hdaRegReadU32(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value); +static int hdaRegWriteU32(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegReadU24(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value); +#ifdef IN_RING3 +static int hdaRegWriteU24(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +#endif +static int hdaRegReadU16(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value); +static int hdaRegWriteU16(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +static int hdaRegReadU8(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value); +static int hdaRegWriteU8(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); +/** @} */ + +/** @name HDA device functions. + * @{ + */ +#ifdef IN_RING3 +static int hdaR3AddStream(PHDASTATE pThis, PPDMAUDIOSTREAMCFG pCfg); +static int hdaR3RemoveStream(PHDASTATE pThis, PPDMAUDIOSTREAMCFG pCfg); +# ifdef HDA_USE_DMA_ACCESS_HANDLER +static DECLCALLBACK(VBOXSTRICTRC) hdaR3DMAAccessHandler(PVM pVM, PVMCPU pVCpu, RTGCPHYS GCPhys, void *pvPhys, + void *pvBuf, size_t cbBuf, + PGMACCESSTYPE enmAccessType, PGMACCESSORIGIN enmOrigin, void *pvUser); +# endif +#endif /* IN_RING3 */ +/** @} */ + +/** @name HDA mixer functions. + * @{ + */ +#ifdef IN_RING3 +static int hdaR3MixerAddDrvStream(PHDASTATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PHDADRIVER pDrv); +#endif +/** @} */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** No register description (RD) flags defined. */ +#define HDA_RD_FLAG_NONE 0 +/** Writes to SD are allowed while RUN bit is set. */ +#define HDA_RD_FLAG_SD_WRITE_RUN RT_BIT(0) + +/** Emits a single audio stream register set (e.g. OSD0) at a specified offset. */ +#define HDA_REG_MAP_STRM(offset, name) \ + /* offset size read mask write mask flags read callback write callback index + abbrev description */ \ + /* ------- ------- ---------- ---------- ------------------------- -------------- ----------------- ----------------------------- ----------- */ \ + /* Offset 0x80 (SD0) */ \ + { offset, 0x00003, 0x00FF001F, 0x00F0001F, HDA_RD_FLAG_SD_WRITE_RUN, hdaRegReadU24 , hdaRegWriteSDCTL , HDA_REG_IDX_STRM(name, CTL) , #name " Stream Descriptor Control" }, \ + /* Offset 0x83 (SD0) */ \ + { offset + 0x3, 0x00001, 0x0000003C, 0x0000001C, HDA_RD_FLAG_SD_WRITE_RUN, hdaRegReadU8 , hdaRegWriteSDSTS , HDA_REG_IDX_STRM(name, STS) , #name " Status" }, \ + /* Offset 0x84 (SD0) */ \ + { offset + 0x4, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadLPIB, hdaRegWriteU32 , HDA_REG_IDX_STRM(name, LPIB) , #name " Link Position In Buffer" }, \ + /* Offset 0x88 (SD0) */ \ + { offset + 0x8, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteSDCBL , HDA_REG_IDX_STRM(name, CBL) , #name " Cyclic Buffer Length" }, \ + /* Offset 0x8C (SD0) */ \ + { offset + 0xC, 0x00002, 0x0000FFFF, 0x0000FFFF, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteSDLVI , HDA_REG_IDX_STRM(name, LVI) , #name " Last Valid Index" }, \ + /* Reserved: FIFO Watermark. ** @todo Document this! */ \ + { offset + 0xE, 0x00002, 0x00000007, 0x00000007, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteSDFIFOW, HDA_REG_IDX_STRM(name, FIFOW), #name " FIFO Watermark" }, \ + /* Offset 0x90 (SD0) */ \ + { offset + 0x10, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteSDFIFOS, HDA_REG_IDX_STRM(name, FIFOS), #name " FIFO Size" }, \ + /* Offset 0x92 (SD0) */ \ + { offset + 0x12, 0x00002, 0x00007F7F, 0x00007F7F, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteSDFMT , HDA_REG_IDX_STRM(name, FMT) , #name " Stream Format" }, \ + /* Reserved: 0x94 - 0x98. */ \ + /* Offset 0x98 (SD0) */ \ + { offset + 0x18, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteSDBDPL , HDA_REG_IDX_STRM(name, BDPL) , #name " Buffer Descriptor List Pointer-Lower Base Address" }, \ + /* Offset 0x9C (SD0) */ \ + { offset + 0x1C, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteSDBDPU , HDA_REG_IDX_STRM(name, BDPU) , #name " Buffer Descriptor List Pointer-Upper Base Address" } + +/** Defines a single audio stream register set (e.g. OSD0). */ +#define HDA_REG_MAP_DEF_STREAM(index, name) \ + HDA_REG_MAP_STRM(HDA_REG_DESC_SD0_BASE + (index * 32 /* 0x20 */), name) + +/* See 302349 p 6.2. */ +const HDAREGDESC g_aHdaRegMap[HDA_NUM_REGS] = +{ + /* offset size read mask write mask flags read callback write callback index + abbrev */ + /*------- ------- ---------- ---------- ----------------- ---------------- ------------------- ------------------------ */ + { 0x00000, 0x00002, 0x0000FFFB, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , HDA_REG_IDX(GCAP) }, /* Global Capabilities */ + { 0x00002, 0x00001, 0x000000FF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , HDA_REG_IDX(VMIN) }, /* Minor Version */ + { 0x00003, 0x00001, 0x000000FF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , HDA_REG_IDX(VMAJ) }, /* Major Version */ + { 0x00004, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteU16 , HDA_REG_IDX(OUTPAY) }, /* Output Payload Capabilities */ + { 0x00006, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , HDA_REG_IDX(INPAY) }, /* Input Payload Capabilities */ + { 0x00008, 0x00004, 0x00000103, 0x00000103, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteGCTL , HDA_REG_IDX(GCTL) }, /* Global Control */ + { 0x0000c, 0x00002, 0x00007FFF, 0x00007FFF, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteU16 , HDA_REG_IDX(WAKEEN) }, /* Wake Enable */ + { 0x0000e, 0x00002, 0x00000007, 0x00000007, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteSTATESTS, HDA_REG_IDX(STATESTS) }, /* State Change Status */ + { 0x00010, 0x00002, 0xFFFFFFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadUnimpl, hdaRegWriteUnimpl , HDA_REG_IDX(GSTS) }, /* Global Status */ + { 0x00018, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteU16 , HDA_REG_IDX(OUTSTRMPAY) }, /* Output Stream Payload Capability */ + { 0x0001A, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , HDA_REG_IDX(INSTRMPAY) }, /* Input Stream Payload Capability */ + { 0x00020, 0x00004, 0xC00000FF, 0xC00000FF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteU32 , HDA_REG_IDX(INTCTL) }, /* Interrupt Control */ + { 0x00024, 0x00004, 0xC00000FF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , HDA_REG_IDX(INTSTS) }, /* Interrupt Status */ + { 0x00030, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadWALCLK, hdaRegWriteUnimpl , HDA_REG_IDX_NOMEM(WALCLK) }, /* Wall Clock Counter */ + { 0x00034, 0x00004, 0x000000FF, 0x000000FF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteU32 , HDA_REG_IDX(SSYNC) }, /* Stream Synchronization */ + { 0x00040, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(CORBLBASE) }, /* CORB Lower Base Address */ + { 0x00044, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(CORBUBASE) }, /* CORB Upper Base Address */ + { 0x00048, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteCORBWP , HDA_REG_IDX(CORBWP) }, /* CORB Write Pointer */ + { 0x0004A, 0x00002, 0x000080FF, 0x00008000, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteCORBRP , HDA_REG_IDX(CORBRP) }, /* CORB Read Pointer */ + { 0x0004C, 0x00001, 0x00000003, 0x00000003, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteCORBCTL , HDA_REG_IDX(CORBCTL) }, /* CORB Control */ + { 0x0004D, 0x00001, 0x00000001, 0x00000001, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteCORBSTS , HDA_REG_IDX(CORBSTS) }, /* CORB Status */ + { 0x0004E, 0x00001, 0x000000F3, 0x00000003, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteCORBSIZE, HDA_REG_IDX(CORBSIZE) }, /* CORB Size */ + { 0x00050, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(RIRBLBASE) }, /* RIRB Lower Base Address */ + { 0x00054, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(RIRBUBASE) }, /* RIRB Upper Base Address */ + { 0x00058, 0x00002, 0x000000FF, 0x00008000, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteRIRBWP , HDA_REG_IDX(RIRBWP) }, /* RIRB Write Pointer */ + { 0x0005A, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteRINTCNT , HDA_REG_IDX(RINTCNT) }, /* Response Interrupt Count */ + { 0x0005C, 0x00001, 0x00000007, 0x00000007, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteU8 , HDA_REG_IDX(RIRBCTL) }, /* RIRB Control */ + { 0x0005D, 0x00001, 0x00000005, 0x00000005, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteRIRBSTS , HDA_REG_IDX(RIRBSTS) }, /* RIRB Status */ + { 0x0005E, 0x00001, 0x000000F3, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , HDA_REG_IDX(RIRBSIZE) }, /* RIRB Size */ + { 0x00060, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteU32 , HDA_REG_IDX(IC) }, /* Immediate Command */ + { 0x00064, 0x00004, 0x00000000, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , HDA_REG_IDX(IR) }, /* Immediate Response */ + { 0x00068, 0x00002, 0x00000002, 0x00000002, HDA_RD_FLAG_NONE, hdaRegReadIRS , hdaRegWriteIRS , HDA_REG_IDX(IRS) }, /* Immediate Command Status */ + { 0x00070, 0x00004, 0xFFFFFFFF, 0xFFFFFF81, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(DPLBASE) }, /* DMA Position Lower Base */ + { 0x00074, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(DPUBASE) }, /* DMA Position Upper Base */ + /* 4 Serial Data In (SDI). */ + HDA_REG_MAP_DEF_STREAM(0, SD0), + HDA_REG_MAP_DEF_STREAM(1, SD1), + HDA_REG_MAP_DEF_STREAM(2, SD2), + HDA_REG_MAP_DEF_STREAM(3, SD3), + /* 4 Serial Data Out (SDO). */ + HDA_REG_MAP_DEF_STREAM(4, SD4), + HDA_REG_MAP_DEF_STREAM(5, SD5), + HDA_REG_MAP_DEF_STREAM(6, SD6), + HDA_REG_MAP_DEF_STREAM(7, SD7) +}; + +const HDAREGALIAS g_aHdaRegAliases[] = +{ + { 0x2084, HDA_REG_SD0LPIB }, + { 0x20a4, HDA_REG_SD1LPIB }, + { 0x20c4, HDA_REG_SD2LPIB }, + { 0x20e4, HDA_REG_SD3LPIB }, + { 0x2104, HDA_REG_SD4LPIB }, + { 0x2124, HDA_REG_SD5LPIB }, + { 0x2144, HDA_REG_SD6LPIB }, + { 0x2164, HDA_REG_SD7LPIB } +}; + +#ifdef IN_RING3 + +/** HDABDLEDESC field descriptors for the v7 saved state. */ +static SSMFIELD const g_aSSMBDLEDescFields7[] = +{ + SSMFIELD_ENTRY(HDABDLEDESC, u64BufAddr), + SSMFIELD_ENTRY(HDABDLEDESC, u32BufSize), + SSMFIELD_ENTRY(HDABDLEDESC, fFlags), + SSMFIELD_ENTRY_TERM() +}; + +/** HDABDLESTATE field descriptors for the v6+ saved state. */ +static SSMFIELD const g_aSSMBDLEStateFields6[] = +{ + SSMFIELD_ENTRY(HDABDLESTATE, u32BDLIndex), + SSMFIELD_ENTRY(HDABDLESTATE, cbBelowFIFOW), + SSMFIELD_ENTRY_OLD(FIFO, HDA_FIFO_MAX), /* Deprecated; now is handled in the stream's circular buffer. */ + SSMFIELD_ENTRY(HDABDLESTATE, u32BufOff), + SSMFIELD_ENTRY_TERM() +}; + +/** HDABDLESTATE field descriptors for the v7 saved state. */ +static SSMFIELD const g_aSSMBDLEStateFields7[] = +{ + SSMFIELD_ENTRY(HDABDLESTATE, u32BDLIndex), + SSMFIELD_ENTRY(HDABDLESTATE, cbBelowFIFOW), + SSMFIELD_ENTRY(HDABDLESTATE, u32BufOff), + SSMFIELD_ENTRY_TERM() +}; + +/** HDASTREAMSTATE field descriptors for the v6 saved state. */ +static SSMFIELD const g_aSSMStreamStateFields6[] = +{ + SSMFIELD_ENTRY_OLD(cBDLE, sizeof(uint16_t)), /* Deprecated. */ + SSMFIELD_ENTRY(HDASTREAMSTATE, uCurBDLE), + SSMFIELD_ENTRY_OLD(fStop, 1), /* Deprecated; see SSMR3PutBool(). */ + SSMFIELD_ENTRY_OLD(fRunning, 1), /* Deprecated; using the HDA_SDCTL_RUN bit is sufficient. */ + SSMFIELD_ENTRY(HDASTREAMSTATE, fInReset), + SSMFIELD_ENTRY_TERM() +}; + +/** HDASTREAMSTATE field descriptors for the v7 saved state. */ +static SSMFIELD const g_aSSMStreamStateFields7[] = +{ + SSMFIELD_ENTRY(HDASTREAMSTATE, uCurBDLE), + SSMFIELD_ENTRY(HDASTREAMSTATE, fInReset), + SSMFIELD_ENTRY(HDASTREAMSTATE, tsTransferNext), + SSMFIELD_ENTRY_TERM() +}; + +/** HDASTREAMPERIOD field descriptors for the v7 saved state. */ +static SSMFIELD const g_aSSMStreamPeriodFields7[] = +{ + SSMFIELD_ENTRY(HDASTREAMPERIOD, u64StartWalClk), + SSMFIELD_ENTRY(HDASTREAMPERIOD, u64ElapsedWalClk), + SSMFIELD_ENTRY(HDASTREAMPERIOD, framesTransferred), + SSMFIELD_ENTRY(HDASTREAMPERIOD, cIntPending), + SSMFIELD_ENTRY_TERM() +}; + +/** + * 32-bit size indexed masks, i.e. g_afMasks[2 bytes] = 0xffff. + */ +static uint32_t const g_afMasks[5] = +{ + UINT32_C(0), UINT32_C(0x000000ff), UINT32_C(0x0000ffff), UINT32_C(0x00ffffff), UINT32_C(0xffffffff) +}; + +#endif /* IN_RING3 */ + + + +/** + * Retrieves the number of bytes of a FIFOW register. + * + * @return Number of bytes of a given FIFOW register. + */ +DECLINLINE(uint8_t) hdaSDFIFOWToBytes(uint32_t u32RegFIFOW) +{ + uint32_t cb; + switch (u32RegFIFOW) + { + case HDA_SDFIFOW_8B: cb = 8; break; + case HDA_SDFIFOW_16B: cb = 16; break; + case HDA_SDFIFOW_32B: cb = 32; break; + default: cb = 0; break; + } + + Assert(RT_IS_POWER_OF_TWO(cb)); + return cb; +} + +#ifdef IN_RING3 +/** + * Reschedules pending interrupts for all audio streams which have complete + * audio periods but did not have the chance to issue their (pending) interrupts yet. + * + * @param pThis The HDA device state. + */ +static void hdaR3ReschedulePendingInterrupts(PHDASTATE pThis) +{ + bool fInterrupt = false; + + for (uint8_t i = 0; i < HDA_MAX_STREAMS; ++i) + { + PHDASTREAM pStream = hdaGetStreamFromSD(pThis, i); + if (!pStream) + continue; + + if ( hdaR3StreamPeriodIsComplete (&pStream->State.Period) + && hdaR3StreamPeriodNeedsInterrupt(&pStream->State.Period) + && hdaR3WalClkSet(pThis, hdaR3StreamPeriodGetAbsElapsedWalClk(&pStream->State.Period), false /* fForce */)) + { + fInterrupt = true; + break; + } + } + + LogFunc(("fInterrupt=%RTbool\n", fInterrupt)); + +# ifndef LOG_ENABLED + hdaProcessInterrupt(pThis); +# else + hdaProcessInterrupt(pThis, __FUNCTION__); +# endif +} +#endif /* IN_RING3 */ + +/** + * Looks up a register at the exact offset given by @a offReg. + * + * @returns Register index on success, -1 if not found. + * @param offReg The register offset. + */ +static int hdaRegLookup(uint32_t offReg) +{ + /* + * Aliases. + */ + if (offReg >= g_aHdaRegAliases[0].offReg) + { + for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegAliases); i++) + if (offReg == g_aHdaRegAliases[i].offReg) + return g_aHdaRegAliases[i].idxAlias; + Assert(g_aHdaRegMap[RT_ELEMENTS(g_aHdaRegMap) - 1].offset < offReg); + return -1; + } + + /* + * Binary search the + */ + int idxEnd = RT_ELEMENTS(g_aHdaRegMap); + int idxLow = 0; + for (;;) + { + int idxMiddle = idxLow + (idxEnd - idxLow) / 2; + if (offReg < g_aHdaRegMap[idxMiddle].offset) + { + if (idxLow == idxMiddle) + break; + idxEnd = idxMiddle; + } + else if (offReg > g_aHdaRegMap[idxMiddle].offset) + { + idxLow = idxMiddle + 1; + if (idxLow >= idxEnd) + break; + } + else + return idxMiddle; + } + +#ifdef RT_STRICT + for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++) + Assert(g_aHdaRegMap[i].offset != offReg); +#endif + return -1; +} + +#ifdef IN_RING3 + +/** + * Looks up a register covering the offset given by @a offReg. + * + * @returns Register index on success, -1 if not found. + * @param offReg The register offset. + */ +static int hdaR3RegLookupWithin(uint32_t offReg) +{ + /* + * Aliases. + */ + if (offReg >= g_aHdaRegAliases[0].offReg) + { + for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegAliases); i++) + { + uint32_t off = offReg - g_aHdaRegAliases[i].offReg; + if (off < 4 && off < g_aHdaRegMap[g_aHdaRegAliases[i].idxAlias].size) + return g_aHdaRegAliases[i].idxAlias; + } + Assert(g_aHdaRegMap[RT_ELEMENTS(g_aHdaRegMap) - 1].offset < offReg); + return -1; + } + + /* + * Binary search the register map. + */ + int idxEnd = RT_ELEMENTS(g_aHdaRegMap); + int idxLow = 0; + for (;;) + { + int idxMiddle = idxLow + (idxEnd - idxLow) / 2; + if (offReg < g_aHdaRegMap[idxMiddle].offset) + { + if (idxLow == idxMiddle) + break; + idxEnd = idxMiddle; + } + else if (offReg >= g_aHdaRegMap[idxMiddle].offset + g_aHdaRegMap[idxMiddle].size) + { + idxLow = idxMiddle + 1; + if (idxLow >= idxEnd) + break; + } + else + return idxMiddle; + } + +# ifdef RT_STRICT + for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++) + Assert(offReg - g_aHdaRegMap[i].offset >= g_aHdaRegMap[i].size); +# endif + return -1; +} + + +/** + * Synchronizes the CORB / RIRB buffers between internal <-> device state. + * + * @returns IPRT status code. + * @param pThis HDA state. + * @param fLocal Specify true to synchronize HDA state's CORB buffer with the device state, + * or false to synchronize the device state's RIRB buffer with the HDA state. + * + * @todo r=andy Break this up into two functions? + */ +static int hdaR3CmdSync(PHDASTATE pThis, bool fLocal) +{ + int rc = VINF_SUCCESS; + if (fLocal) + { + if (pThis->u64CORBBase) + { + AssertPtr(pThis->pu32CorbBuf); + Assert(pThis->cbCorbBuf); + +/** @todo r=bird: An explanation is required why PDMDevHlpPhysRead is used with + * the CORB and PDMDevHlpPCIPhysWrite with RIRB below. There are + * similar unexplained inconsistencies in DevHDACommon.cpp. */ + rc = PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), pThis->u64CORBBase, pThis->pu32CorbBuf, pThis->cbCorbBuf); + Log(("hdaR3CmdSync/CORB: read %RGp LB %#x (%Rrc)\n", pThis->u64CORBBase, pThis->cbCorbBuf, rc)); + AssertRCReturn(rc, rc); + } + } + else + { + if (pThis->u64RIRBBase) + { + AssertPtr(pThis->pu64RirbBuf); + Assert(pThis->cbRirbBuf); + + rc = PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns), pThis->u64RIRBBase, pThis->pu64RirbBuf, pThis->cbRirbBuf); + Log(("hdaR3CmdSync/RIRB: phys read %RGp LB %#x (%Rrc)\n", pThis->u64RIRBBase, pThis->pu64RirbBuf, rc)); + AssertRCReturn(rc, rc); + } + } + +# ifdef DEBUG_CMD_BUFFER + LogFunc(("fLocal=%RTbool\n", fLocal)); + + uint8_t i = 0; + do + { + LogFunc(("CORB%02x: ", i)); + uint8_t j = 0; + do + { + const char *pszPrefix; + if ((i + j) == HDA_REG(pThis, CORBRP)) + pszPrefix = "[R]"; + else if ((i + j) == HDA_REG(pThis, CORBWP)) + pszPrefix = "[W]"; + else + pszPrefix = " "; /* three spaces */ + Log((" %s%08x", pszPrefix, pThis->pu32CorbBuf[i + j])); + j++; + } while (j < 8); + Log(("\n")); + i += 8; + } while(i != 0); + + do + { + LogFunc(("RIRB%02x: ", i)); + uint8_t j = 0; + do + { + const char *prefix; + if ((i + j) == HDA_REG(pThis, RIRBWP)) + prefix = "[W]"; + else + prefix = " "; + Log((" %s%016lx", prefix, pThis->pu64RirbBuf[i + j])); + } while (++j < 8); + Log(("\n")); + i += 8; + } while (i != 0); +# endif + return rc; +} + +/** + * Processes the next CORB buffer command in the queue. + * + * This will invoke the HDA codec verb dispatcher. + * + * @returns IPRT status code. + * @param pThis HDA state. + */ +static int hdaR3CORBCmdProcess(PHDASTATE pThis) +{ + uint8_t corbRp = HDA_REG(pThis, CORBRP); + uint8_t corbWp = HDA_REG(pThis, CORBWP); + uint8_t rirbWp = HDA_REG(pThis, RIRBWP); + + Log3Func(("CORB(RP:%x, WP:%x) RIRBWP:%x\n", corbRp, corbWp, rirbWp)); + + if (!(HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA)) + { + LogFunc(("CORB DMA not active, skipping\n")); + return VINF_SUCCESS; + } + + Assert(pThis->cbCorbBuf); + + int rc = hdaR3CmdSync(pThis, true /* Sync from guest */); + AssertRCReturn(rc, rc); + + uint16_t cIntCnt = HDA_REG(pThis, RINTCNT) & 0xff; + + if (!cIntCnt) /* 0 means 256 interrupts. */ + cIntCnt = HDA_MAX_RINTCNT; + + Log3Func(("START CORB(RP:%x, WP:%x) RIRBWP:%x, RINTCNT:%RU8/%RU8\n", + corbRp, corbWp, rirbWp, pThis->u16RespIntCnt, cIntCnt)); + + while (corbRp != corbWp) + { + corbRp = (corbRp + 1) % (pThis->cbCorbBuf / HDA_CORB_ELEMENT_SIZE); /* Advance +1 as the first command(s) are at CORBWP + 1. */ + + uint32_t uCmd = pThis->pu32CorbBuf[corbRp]; + uint64_t uResp = 0; + + rc = pThis->pCodec->pfnLookup(pThis->pCodec, HDA_CODEC_CMD(uCmd, 0 /* Codec index */), &uResp); + if (RT_FAILURE(rc)) + LogFunc(("Codec lookup failed with rc=%Rrc\n", rc)); + + Log3Func(("Codec verb %08x -> response %016lx\n", uCmd, uResp)); + + if ( (uResp & CODEC_RESPONSE_UNSOLICITED) + && !(HDA_REG(pThis, GCTL) & HDA_GCTL_UNSOL)) + { + LogFunc(("Unexpected unsolicited response.\n")); + HDA_REG(pThis, CORBRP) = corbRp; + + /** @todo r=andy No CORB/RIRB syncing to guest required in that case? */ + return rc; + } + + rirbWp = (rirbWp + 1) % HDA_RIRB_SIZE; + + pThis->pu64RirbBuf[rirbWp] = uResp; + + pThis->u16RespIntCnt++; + + bool fSendInterrupt = false; + + if (pThis->u16RespIntCnt == cIntCnt) /* Response interrupt count reached? */ + { + pThis->u16RespIntCnt = 0; /* Reset internal interrupt response counter. */ + + Log3Func(("Response interrupt count reached (%RU16)\n", pThis->u16RespIntCnt)); + fSendInterrupt = true; + + } + else if (corbRp == corbWp) /* Did we reach the end of the current command buffer? */ + { + Log3Func(("Command buffer empty\n")); + fSendInterrupt = true; + } + + if (fSendInterrupt) + { + if (HDA_REG(pThis, RIRBCTL) & HDA_RIRBCTL_RINTCTL) /* Response Interrupt Control (RINTCTL) enabled? */ + { + HDA_REG(pThis, RIRBSTS) |= HDA_RIRBSTS_RINTFL; + +# ifndef LOG_ENABLED + rc = hdaProcessInterrupt(pThis); +# else + rc = hdaProcessInterrupt(pThis, __FUNCTION__); +# endif + } + } + } + + Log3Func(("END CORB(RP:%x, WP:%x) RIRBWP:%x, RINTCNT:%RU8/%RU8\n", + corbRp, corbWp, rirbWp, pThis->u16RespIntCnt, cIntCnt)); + + HDA_REG(pThis, CORBRP) = corbRp; + HDA_REG(pThis, RIRBWP) = rirbWp; + + rc = hdaR3CmdSync(pThis, false /* Sync to guest */); + AssertRCReturn(rc, rc); + + if (RT_FAILURE(rc)) + AssertRCReturn(rc, rc); + + return rc; +} + +#endif /* IN_RING3 */ + +/* Register access handlers. */ + +static int hdaRegReadUnimpl(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF_PV(pThis); RT_NOREF_PV(iReg); + *pu32Value = 0; + return VINF_SUCCESS; +} + +static int hdaRegWriteUnimpl(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF_PV(pThis); RT_NOREF_PV(iReg); RT_NOREF_PV(u32Value); + return VINF_SUCCESS; +} + +/* U8 */ +static int hdaRegReadU8(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].mem_idx] & g_aHdaRegMap[iReg].readable) & 0xffffff00) == 0); + return hdaRegReadU32(pThis, iReg, pu32Value); +} + +static int hdaRegWriteU8(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + Assert((u32Value & 0xffffff00) == 0); + return hdaRegWriteU32(pThis, iReg, u32Value); +} + +/* U16 */ +static int hdaRegReadU16(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].mem_idx] & g_aHdaRegMap[iReg].readable) & 0xffff0000) == 0); + return hdaRegReadU32(pThis, iReg, pu32Value); +} + +static int hdaRegWriteU16(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + Assert((u32Value & 0xffff0000) == 0); + return hdaRegWriteU32(pThis, iReg, u32Value); +} + +/* U24 */ +static int hdaRegReadU24(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].mem_idx] & g_aHdaRegMap[iReg].readable) & 0xff000000) == 0); + return hdaRegReadU32(pThis, iReg, pu32Value); +} + +#ifdef IN_RING3 +static int hdaRegWriteU24(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + Assert((u32Value & 0xff000000) == 0); + return hdaRegWriteU32(pThis, iReg, u32Value); +} +#endif + +/* U32 */ +static int hdaRegReadU32(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t iRegMem = g_aHdaRegMap[iReg].mem_idx; + + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_READ); + + *pu32Value = pThis->au32Regs[iRegMem] & g_aHdaRegMap[iReg].readable; + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +} + +static int hdaRegWriteU32(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + uint32_t iRegMem = g_aHdaRegMap[iReg].mem_idx; + + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + pThis->au32Regs[iRegMem] = (u32Value & g_aHdaRegMap[iReg].writable) + | (pThis->au32Regs[iRegMem] & ~g_aHdaRegMap[iReg].writable); + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +} + +static int hdaRegWriteGCTL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF_PV(iReg); +#ifdef IN_RING3 + DEVHDA_LOCK(pThis); +#else + if (!(u32Value & HDA_GCTL_CRST)) + return VINF_IOM_R3_MMIO_WRITE; + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); +#endif + + if (u32Value & HDA_GCTL_CRST) + { + /* Set the CRST bit to indicate that we're leaving reset mode. */ + HDA_REG(pThis, GCTL) |= HDA_GCTL_CRST; + LogFunc(("Guest leaving HDA reset\n")); + } + else + { +#ifdef IN_RING3 + /* Enter reset state. */ + LogFunc(("Guest entering HDA reset with DMA(RIRB:%s, CORB:%s)\n", + HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA ? "on" : "off", + HDA_REG(pThis, RIRBCTL) & HDA_RIRBCTL_RDMAEN ? "on" : "off")); + + /* Clear the CRST bit to indicate that we're in reset state. */ + HDA_REG(pThis, GCTL) &= ~HDA_GCTL_CRST; + + hdaR3GCTLReset(pThis); +#else + AssertFailedReturnStmt(DEVHDA_UNLOCK(pThis), VINF_IOM_R3_MMIO_WRITE); +#endif + } + + if (u32Value & HDA_GCTL_FCNTRL) + { + /* Flush: GSTS:1 set, see 6.2.6. */ + HDA_REG(pThis, GSTS) |= HDA_GSTS_FSTS; /* Set the flush status. */ + /* DPLBASE and DPUBASE should be initialized with initial value (see 6.2.6). */ + } + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +} + +static int hdaRegWriteSTATESTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + uint32_t v = HDA_REG_IND(pThis, iReg); + uint32_t nv = u32Value & HDA_STATESTS_SCSF_MASK; + + HDA_REG(pThis, STATESTS) &= ~(v & nv); /* Write of 1 clears corresponding bit. */ + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +} + +static int hdaRegReadLPIB(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_READ); + + const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, LPIB, iReg); + uint32_t u32LPIB = HDA_STREAM_REG(pThis, LPIB, uSD); +#ifdef LOG_ENABLED + const uint32_t u32CBL = HDA_STREAM_REG(pThis, CBL, uSD); + LogFlowFunc(("[SD%RU8] LPIB=%RU32, CBL=%RU32\n", uSD, u32LPIB, u32CBL)); +#endif + + *pu32Value = u32LPIB; + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +} + +#ifdef IN_RING3 +/** + * Returns the current maximum value the wall clock counter can be set to. + * This maximum value depends on all currently handled HDA streams and their own current timing. + * + * @return Current maximum value the wall clock counter can be set to. + * @param pThis HDA state. + * + * @remark Does not actually set the wall clock counter. + */ +static uint64_t hdaR3WalClkGetMax(PHDASTATE pThis) +{ + const uint64_t u64WalClkCur = ASMAtomicReadU64(&pThis->u64WalClk); + const uint64_t u64FrontAbsWalClk = pThis->SinkFront.pStream + ? hdaR3StreamPeriodGetAbsElapsedWalClk(&pThis->SinkFront.pStream->State.Period) : 0; +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND +# error "Implement me!" +# endif + const uint64_t u64LineInAbsWalClk = pThis->SinkLineIn.pStream + ? hdaR3StreamPeriodGetAbsElapsedWalClk(&pThis->SinkLineIn.pStream->State.Period) : 0; +# ifdef VBOX_WITH_HDA_MIC_IN + const uint64_t u64MicInAbsWalClk = pThis->SinkMicIn.pStream + ? hdaR3StreamPeriodGetAbsElapsedWalClk(&pThis->SinkMicIn.pStream->State.Period) : 0; +# endif + + uint64_t u64WalClkNew = RT_MAX(u64WalClkCur, u64FrontAbsWalClk); +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND +# error "Implement me!" +# endif + u64WalClkNew = RT_MAX(u64WalClkNew, u64LineInAbsWalClk); +# ifdef VBOX_WITH_HDA_MIC_IN + u64WalClkNew = RT_MAX(u64WalClkNew, u64MicInAbsWalClk); +# endif + + Log3Func(("%RU64 -> Front=%RU64, LineIn=%RU64 -> %RU64\n", + u64WalClkCur, u64FrontAbsWalClk, u64LineInAbsWalClk, u64WalClkNew)); + + return u64WalClkNew; +} +#endif /* IN_RING3 */ + +static int hdaRegReadWALCLK(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ +#ifdef IN_RING3 + RT_NOREF(iReg); + + DEVHDA_LOCK(pThis); + + *pu32Value = RT_LO_U32(ASMAtomicReadU64(&pThis->u64WalClk)); + + Log3Func(("%RU32 (max @ %RU64)\n",*pu32Value, hdaR3WalClkGetMax(pThis))); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +#else + RT_NOREF(pThis, iReg, pu32Value); + return VINF_IOM_R3_MMIO_READ; +#endif +} + +static int hdaRegWriteCORBRP(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(iReg); + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + if (u32Value & HDA_CORBRP_RST) + { + /* Do a CORB reset. */ + if (pThis->cbCorbBuf) + { +#ifdef IN_RING3 + Assert(pThis->pu32CorbBuf); + RT_BZERO((void *)pThis->pu32CorbBuf, pThis->cbCorbBuf); +#else + DEVHDA_UNLOCK(pThis); + return VINF_IOM_R3_MMIO_WRITE; +#endif + } + + LogRel2(("HDA: CORB reset\n")); + + HDA_REG(pThis, CORBRP) = HDA_CORBRP_RST; /* Clears the pointer. */ + } + else + HDA_REG(pThis, CORBRP) &= ~HDA_CORBRP_RST; /* Only CORBRP_RST bit is writable. */ + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +} + +static int hdaRegWriteCORBCTL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ +#ifdef IN_RING3 + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + int rc = hdaRegWriteU8(pThis, iReg, u32Value); + AssertRC(rc); + + if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Start DMA engine. */ + { + rc = hdaR3CORBCmdProcess(pThis); + } + else + LogFunc(("CORB DMA not running, skipping\n")); + + DEVHDA_UNLOCK(pThis); + return rc; +#else + RT_NOREF(pThis, iReg, u32Value); + return VINF_IOM_R3_MMIO_WRITE; +#endif +} + +static int hdaRegWriteCORBSIZE(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ +#ifdef IN_RING3 + RT_NOREF(iReg); + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Ignore request if CORB DMA engine is (still) running. */ + { + LogFunc(("CORB DMA is (still) running, skipping\n")); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; + } + + u32Value = (u32Value & HDA_CORBSIZE_SZ); + + uint16_t cEntries = HDA_CORB_SIZE; /* Set default. */ + + switch (u32Value) + { + case 0: /* 8 byte; 2 entries. */ + cEntries = 2; + break; + + case 1: /* 64 byte; 16 entries. */ + cEntries = 16; + break; + + case 2: /* 1 KB; 256 entries. */ + /* Use default size. */ + break; + + default: + LogRel(("HDA: Guest tried to set an invalid CORB size (0x%x), keeping default\n", u32Value)); + u32Value = 2; + /* Use default size. */ + break; + } + + uint32_t cbCorbBuf = cEntries * HDA_CORB_ELEMENT_SIZE; + Assert(cbCorbBuf <= HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE); /* Paranoia. */ + + if (cbCorbBuf != pThis->cbCorbBuf) + { + RT_BZERO(pThis->pu32CorbBuf, HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE); /* Clear CORB when setting a new size. */ + pThis->cbCorbBuf = cbCorbBuf; + } + + LogFunc(("CORB buffer size is now %RU32 bytes (%u entries)\n", pThis->cbCorbBuf, pThis->cbCorbBuf / HDA_CORB_ELEMENT_SIZE)); + + HDA_REG(pThis, CORBSIZE) = u32Value; + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +#else + RT_NOREF(pThis, iReg, u32Value); + return VINF_IOM_R3_MMIO_WRITE; +#endif +} + +static int hdaRegWriteCORBSTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF_PV(iReg); + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + uint32_t v = HDA_REG(pThis, CORBSTS); + HDA_REG(pThis, CORBSTS) &= ~(v & u32Value); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +} + +static int hdaRegWriteCORBWP(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ +#ifdef IN_RING3 + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + int rc = hdaRegWriteU16(pThis, iReg, u32Value); + AssertRCSuccess(rc); + + rc = hdaR3CORBCmdProcess(pThis); + + DEVHDA_UNLOCK(pThis); + return rc; +#else + RT_NOREF(pThis, iReg, u32Value); + return VINF_IOM_R3_MMIO_WRITE; +#endif +} + +static int hdaRegWriteSDCBL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + int rc = hdaRegWriteU32(pThis, iReg, u32Value); + AssertRCSuccess(rc); + + DEVHDA_UNLOCK(pThis); + return rc; +} + +static int hdaRegWriteSDCTL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ +#ifdef IN_RING3 + /* Get the stream descriptor. */ + const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, CTL, iReg); + + DEVHDA_LOCK_BOTH_RETURN(pThis, uSD, VINF_IOM_R3_MMIO_WRITE); + + /* + * Some guests write too much (that is, 32-bit with the top 8 bit being junk) + * instead of 24-bit required for SDCTL. So just mask this here to be safe. + */ + u32Value &= 0x00ffffff; + + const bool fRun = RT_BOOL(u32Value & HDA_SDCTL_RUN); + const bool fInRun = RT_BOOL(HDA_REG_IND(pThis, iReg) & HDA_SDCTL_RUN); + + const bool fReset = RT_BOOL(u32Value & HDA_SDCTL_SRST); + const bool fInReset = RT_BOOL(HDA_REG_IND(pThis, iReg) & HDA_SDCTL_SRST); + + /*LogFunc(("[SD%RU8] fRun=%RTbool, fInRun=%RTbool, fReset=%RTbool, fInReset=%RTbool, %R[sdctl]\n", + uSD, fRun, fInRun, fReset, fInReset, u32Value));*/ + + /* + * Extract the stream tag the guest wants to use for this specific + * stream descriptor (SDn). This only can happen if the stream is in a non-running + * state, so we're doing the lookup and assignment here. + * + * So depending on the guest OS, SD3 can use stream tag 4, for example. + */ + uint8_t uTag = (u32Value >> HDA_SDCTL_NUM_SHIFT) & HDA_SDCTL_NUM_MASK; + if (uTag > HDA_MAX_TAGS) + { + LogFunc(("[SD%RU8] Warning: Invalid stream tag %RU8 specified!\n", uSD, uTag)); + + int rc = hdaRegWriteU24(pThis, iReg, u32Value); + DEVHDA_UNLOCK_BOTH(pThis, uSD); + return rc; + } + + PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uSD); + AssertPtr(pStream); + + if (fInReset) + { + Assert(!fReset); + Assert(!fInRun && !fRun); + + /* Exit reset state. */ + ASMAtomicXchgBool(&pStream->State.fInReset, false); + + /* Report that we're done resetting this stream by clearing SRST. */ + HDA_STREAM_REG(pThis, CTL, uSD) &= ~HDA_SDCTL_SRST; + + LogFunc(("[SD%RU8] Reset exit\n", uSD)); + } + else if (fReset) + { + /* ICH6 datasheet 18.2.33 says that RUN bit should be cleared before initiation of reset. */ + Assert(!fInRun && !fRun); + + LogFunc(("[SD%RU8] Reset enter\n", uSD)); + + hdaR3StreamLock(pStream); + +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + hdaR3StreamAsyncIOLock(pStream); +# endif + /* Make sure to remove the run bit before doing the actual stream reset. */ + HDA_STREAM_REG(pThis, CTL, uSD) &= ~HDA_SDCTL_RUN; + + hdaR3StreamReset(pThis, pStream, pStream->u8SD); + +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + hdaR3StreamAsyncIOUnlock(pStream); +# endif + hdaR3StreamUnlock(pStream); + } + else + { + /* + * We enter here to change DMA states only. + */ + if (fInRun != fRun) + { + Assert(!fReset && !fInReset); + LogFunc(("[SD%RU8] State changed (fRun=%RTbool)\n", uSD, fRun)); + + hdaR3StreamLock(pStream); + + int rc2; + +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + if (fRun) + rc2 = hdaR3StreamAsyncIOCreate(pStream); + + hdaR3StreamAsyncIOLock(pStream); +# endif + if (fRun) + { + if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) + { + const uint8_t uStripeCtl = ((u32Value >> HDA_SDCTL_STRIPE_SHIFT) & HDA_SDCTL_STRIPE_MASK) + 1; + LogFunc(("[SD%RU8] Using %RU8 SDOs (stripe control)\n", uSD, uStripeCtl)); + if (uStripeCtl > 1) + LogRel2(("HDA: Warning: Striping output over more than one SDO for stream #%RU8 currently is not implemented " \ + "(%RU8 SDOs requested)\n", uSD, uStripeCtl)); + } + + PHDATAG pTag = &pThis->aTags[uTag]; + AssertPtr(pTag); + + LogFunc(("[SD%RU8] Using stream tag=%RU8\n", uSD, uTag)); + + /* Assign new values. */ + pTag->uTag = uTag; + pTag->pStream = hdaGetStreamFromSD(pThis, uSD); + +# ifdef LOG_ENABLED + PDMAUDIOPCMPROPS Props; + rc2 = hdaR3SDFMTToPCMProps(HDA_STREAM_REG(pThis, FMT, pStream->u8SD), &Props); + AssertRC(rc2); + LogFunc(("[SD%RU8] %RU32Hz, %RU8bit, %RU8 channel(s)\n", + pStream->u8SD, Props.uHz, Props.cBytes * 8 /* Bit */, Props.cChannels)); +# endif + /* (Re-)initialize the stream with current values. */ + rc2 = hdaR3StreamInit(pStream, pStream->u8SD); + if ( RT_SUCCESS(rc2) + /* Any vital stream change occurred so that we need to (re-)add the stream to our setup? + * Otherwise just skip this, as this costs a lot of performance. */ + && rc2 != VINF_NO_CHANGE) + { + /* Remove the old stream from the device setup. */ + rc2 = hdaR3RemoveStream(pThis, &pStream->State.Cfg); + AssertRC(rc2); + + /* Add the stream to the device setup. */ + rc2 = hdaR3AddStream(pThis, &pStream->State.Cfg); + AssertRC(rc2); + } + } + + /* Enable/disable the stream. */ + rc2 = hdaR3StreamEnable(pStream, fRun /* fEnable */); + AssertRC(rc2); + + if (fRun) + { + /* Keep track of running streams. */ + pThis->cStreamsActive++; + + /* (Re-)init the stream's period. */ + hdaR3StreamPeriodInit(&pStream->State.Period, + pStream->u8SD, pStream->u16LVI, pStream->u32CBL, &pStream->State.Cfg); + + /* Begin a new period for this stream. */ + rc2 = hdaR3StreamPeriodBegin(&pStream->State.Period, hdaWalClkGetCurrent(pThis)/* Use current wall clock time */); + AssertRC(rc2); + + rc2 = hdaR3TimerSet(pThis, pStream, TMTimerGet(pThis->pTimer[pStream->u8SD]) + pStream->State.cTransferTicks, false /* fForce */); + AssertRC(rc2); + } + else + { + /* Keep track of running streams. */ + Assert(pThis->cStreamsActive); + if (pThis->cStreamsActive) + pThis->cStreamsActive--; + + /* Make sure to (re-)schedule outstanding (delayed) interrupts. */ + hdaR3ReschedulePendingInterrupts(pThis); + + /* Reset the period. */ + hdaR3StreamPeriodReset(&pStream->State.Period); + } + +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + hdaR3StreamAsyncIOUnlock(pStream); +# endif + /* Make sure to leave the lock before (eventually) starting the timer. */ + hdaR3StreamUnlock(pStream); + } + } + + int rc2 = hdaRegWriteU24(pThis, iReg, u32Value); + AssertRC(rc2); + + DEVHDA_UNLOCK_BOTH(pThis, uSD); + return VINF_SUCCESS; /* Always return success to the MMIO handler. */ +#else /* !IN_RING3 */ + RT_NOREF_PV(pThis); RT_NOREF_PV(iReg); RT_NOREF_PV(u32Value); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* IN_RING3 */ +} + +static int hdaRegWriteSDSTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ +#ifdef IN_RING3 + const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, STS, iReg); + + DEVHDA_LOCK_BOTH_RETURN(pThis, uSD, VINF_IOM_R3_MMIO_WRITE); + + PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uSD); + if (!pStream) + { + AssertMsgFailed(("[SD%RU8] Warning: Writing SDSTS on non-attached stream (0x%x)\n", + HDA_SD_NUM_FROM_REG(pThis, STS, iReg), u32Value)); + + int rc = hdaRegWriteU16(pThis, iReg, u32Value); + DEVHDA_UNLOCK_BOTH(pThis, uSD); + return rc; + } + + hdaR3StreamLock(pStream); + + uint32_t v = HDA_REG_IND(pThis, iReg); + + /* Clear (zero) FIFOE, DESE and BCIS bits when writing 1 to it (6.2.33). */ + HDA_REG_IND(pThis, iReg) &= ~(u32Value & v); + + /* Some guests tend to write SDnSTS even if the stream is not running. + * So make sure to check if the RUN bit is set first. */ + const bool fRunning = pStream->State.fRunning; + + Log3Func(("[SD%RU8] fRunning=%RTbool %R[sdsts]\n", pStream->u8SD, fRunning, v)); + + PHDASTREAMPERIOD pPeriod = &pStream->State.Period; + + if (hdaR3StreamPeriodLock(pPeriod)) + { + const bool fNeedsInterrupt = hdaR3StreamPeriodNeedsInterrupt(pPeriod); + if (fNeedsInterrupt) + hdaR3StreamPeriodReleaseInterrupt(pPeriod); + + if (hdaR3StreamPeriodIsComplete(pPeriod)) + { + /* Make sure to try to update the WALCLK register if a period is complete. + * Use the maximum WALCLK value all (active) streams agree to. */ + const uint64_t uWalClkMax = hdaR3WalClkGetMax(pThis); + if (uWalClkMax > hdaWalClkGetCurrent(pThis)) + hdaR3WalClkSet(pThis, uWalClkMax, false /* fForce */); + + hdaR3StreamPeriodEnd(pPeriod); + + if (fRunning) + hdaR3StreamPeriodBegin(pPeriod, hdaWalClkGetCurrent(pThis) /* Use current wall clock time */); + } + + hdaR3StreamPeriodUnlock(pPeriod); /* Unlock before processing interrupt. */ + } + +# ifndef LOG_ENABLED + hdaProcessInterrupt(pThis); +# else + hdaProcessInterrupt(pThis, __FUNCTION__); +# endif + + const uint64_t tsNow = TMTimerGet(pThis->pTimer[uSD]); + Assert(tsNow >= pStream->State.tsTransferLast); + + const uint64_t cTicksElapsed = tsNow - pStream->State.tsTransferLast; +# ifdef LOG_ENABLED + const uint64_t cTicksTransferred = pStream->State.cbTransferProcessed * pStream->State.cTicksPerByte; +# endif + + uint64_t cTicksToNext = pStream->State.cTransferTicks; + if (cTicksToNext) /* Only do any calculations if the stream currently is set up for transfers. */ + { + Log3Func(("[SD%RU8] cTicksElapsed=%RU64, cTicksTransferred=%RU64, cTicksToNext=%RU64\n", + pStream->u8SD, cTicksElapsed, cTicksTransferred, cTicksToNext)); + + Log3Func(("[SD%RU8] cbTransferProcessed=%RU32, cbTransferChunk=%RU32, cbTransferSize=%RU32\n", + pStream->u8SD, pStream->State.cbTransferProcessed, pStream->State.cbTransferChunk, pStream->State.cbTransferSize)); + + if (cTicksElapsed <= cTicksToNext) + { + cTicksToNext = cTicksToNext - cTicksElapsed; + } + else /* Catch up. */ + { + Log3Func(("[SD%RU8] Warning: Lagging behind (%RU64 ticks elapsed, maximum allowed is %RU64)\n", + pStream->u8SD, cTicksElapsed, cTicksToNext)); + + LogRelMax2(64, ("HDA: Stream #%RU8 interrupt lagging behind (expected %uus, got %uus), trying to catch up ...\n", + pStream->u8SD, + (TMTimerGetFreq(pThis->pTimer[pStream->u8SD]) / pThis->uTimerHz) / 1000,(tsNow - pStream->State.tsTransferLast) / 1000)); + + cTicksToNext = 0; + } + + Log3Func(("[SD%RU8] -> cTicksToNext=%RU64\n", pStream->u8SD, cTicksToNext)); + + /* Reset processed data counter. */ + pStream->State.cbTransferProcessed = 0; + pStream->State.tsTransferNext = tsNow + cTicksToNext; + + /* Only re-arm the timer if there were pending transfer interrupts left + * -- it could happen that we land in here if a guest writes to SDnSTS + * unconditionally. */ + if (pStream->State.cTransferPendingInterrupts) + { + pStream->State.cTransferPendingInterrupts--; + + /* Re-arm the timer. */ + LogFunc(("Timer set SD%RU8\n", pStream->u8SD)); + hdaR3TimerSet(pThis, pStream, tsNow + cTicksToNext, false /* fForce */); + } + } + + hdaR3StreamUnlock(pStream); + + DEVHDA_UNLOCK_BOTH(pThis, uSD); + return VINF_SUCCESS; +#else /* IN_RING3 */ + RT_NOREF(pThis, iReg, u32Value); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ +} + +static int hdaRegWriteSDLVI(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + +#ifdef HDA_USE_DMA_ACCESS_HANDLER + uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, LVI, iReg); + + if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) + { + PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uSD); + + /* Try registering the DMA handlers. + * As we can't be sure in which order LVI + BDL base are set, try registering in both routines. */ + if ( pStream + && hdaR3StreamRegisterDMAHandlers(pThis, pStream)) + { + LogFunc(("[SD%RU8] DMA logging enabled\n", pStream->u8SD)); + } + } +#endif + + int rc2 = hdaRegWriteU16(pThis, iReg, u32Value); + AssertRC(rc2); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; /* Always return success to the MMIO handler. */ +} + +static int hdaRegWriteSDFIFOW(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, FIFOW, iReg); + + if (hdaGetDirFromSD(uSD) != PDMAUDIODIR_IN) /* FIFOW for input streams only. */ + { +#ifndef IN_RING0 + LogRel(("HDA: Warning: Guest tried to write read-only FIFOW to output stream #%RU8, ignoring\n", uSD)); + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +#else + DEVHDA_UNLOCK(pThis); + return VINF_IOM_R3_MMIO_WRITE; +#endif + } + + PHDASTREAM pStream = hdaGetStreamFromSD(pThis, HDA_SD_NUM_FROM_REG(pThis, FIFOW, iReg)); + if (!pStream) + { + AssertMsgFailed(("[SD%RU8] Warning: Changing FIFOW on non-attached stream (0x%x)\n", uSD, u32Value)); + + int rc = hdaRegWriteU16(pThis, iReg, u32Value); + DEVHDA_UNLOCK(pThis); + return rc; + } + + uint32_t u32FIFOW = 0; + + switch (u32Value) + { + case HDA_SDFIFOW_8B: + case HDA_SDFIFOW_16B: + case HDA_SDFIFOW_32B: + u32FIFOW = u32Value; + break; + default: + ASSERT_GUEST_LOGREL_MSG_FAILED(("Guest tried write unsupported FIFOW (0x%x) to stream #%RU8, defaulting to 32 bytes\n", + u32Value, uSD)); + u32FIFOW = HDA_SDFIFOW_32B; + break; + } + + if (u32FIFOW) + { + pStream->u16FIFOW = hdaSDFIFOWToBytes(u32FIFOW); + LogFunc(("[SD%RU8] Updating FIFOW to %RU32 bytes\n", uSD, pStream->u16FIFOW)); + + int rc2 = hdaRegWriteU16(pThis, iReg, u32FIFOW); + AssertRC(rc2); + } + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; /* Always return success to the MMIO handler. */ +} + +/** + * @note This method could be called for changing value on Output Streams only (ICH6 datasheet 18.2.39). + */ +static int hdaRegWriteSDFIFOS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, FIFOS, iReg); + + if (hdaGetDirFromSD(uSD) != PDMAUDIODIR_OUT) /* FIFOS for output streams only. */ + { + LogRel(("HDA: Warning: Guest tried to write read-only FIFOS to input stream #%RU8, ignoring\n", uSD)); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; + } + + uint32_t u32FIFOS; + + switch(u32Value) + { + case HDA_SDOFIFO_16B: + case HDA_SDOFIFO_32B: + case HDA_SDOFIFO_64B: + case HDA_SDOFIFO_128B: + case HDA_SDOFIFO_192B: + case HDA_SDOFIFO_256B: + u32FIFOS = u32Value; + break; + + default: + ASSERT_GUEST_LOGREL_MSG_FAILED(("Guest tried write unsupported FIFOS (0x%x) to stream #%RU8, defaulting to 192 bytes\n", + u32Value, uSD)); + u32FIFOS = HDA_SDOFIFO_192B; + break; + } + + int rc2 = hdaRegWriteU16(pThis, iReg, u32FIFOS); + AssertRC(rc2); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; /* Always return success to the MMIO handler. */ +} + +#ifdef IN_RING3 + +/** + * Adds an audio output stream to the device setup using the given configuration. + * + * @returns IPRT status code. + * @param pThis Device state. + * @param pCfg Stream configuration to use for adding a stream. + */ +static int hdaR3AddStreamOut(PHDASTATE pThis, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + AssertReturn(pCfg->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + + LogFlowFunc(("Stream=%s\n", pCfg->szName)); + + int rc = VINF_SUCCESS; + + bool fUseFront = true; /* Always use front out by default. */ +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + bool fUseRear; + bool fUseCenter; + bool fUseLFE; + + fUseRear = fUseCenter = fUseLFE = false; + + /* + * Use commonly used setups for speaker configurations. + */ + + /** @todo Make the following configurable through mixer API and/or CFGM? */ + switch (pCfg->Props.cChannels) + { + case 3: /* 2.1: Front (Stereo) + LFE. */ + { + fUseLFE = true; + break; + } + + case 4: /* Quadrophonic: Front (Stereo) + Rear (Stereo). */ + { + fUseRear = true; + break; + } + + case 5: /* 4.1: Front (Stereo) + Rear (Stereo) + LFE. */ + { + fUseRear = true; + fUseLFE = true; + break; + } + + case 6: /* 5.1: Front (Stereo) + Rear (Stereo) + Center/LFE. */ + { + fUseRear = true; + fUseCenter = true; + fUseLFE = true; + break; + } + + default: /* Unknown; fall back to 2 front channels (stereo). */ + { + rc = VERR_NOT_SUPPORTED; + break; + } + } +# endif /* !VBOX_WITH_AUDIO_HDA_51_SURROUND */ + + if (rc == VERR_NOT_SUPPORTED) + { + LogRel2(("HDA: Warning: Unsupported channel count (%RU8), falling back to stereo channels (2)\n", pCfg->Props.cChannels)); + + /* Fall back to 2 channels (see below in fUseFront block). */ + rc = VINF_SUCCESS; + } + + do + { + if (RT_FAILURE(rc)) + break; + + if (fUseFront) + { + RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Front"); + + pCfg->DestSource.Dest = PDMAUDIOPLAYBACKDEST_FRONT; + pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + + pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cBytes, pCfg->Props.cChannels); + + rc = hdaCodecAddStream(pThis->pCodec, PDMAUDIOMIXERCTL_FRONT, pCfg); + } + +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + if ( RT_SUCCESS(rc) + && (fUseCenter || fUseLFE)) + { + RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Center/LFE"); + + pCfg->DestSource.Dest = PDMAUDIOPLAYBACKDEST_CENTER_LFE; + pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + + pCfg->Props.cChannels = (fUseCenter && fUseLFE) ? 2 : 1; + pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cBytes, pCfg->Props.cChannels); + + rc = hdaCodecAddStream(pThis->pCodec, PDMAUDIOMIXERCTL_CENTER_LFE, pCfg); + } + + if ( RT_SUCCESS(rc) + && fUseRear) + { + RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Rear"); + + pCfg->DestSource.Dest = PDMAUDIOPLAYBACKDEST_REAR; + pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + + pCfg->Props.cChannels = 2; + pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cBytes, pCfg->Props.cChannels); + + rc = hdaCodecAddStream(pThis->pCodec, PDMAUDIOMIXERCTL_REAR, pCfg); + } +# endif /* VBOX_WITH_AUDIO_HDA_51_SURROUND */ + + } while (0); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Adds an audio input stream to the device setup using the given configuration. + * + * @returns IPRT status code. + * @param pThis Device state. + * @param pCfg Stream configuration to use for adding a stream. + */ +static int hdaR3AddStreamIn(PHDASTATE pThis, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + AssertReturn(pCfg->enmDir == PDMAUDIODIR_IN, VERR_INVALID_PARAMETER); + + LogFlowFunc(("Stream=%s, Source=%ld\n", pCfg->szName, pCfg->DestSource.Source)); + + int rc; + + switch (pCfg->DestSource.Source) + { + case PDMAUDIORECSOURCE_LINE: + { + rc = hdaCodecAddStream(pThis->pCodec, PDMAUDIOMIXERCTL_LINE_IN, pCfg); + break; + } +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + case PDMAUDIORECSOURCE_MIC: + { + rc = hdaCodecAddStream(pThis->pCodec, PDMAUDIOMIXERCTL_MIC_IN, pCfg); + break; + } +# endif + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Adds an audio stream to the device setup using the given configuration. + * + * @returns IPRT status code. + * @param pThis Device state. + * @param pCfg Stream configuration to use for adding a stream. + */ +static int hdaR3AddStream(PHDASTATE pThis, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + int rc; + + LogFlowFuncEnter(); + + switch (pCfg->enmDir) + { + case PDMAUDIODIR_OUT: + rc = hdaR3AddStreamOut(pThis, pCfg); + break; + + case PDMAUDIODIR_IN: + rc = hdaR3AddStreamIn(pThis, pCfg); + break; + + default: + rc = VERR_NOT_SUPPORTED; + AssertFailed(); + break; + } + + LogFlowFunc(("Returning %Rrc\n", rc)); + + return rc; +} + +/** + * Removes an audio stream from the device setup using the given configuration. + * + * @returns IPRT status code. + * @param pThis Device state. + * @param pCfg Stream configuration to use for removing a stream. + */ +static int hdaR3RemoveStream(PHDASTATE pThis, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + PDMAUDIOMIXERCTL enmMixerCtl = PDMAUDIOMIXERCTL_UNKNOWN; + switch (pCfg->enmDir) + { + case PDMAUDIODIR_IN: + { + LogFlowFunc(("Stream=%s, Source=%ld\n", pCfg->szName, pCfg->DestSource.Source)); + + switch (pCfg->DestSource.Source) + { + case PDMAUDIORECSOURCE_UNKNOWN: break; + case PDMAUDIORECSOURCE_LINE: enmMixerCtl = PDMAUDIOMIXERCTL_LINE_IN; break; +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + case PDMAUDIORECSOURCE_MIC: enmMixerCtl = PDMAUDIOMIXERCTL_MIC_IN; break; +# endif + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + break; + } + + case PDMAUDIODIR_OUT: + { + LogFlowFunc(("Stream=%s, Source=%ld\n", pCfg->szName, pCfg->DestSource.Dest)); + + switch (pCfg->DestSource.Dest) + { + case PDMAUDIOPLAYBACKDEST_UNKNOWN: break; + case PDMAUDIOPLAYBACKDEST_FRONT: enmMixerCtl = PDMAUDIOMIXERCTL_FRONT; break; +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + case PDMAUDIOPLAYBACKDEST_CENTER_LFE: enmMixerCtl = PDMAUDIOMIXERCTL_CENTER_LFE; break; + case PDMAUDIOPLAYBACKDEST_REAR: enmMixerCtl = PDMAUDIOMIXERCTL_REAR; break; +# endif + default: + rc = VERR_NOT_SUPPORTED; + break; + } + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + if ( RT_SUCCESS(rc) + && enmMixerCtl != PDMAUDIOMIXERCTL_UNKNOWN) + { + rc = hdaCodecRemoveStream(pThis->pCodec, enmMixerCtl); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} +#endif /* IN_RING3 */ + +static int hdaRegWriteSDFMT(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + /* Write the wanted stream format into the register in any case. + * + * This is important for e.g. MacOS guests, as those try to initialize streams which are not reported + * by the device emulation (wants 4 channels, only have 2 channels at the moment). + * + * When ignoring those (invalid) formats, this leads to MacOS thinking that the device is malfunctioning + * and therefore disabling the device completely. */ + int rc = hdaRegWriteU16(pThis, iReg, u32Value); + AssertRC(rc); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; /* Always return success to the MMIO handler. */ +} + +/* Note: Will be called for both, BDPL and BDPU, registers. */ +DECLINLINE(int) hdaRegWriteSDBDPX(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value, uint8_t uSD) +{ +#ifdef IN_RING3 + DEVHDA_LOCK(pThis); + +# ifdef HDA_USE_DMA_ACCESS_HANDLER + if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) + { + PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uSD); + + /* Try registering the DMA handlers. + * As we can't be sure in which order LVI + BDL base are set, try registering in both routines. */ + if ( pStream + && hdaR3StreamRegisterDMAHandlers(pThis, pStream)) + { + LogFunc(("[SD%RU8] DMA logging enabled\n", pStream->u8SD)); + } + } +# else + RT_NOREF(uSD); +# endif + + int rc2 = hdaRegWriteU32(pThis, iReg, u32Value); + AssertRC(rc2); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; /* Always return success to the MMIO handler. */ +#else /* !IN_RING3 */ + RT_NOREF_PV(pThis); RT_NOREF_PV(iReg); RT_NOREF_PV(u32Value); RT_NOREF_PV(uSD); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* IN_RING3 */ +} + +static int hdaRegWriteSDBDPL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + return hdaRegWriteSDBDPX(pThis, iReg, u32Value, HDA_SD_NUM_FROM_REG(pThis, BDPL, iReg)); +} + +static int hdaRegWriteSDBDPU(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + return hdaRegWriteSDBDPX(pThis, iReg, u32Value, HDA_SD_NUM_FROM_REG(pThis, BDPU, iReg)); +} + +static int hdaRegReadIRS(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) +{ + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_READ); + + /* regarding 3.4.3 we should mark IRS as busy in case CORB is active */ + if ( HDA_REG(pThis, CORBWP) != HDA_REG(pThis, CORBRP) + || (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA)) + { + HDA_REG(pThis, IRS) = HDA_IRS_ICB; /* busy */ + } + + int rc = hdaRegReadU32(pThis, iReg, pu32Value); + DEVHDA_UNLOCK(pThis); + + return rc; +} + +static int hdaRegWriteIRS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF_PV(iReg); + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + /* + * If the guest set the ICB bit of IRS register, HDA should process the verb in IC register, + * write the response to IR register, and set the IRV (valid in case of success) bit of IRS register. + */ + if ( (u32Value & HDA_IRS_ICB) + && !(HDA_REG(pThis, IRS) & HDA_IRS_ICB)) + { +#ifdef IN_RING3 + uint32_t uCmd = HDA_REG(pThis, IC); + + if (HDA_REG(pThis, CORBWP) != HDA_REG(pThis, CORBRP)) + { + DEVHDA_UNLOCK(pThis); + + /* + * 3.4.3: Defines behavior of immediate Command status register. + */ + LogRel(("HDA: Guest attempted process immediate verb (%x) with active CORB\n", uCmd)); + return VINF_SUCCESS; + } + + HDA_REG(pThis, IRS) = HDA_IRS_ICB; /* busy */ + + uint64_t uResp; + int rc2 = pThis->pCodec->pfnLookup(pThis->pCodec, + HDA_CODEC_CMD(uCmd, 0 /* LUN */), &uResp); + if (RT_FAILURE(rc2)) + LogFunc(("Codec lookup failed with rc2=%Rrc\n", rc2)); + + HDA_REG(pThis, IR) = (uint32_t)uResp; /** @todo r=andy Do we need a 64-bit response? */ + HDA_REG(pThis, IRS) = HDA_IRS_IRV; /* result is ready */ + /** @todo r=michaln We just set the IRS value, why are we clearing unset bits? */ + HDA_REG(pThis, IRS) &= ~HDA_IRS_ICB; /* busy is clear */ + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +#else /* !IN_RING3 */ + DEVHDA_UNLOCK(pThis); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ + } + + /* + * Once the guest read the response, it should clear the IRV bit of the IRS register. + */ + HDA_REG(pThis, IRS) &= ~(u32Value & HDA_IRS_IRV); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +} + +static int hdaRegWriteRIRBWP(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(iReg); + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Ignore request if CORB DMA engine is (still) running. */ + { + LogFunc(("CORB DMA (still) running, skipping\n")); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; + } + + if (u32Value & HDA_RIRBWP_RST) + { + /* Do a RIRB reset. */ + if (pThis->cbRirbBuf) + { + Assert(pThis->pu64RirbBuf); + RT_BZERO((void *)pThis->pu64RirbBuf, pThis->cbRirbBuf); + } + + LogRel2(("HDA: RIRB reset\n")); + + HDA_REG(pThis, RIRBWP) = 0; + } + + /* The remaining bits are O, see 6.2.22. */ + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +} + +static int hdaRegWriteRINTCNT(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Ignore request if CORB DMA engine is (still) running. */ + { + LogFunc(("CORB DMA is (still) running, skipping\n")); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; + } + + int rc = hdaRegWriteU16(pThis, iReg, u32Value); + AssertRC(rc); + + LogFunc(("Response interrupt count is now %RU8\n", HDA_REG(pThis, RINTCNT) & 0xFF)); + + DEVHDA_UNLOCK(pThis); + return rc; +} + +static int hdaRegWriteBase(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + uint32_t iRegMem = g_aHdaRegMap[iReg].mem_idx; + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + int rc = hdaRegWriteU32(pThis, iReg, u32Value); + AssertRCSuccess(rc); + + switch (iReg) + { + case HDA_REG_CORBLBASE: + pThis->u64CORBBase &= UINT64_C(0xFFFFFFFF00000000); + pThis->u64CORBBase |= pThis->au32Regs[iRegMem]; + break; + case HDA_REG_CORBUBASE: + pThis->u64CORBBase &= UINT64_C(0x00000000FFFFFFFF); + pThis->u64CORBBase |= ((uint64_t)pThis->au32Regs[iRegMem] << 32); + break; + case HDA_REG_RIRBLBASE: + pThis->u64RIRBBase &= UINT64_C(0xFFFFFFFF00000000); + pThis->u64RIRBBase |= pThis->au32Regs[iRegMem]; + break; + case HDA_REG_RIRBUBASE: + pThis->u64RIRBBase &= UINT64_C(0x00000000FFFFFFFF); + pThis->u64RIRBBase |= ((uint64_t)pThis->au32Regs[iRegMem] << 32); + break; + case HDA_REG_DPLBASE: + { + pThis->u64DPBase = pThis->au32Regs[iRegMem] & DPBASE_ADDR_MASK; + Assert(pThis->u64DPBase % 128 == 0); /* Must be 128-byte aligned. */ + + /* Also make sure to handle the DMA position enable bit. */ + pThis->fDMAPosition = pThis->au32Regs[iRegMem] & RT_BIT_32(0); + LogRel(("HDA: %s DMA position buffer\n", pThis->fDMAPosition ? "Enabled" : "Disabled")); + break; + } + case HDA_REG_DPUBASE: + pThis->u64DPBase = RT_MAKE_U64(RT_LO_U32(pThis->u64DPBase) & DPBASE_ADDR_MASK, pThis->au32Regs[iRegMem]); + break; + default: + AssertMsgFailed(("Invalid index\n")); + break; + } + + LogFunc(("CORB base:%llx RIRB base: %llx DP base: %llx\n", + pThis->u64CORBBase, pThis->u64RIRBBase, pThis->u64DPBase)); + + DEVHDA_UNLOCK(pThis); + return rc; +} + +static int hdaRegWriteRIRBSTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF_PV(iReg); + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + uint8_t v = HDA_REG(pThis, RIRBSTS); + HDA_REG(pThis, RIRBSTS) &= ~(v & u32Value); + +#ifndef LOG_ENABLED + int rc = hdaProcessInterrupt(pThis); +#else + int rc = hdaProcessInterrupt(pThis, __FUNCTION__); +#endif + + DEVHDA_UNLOCK(pThis); + return rc; +} + +#ifdef IN_RING3 + +/** + * Retrieves a corresponding sink for a given mixer control. + * Returns NULL if no sink is found. + * + * @return PHDAMIXERSINK + * @param pThis HDA state. + * @param enmMixerCtl Mixer control to get the corresponding sink for. + */ +static PHDAMIXERSINK hdaR3MixerControlToSink(PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl) +{ + PHDAMIXERSINK pSink; + + switch (enmMixerCtl) + { + case PDMAUDIOMIXERCTL_VOLUME_MASTER: + /* Fall through is intentional. */ + case PDMAUDIOMIXERCTL_FRONT: + pSink = &pThis->SinkFront; + break; +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + case PDMAUDIOMIXERCTL_CENTER_LFE: + pSink = &pThis->SinkCenterLFE; + break; + case PDMAUDIOMIXERCTL_REAR: + pSink = &pThis->SinkRear; + break; +# endif + case PDMAUDIOMIXERCTL_LINE_IN: + pSink = &pThis->SinkLineIn; + break; +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + case PDMAUDIOMIXERCTL_MIC_IN: + pSink = &pThis->SinkMicIn; + break; +# endif + default: + pSink = NULL; + AssertMsgFailed(("Unhandled mixer control\n")); + break; + } + + return pSink; +} + +/** + * Adds a specific HDA driver to the driver chain. + * + * @return IPRT status code. + * @param pThis HDA state. + * @param pDrv HDA driver to add. + */ +static int hdaR3MixerAddDrv(PHDASTATE pThis, PHDADRIVER pDrv) +{ + int rc = VINF_SUCCESS; + + PHDASTREAM pStream = hdaR3GetStreamFromSink(pThis, &pThis->SinkLineIn); + if ( pStream + && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) + { + int rc2 = hdaR3MixerAddDrvStream(pThis, pThis->SinkLineIn.pMixSink, &pStream->State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + pStream = hdaR3GetStreamFromSink(pThis, &pThis->SinkMicIn); + if ( pStream + && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) + { + int rc2 = hdaR3MixerAddDrvStream(pThis, pThis->SinkMicIn.pMixSink, &pStream->State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } +# endif + + pStream = hdaR3GetStreamFromSink(pThis, &pThis->SinkFront); + if ( pStream + && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) + { + int rc2 = hdaR3MixerAddDrvStream(pThis, pThis->SinkFront.pMixSink, &pStream->State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + pStream = hdaR3GetStreamFromSink(pThis, &pThis->SinkCenterLFE); + if ( pStream + && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) + { + int rc2 = hdaR3MixerAddDrvStream(pThis, pThis->SinkCenterLFE.pMixSink, &pStream->State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + pStream = hdaR3GetStreamFromSink(pThis, &pThis->SinkRear); + if ( pStream + && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) + { + int rc2 = hdaR3MixerAddDrvStream(pThis, pThis->SinkRear.pMixSink, &pStream->State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } +# endif + + return rc; +} + +/** + * Removes a specific HDA driver from the driver chain and destroys its + * associated streams. + * + * @param pThis HDA state. + * @param pDrv HDA driver to remove. + */ +static void hdaR3MixerRemoveDrv(PHDASTATE pThis, PHDADRIVER pDrv) +{ + AssertPtrReturnVoid(pThis); + AssertPtrReturnVoid(pDrv); + + if (pDrv->LineIn.pMixStrm) + { + if (AudioMixerSinkGetRecordingSource(pThis->SinkLineIn.pMixSink) == pDrv->LineIn.pMixStrm) + AudioMixerSinkSetRecordingSource(pThis->SinkLineIn.pMixSink, NULL); + + AudioMixerSinkRemoveStream(pThis->SinkLineIn.pMixSink, pDrv->LineIn.pMixStrm); + AudioMixerStreamDestroy(pDrv->LineIn.pMixStrm); + pDrv->LineIn.pMixStrm = NULL; + } + +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + if (pDrv->MicIn.pMixStrm) + { + if (AudioMixerSinkGetRecordingSource(pThis->SinkMicIn.pMixSink) == pDrv->MicIn.pMixStrm) + AudioMixerSinkSetRecordingSource(&pThis->SinkMicIn.pMixSink, NULL); + + AudioMixerSinkRemoveStream(pThis->SinkMicIn.pMixSink, pDrv->MicIn.pMixStrm); + AudioMixerStreamDestroy(pDrv->MicIn.pMixStrm); + pDrv->MicIn.pMixStrm = NULL; + } +# endif + + if (pDrv->Front.pMixStrm) + { + AudioMixerSinkRemoveStream(pThis->SinkFront.pMixSink, pDrv->Front.pMixStrm); + AudioMixerStreamDestroy(pDrv->Front.pMixStrm); + pDrv->Front.pMixStrm = NULL; + } + +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + if (pDrv->CenterLFE.pMixStrm) + { + AudioMixerSinkRemoveStream(pThis->SinkCenterLFE.pMixSink, pDrv->CenterLFE.pMixStrm); + AudioMixerStreamDestroy(pDrv->CenterLFE.pMixStrm); + pDrv->CenterLFE.pMixStrm = NULL; + } + + if (pDrv->Rear.pMixStrm) + { + AudioMixerSinkRemoveStream(pThis->SinkRear.pMixSink, pDrv->Rear.pMixStrm); + AudioMixerStreamDestroy(pDrv->Rear.pMixStrm); + pDrv->Rear.pMixStrm = NULL; + } +# endif + + RTListNodeRemove(&pDrv->Node); +} + +/** + * Adds a driver stream to a specific mixer sink. + * + * @returns IPRT status code (ignored by caller). + * @param pThis HDA state. + * @param pMixSink Audio mixer sink to add audio streams to. + * @param pCfg Audio stream configuration to use for the audio streams to add. + * @param pDrv Driver stream to add. + */ +static int hdaR3MixerAddDrvStream(PHDASTATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PHDADRIVER pDrv) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pMixSink, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + LogFunc(("szSink=%s, szStream=%s, cChannels=%RU8\n", pMixSink->pszName, pCfg->szName, pCfg->Props.cChannels)); + + PPDMAUDIOSTREAMCFG pStreamCfg = DrvAudioHlpStreamCfgDup(pCfg); + if (!pStreamCfg) + return VERR_NO_MEMORY; + + LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pStreamCfg->szName)); + + int rc = VINF_SUCCESS; + + PHDADRIVERSTREAM pDrvStream = NULL; + + if (pStreamCfg->enmDir == PDMAUDIODIR_IN) + { + LogFunc(("enmRecSource=%d\n", pStreamCfg->DestSource.Source)); + + switch (pStreamCfg->DestSource.Source) + { + case PDMAUDIORECSOURCE_LINE: + pDrvStream = &pDrv->LineIn; + break; +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + case PDMAUDIORECSOURCE_MIC: + pDrvStream = &pDrv->MicIn; + break; +# endif + default: + rc = VERR_NOT_SUPPORTED; + break; + } + } + else if (pStreamCfg->enmDir == PDMAUDIODIR_OUT) + { + LogFunc(("enmPlaybackDest=%d\n", pStreamCfg->DestSource.Dest)); + + switch (pStreamCfg->DestSource.Dest) + { + case PDMAUDIOPLAYBACKDEST_FRONT: + pDrvStream = &pDrv->Front; + break; +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + case PDMAUDIOPLAYBACKDEST_CENTER_LFE: + pDrvStream = &pDrv->CenterLFE; + break; + case PDMAUDIOPLAYBACKDEST_REAR: + pDrvStream = &pDrv->Rear; + break; +# endif + default: + rc = VERR_NOT_SUPPORTED; + break; + } + } + else + rc = VERR_NOT_SUPPORTED; + + if (RT_SUCCESS(rc)) + { + AssertPtr(pDrvStream); + AssertMsg(pDrvStream->pMixStrm == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN)); + + PAUDMIXSTREAM pMixStrm; + rc = AudioMixerSinkCreateStream(pMixSink, pDrv->pConnector, pStreamCfg, 0 /* fFlags */, &pMixStrm); + LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pStreamCfg->szName, rc)); + if (RT_SUCCESS(rc)) + { + rc = AudioMixerSinkAddStream(pMixSink, pMixStrm); + LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pStreamCfg->szName, rc)); + if (RT_SUCCESS(rc)) + { + /* If this is an input stream, always set the latest (added) stream + * as the recording source. + * @todo Make the recording source dynamic (CFGM?). */ + if (pStreamCfg->enmDir == PDMAUDIODIR_IN) + { + PDMAUDIOBACKENDCFG Cfg; + rc = pDrv->pConnector->pfnGetConfig(pDrv->pConnector, &Cfg); + if (RT_SUCCESS(rc)) + { + if (Cfg.cMaxStreamsIn) /* At least one input source available? */ + { + rc = AudioMixerSinkSetRecordingSource(pMixSink, pMixStrm); + LogFlowFunc(("LUN#%RU8: Recording source for '%s' -> '%s', rc=%Rrc\n", + pDrv->uLUN, pStreamCfg->szName, Cfg.szName, rc)); + + if (RT_SUCCESS(rc)) + LogRel(("HDA: Set recording source for '%s' to '%s'\n", + pStreamCfg->szName, Cfg.szName)); + } + else + LogRel(("HDA: Backend '%s' currently is not offering any recording source for '%s'\n", + Cfg.szName, pStreamCfg->szName)); + } + else if (RT_FAILURE(rc)) + LogFunc(("LUN#%RU8: Unable to retrieve backend configuration for '%s', rc=%Rrc\n", + pDrv->uLUN, pStreamCfg->szName, rc)); + } + } + } + + if (RT_SUCCESS(rc)) + pDrvStream->pMixStrm = pMixStrm; + } + + DrvAudioHlpStreamCfgFree(pStreamCfg); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Adds all current driver streams to a specific mixer sink. + * + * @returns IPRT status code. + * @param pThis HDA state. + * @param pMixSink Audio mixer sink to add stream to. + * @param pCfg Audio stream configuration to use for the audio streams to add. + */ +static int hdaR3MixerAddDrvStreams(PHDASTATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pMixSink, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + LogFunc(("Sink=%s, Stream=%s\n", pMixSink->pszName, pCfg->szName)); + + if (!DrvAudioHlpStreamCfgIsValid(pCfg)) + return VERR_INVALID_PARAMETER; + + int rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props); + if (RT_FAILURE(rc)) + return rc; + + PHDADRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, HDADRIVER, Node) + { + int rc2 = hdaR3MixerAddDrvStream(pThis, pMixSink, pCfg, pDrv); + if (RT_FAILURE(rc2)) + LogFunc(("Attaching stream failed with %Rrc\n", rc2)); + + /* Do not pass failure to rc here, as there might be drivers which aren't + * configured / ready yet. */ + } + + return rc; +} + +/** + * @interface_method_impl{HDACODEC,pfnCbMixerAddStream} + * + * Adds a new audio stream to a specific mixer control. + * + * Depending on the mixer control the stream then gets assigned to one of the internal + * mixer sinks, which in turn then handle the mixing of all connected streams to that sink. + * + * @return IPRT status code. + * @param pThis HDA state. + * @param enmMixerCtl Mixer control to assign new stream to. + * @param pCfg Stream configuration for the new stream. + */ +static DECLCALLBACK(int) hdaR3MixerAddStream(PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + int rc; + + PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThis, enmMixerCtl); + if (pSink) + { + rc = hdaR3MixerAddDrvStreams(pThis, pSink->pMixSink, pCfg); + + AssertPtr(pSink->pMixSink); + LogFlowFunc(("Sink=%s, Mixer control=%s\n", pSink->pMixSink->pszName, DrvAudioHlpAudMixerCtlToStr(enmMixerCtl))); + } + else + rc = VERR_NOT_FOUND; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @interface_method_impl{HDACODEC,pfnCbMixerRemoveStream} + * + * Removes a specified mixer control from the HDA's mixer. + * + * @return IPRT status code. + * @param pThis HDA state. + * @param enmMixerCtl Mixer control to remove. + * + * @remarks Can be called as a callback by the HDA codec. + */ +static DECLCALLBACK(int) hdaR3MixerRemoveStream(PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + int rc; + + PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThis, enmMixerCtl); + if (pSink) + { + PHDADRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, HDADRIVER, Node) + { + PAUDMIXSTREAM pMixStream = NULL; + switch (enmMixerCtl) + { + /* + * Input. + */ + case PDMAUDIOMIXERCTL_LINE_IN: + pMixStream = pDrv->LineIn.pMixStrm; + pDrv->LineIn.pMixStrm = NULL; + break; +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + case PDMAUDIOMIXERCTL_MIC_IN: + pMixStream = pDrv->MicIn.pMixStrm; + pDrv->MicIn.pMixStrm = NULL; + break; +# endif + /* + * Output. + */ + case PDMAUDIOMIXERCTL_FRONT: + pMixStream = pDrv->Front.pMixStrm; + pDrv->Front.pMixStrm = NULL; + break; +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + case PDMAUDIOMIXERCTL_CENTER_LFE: + pMixStream = pDrv->CenterLFE.pMixStrm; + pDrv->CenterLFE.pMixStrm = NULL; + break; + case PDMAUDIOMIXERCTL_REAR: + pMixStream = pDrv->Rear.pMixStrm; + pDrv->Rear.pMixStrm = NULL; + break; +# endif + default: + AssertMsgFailed(("Mixer control %d not implemented\n", enmMixerCtl)); + break; + } + + if (pMixStream) + { + AudioMixerSinkRemoveStream(pSink->pMixSink, pMixStream); + AudioMixerStreamDestroy(pMixStream); + + pMixStream = NULL; + } + } + + AudioMixerSinkRemoveAllStreams(pSink->pMixSink); + rc = VINF_SUCCESS; + } + else + rc = VERR_NOT_FOUND; + + LogFunc(("Mixer control=%s, rc=%Rrc\n", DrvAudioHlpAudMixerCtlToStr(enmMixerCtl), rc)); + return rc; +} + +/** + * @interface_method_impl{HDACODEC,pfnCbMixerControl} + * + * Controls an input / output converter widget, that is, which converter is connected + * to which stream (and channel). + * + * @returns IPRT status code. + * @param pThis HDA State. + * @param enmMixerCtl Mixer control to set SD stream number and channel for. + * @param uSD SD stream number (number + 1) to set. Set to 0 for unassign. + * @param uChannel Channel to set. Only valid if a valid SD stream number is specified. + * + * @remarks Can be called as a callback by the HDA codec. + */ +static DECLCALLBACK(int) hdaR3MixerControl(PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl, uint8_t uSD, uint8_t uChannel) +{ + LogFunc(("enmMixerCtl=%s, uSD=%RU8, uChannel=%RU8\n", DrvAudioHlpAudMixerCtlToStr(enmMixerCtl), uSD, uChannel)); + + if (uSD == 0) /* Stream number 0 is reserved. */ + { + Log2Func(("Invalid SDn (%RU8) number for mixer control '%s', ignoring\n", uSD, DrvAudioHlpAudMixerCtlToStr(enmMixerCtl))); + return VINF_SUCCESS; + } + /* uChannel is optional. */ + + /* SDn0 starts as 1. */ + Assert(uSD); + uSD--; + +# ifndef VBOX_WITH_AUDIO_HDA_MIC_IN + /* Only SDI0 (Line-In) is supported. */ + if ( hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN + && uSD >= 1) + { + LogRel2(("HDA: Dedicated Mic-In support not imlpemented / built-in (stream #%RU8), using Line-In (stream #0) instead\n", uSD)); + uSD = 0; + } +# endif + + int rc = VINF_SUCCESS; + + PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThis, enmMixerCtl); + if (pSink) + { + AssertPtr(pSink->pMixSink); + + /* If this an output stream, determine the correct SD#. */ + if ( (uSD < HDA_MAX_SDI) + && AudioMixerSinkGetDir(pSink->pMixSink) == AUDMIXSINKDIR_OUTPUT) + { + uSD += HDA_MAX_SDI; + } + + /* Detach the existing stream from the sink. */ + if ( pSink->pStream + && ( pSink->pStream->u8SD != uSD + || pSink->pStream->u8Channel != uChannel) + ) + { + LogFunc(("Sink '%s' was assigned to stream #%RU8 (channel %RU8) before\n", + pSink->pMixSink->pszName, pSink->pStream->u8SD, pSink->pStream->u8Channel)); + + hdaR3StreamLock(pSink->pStream); + + /* Only disable the stream if the stream descriptor # has changed. */ + if (pSink->pStream->u8SD != uSD) + hdaR3StreamEnable(pSink->pStream, false); + + pSink->pStream->pMixSink = NULL; + + hdaR3StreamUnlock(pSink->pStream); + + pSink->pStream = NULL; + } + + Assert(uSD < HDA_MAX_STREAMS); + + /* Attach the new stream to the sink. + * Enabling the stream will be done by the gust via a separate SDnCTL call then. */ + if (pSink->pStream == NULL) + { + LogRel2(("HDA: Setting sink '%s' to stream #%RU8 (channel %RU8), mixer control=%s\n", + pSink->pMixSink->pszName, uSD, uChannel, DrvAudioHlpAudMixerCtlToStr(enmMixerCtl))); + + PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uSD); + if (pStream) + { + hdaR3StreamLock(pStream); + + pSink->pStream = pStream; + + pStream->u8Channel = uChannel; + pStream->pMixSink = pSink; + + hdaR3StreamUnlock(pStream); + + rc = VINF_SUCCESS; + } + else + rc = VERR_NOT_IMPLEMENTED; + } + } + else + rc = VERR_NOT_FOUND; + + if (RT_FAILURE(rc)) + LogRel(("HDA: Converter control for stream #%RU8 (channel %RU8) / mixer control '%s' failed with %Rrc, skipping\n", + uSD, uChannel, DrvAudioHlpAudMixerCtlToStr(enmMixerCtl), rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @interface_method_impl{HDACODEC,pfnCbMixerSetVolume} + * + * Sets the volume of a specified mixer control. + * + * @return IPRT status code. + * @param pThis HDA State. + * @param enmMixerCtl Mixer control to set volume for. + * @param pVol Pointer to volume data to set. + * + * @remarks Can be called as a callback by the HDA codec. + */ +static DECLCALLBACK(int) hdaR3MixerSetVolume(PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOVOLUME pVol) +{ + int rc; + + PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThis, enmMixerCtl); + if ( pSink + && pSink->pMixSink) + { + LogRel2(("HDA: Setting volume for mixer sink '%s' to %RU8/%RU8 (%s)\n", + pSink->pMixSink->pszName, pVol->uLeft, pVol->uRight, pVol->fMuted ? "Muted" : "Unmuted")); + + /* Set the volume. + * We assume that the codec already converted it to the correct range. */ + rc = AudioMixerSinkSetVolume(pSink->pMixSink, pVol); + } + else + rc = VERR_NOT_FOUND; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Main routine for the stream's timer. + * + * @param pDevIns Device instance. + * @param pTimer Timer this callback was called for. + * @param pvUser Pointer to associated HDASTREAM. + */ +static DECLCALLBACK(void) hdaR3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) +{ + RT_NOREF(pDevIns, pTimer); + + PHDASTREAM pStream = (PHDASTREAM)pvUser; + AssertPtr(pStream); + + PHDASTATE pThis = pStream->pHDAState; + + DEVHDA_LOCK_BOTH_RETURN_VOID(pStream->pHDAState, pStream->u8SD); + + hdaR3StreamUpdate(pStream, true /* fInTimer */); + + /* Flag indicating whether to kick the timer again for a new data processing round. */ + bool fSinkActive = false; + if (pStream->pMixSink) + fSinkActive = AudioMixerSinkIsActive(pStream->pMixSink->pMixSink); + + if (fSinkActive) + { + const bool fTimerScheduled = hdaR3StreamTransferIsScheduled(pStream); + Log3Func(("fSinksActive=%RTbool, fTimerScheduled=%RTbool\n", fSinkActive, fTimerScheduled)); + if (!fTimerScheduled) + hdaR3TimerSet(pThis, pStream, + TMTimerGet(pThis->pTimer[pStream->u8SD]) + + TMTimerGetFreq(pThis->pTimer[pStream->u8SD]) / pStream->pHDAState->uTimerHz, + true /* fForce */); + } + else + Log3Func(("fSinksActive=%RTbool\n", fSinkActive)); + + DEVHDA_UNLOCK_BOTH(pThis, pStream->u8SD); +} + +# ifdef HDA_USE_DMA_ACCESS_HANDLER +/** + * HC access handler for the FIFO. + * + * @returns VINF_SUCCESS if the handler have carried out the operation. + * @returns VINF_PGM_HANDLER_DO_DEFAULT if the caller should carry out the access operation. + * @param pVM VM Handle. + * @param pVCpu The cross context CPU structure for the calling EMT. + * @param GCPhys The physical address the guest is writing to. + * @param pvPhys The HC mapping of that address. + * @param pvBuf What the guest is reading/writing. + * @param cbBuf How much it's reading/writing. + * @param enmAccessType The access type. + * @param enmOrigin Who is making the access. + * @param pvUser User argument. + */ +static DECLCALLBACK(VBOXSTRICTRC) hdaR3DMAAccessHandler(PVM pVM, PVMCPU pVCpu, RTGCPHYS GCPhys, void *pvPhys, + void *pvBuf, size_t cbBuf, + PGMACCESSTYPE enmAccessType, PGMACCESSORIGIN enmOrigin, void *pvUser) +{ + RT_NOREF(pVM, pVCpu, pvPhys, pvBuf, enmOrigin); + + PHDADMAACCESSHANDLER pHandler = (PHDADMAACCESSHANDLER)pvUser; + AssertPtr(pHandler); + + PHDASTREAM pStream = pHandler->pStream; + AssertPtr(pStream); + + Assert(GCPhys >= pHandler->GCPhysFirst); + Assert(GCPhys <= pHandler->GCPhysLast); + Assert(enmAccessType == PGMACCESSTYPE_WRITE); + + /* Not within BDLE range? Bail out. */ + if ( (GCPhys < pHandler->BDLEAddr) + || (GCPhys + cbBuf > pHandler->BDLEAddr + pHandler->BDLESize)) + { + return VINF_PGM_HANDLER_DO_DEFAULT; + } + + switch(enmAccessType) + { + case PGMACCESSTYPE_WRITE: + { +# ifdef DEBUG + PHDASTREAMDBGINFO pStreamDbg = &pStream->Dbg; + + const uint64_t tsNowNs = RTTimeNanoTS(); + const uint32_t tsElapsedMs = (tsNowNs - pStreamDbg->tsWriteSlotBegin) / 1000 / 1000; + + uint64_t cWritesHz = ASMAtomicReadU64(&pStreamDbg->cWritesHz); + uint64_t cbWrittenHz = ASMAtomicReadU64(&pStreamDbg->cbWrittenHz); + + if (tsElapsedMs >= (1000 / HDA_TIMER_HZ_DEFAULT)) + { + LogFunc(("[SD%RU8] %RU32ms elapsed, cbWritten=%RU64, cWritten=%RU64 -- %RU32 bytes on average per time slot (%zums)\n", + pStream->u8SD, tsElapsedMs, cbWrittenHz, cWritesHz, + ASMDivU64ByU32RetU32(cbWrittenHz, cWritesHz ? cWritesHz : 1), 1000 / HDA_TIMER_HZ_DEFAULT)); + + pStreamDbg->tsWriteSlotBegin = tsNowNs; + + cWritesHz = 0; + cbWrittenHz = 0; + } + + cWritesHz += 1; + cbWrittenHz += cbBuf; + + ASMAtomicIncU64(&pStreamDbg->cWritesTotal); + ASMAtomicAddU64(&pStreamDbg->cbWrittenTotal, cbBuf); + + ASMAtomicWriteU64(&pStreamDbg->cWritesHz, cWritesHz); + ASMAtomicWriteU64(&pStreamDbg->cbWrittenHz, cbWrittenHz); + + LogFunc(("[SD%RU8] Writing %3zu @ 0x%x (off %zu)\n", + pStream->u8SD, cbBuf, GCPhys, GCPhys - pHandler->BDLEAddr)); + + LogFunc(("[SD%RU8] cWrites=%RU64, cbWritten=%RU64 -> %RU32 bytes on average\n", + pStream->u8SD, pStreamDbg->cWritesTotal, pStreamDbg->cbWrittenTotal, + ASMDivU64ByU32RetU32(pStreamDbg->cbWrittenTotal, pStreamDbg->cWritesTotal))); +# endif + + if (pThis->fDebugEnabled) + { + RTFILE fh; + RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "hdaDMAAccessWrite.pcm", + RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + RTFileWrite(fh, pvBuf, cbBuf, NULL); + RTFileClose(fh); + } + +# ifdef HDA_USE_DMA_ACCESS_HANDLER_WRITING + PRTCIRCBUF pCircBuf = pStream->State.pCircBuf; + AssertPtr(pCircBuf); + + uint8_t *pbBuf = (uint8_t *)pvBuf; + while (cbBuf) + { + /* Make sure we only copy as much as the stream's FIFO can hold (SDFIFOS, 18.2.39). */ + void *pvChunk; + size_t cbChunk; + RTCircBufAcquireWriteBlock(pCircBuf, cbBuf, &pvChunk, &cbChunk); + + if (cbChunk) + { + memcpy(pvChunk, pbBuf, cbChunk); + + pbBuf += cbChunk; + Assert(cbBuf >= cbChunk); + cbBuf -= cbChunk; + } + else + { + //AssertMsg(RTCircBufFree(pCircBuf), ("No more space but still %zu bytes to write\n", cbBuf)); + break; + } + + LogFunc(("[SD%RU8] cbChunk=%zu\n", pStream->u8SD, cbChunk)); + + RTCircBufReleaseWriteBlock(pCircBuf, cbChunk); + } +# endif /* HDA_USE_DMA_ACCESS_HANDLER_WRITING */ + break; + } + + default: + AssertMsgFailed(("Access type not implemented\n")); + break; + } + + return VINF_PGM_HANDLER_DO_DEFAULT; +} +# endif /* HDA_USE_DMA_ACCESS_HANDLER */ + +/** + * Soft reset of the device triggered via GCTL. + * + * @param pThis HDA state. + * + */ +static void hdaR3GCTLReset(PHDASTATE pThis) +{ + LogFlowFuncEnter(); + + pThis->cStreamsActive = 0; + + HDA_REG(pThis, GCAP) = HDA_MAKE_GCAP(HDA_MAX_SDO, HDA_MAX_SDI, 0, 0, 1); /* see 6.2.1 */ + HDA_REG(pThis, VMIN) = 0x00; /* see 6.2.2 */ + HDA_REG(pThis, VMAJ) = 0x01; /* see 6.2.3 */ + HDA_REG(pThis, OUTPAY) = 0x003C; /* see 6.2.4 */ + HDA_REG(pThis, INPAY) = 0x001D; /* see 6.2.5 */ + HDA_REG(pThis, CORBSIZE) = 0x42; /* Up to 256 CORB entries see 6.2.1 */ + HDA_REG(pThis, RIRBSIZE) = 0x42; /* Up to 256 RIRB entries see 6.2.1 */ + HDA_REG(pThis, CORBRP) = 0x0; + HDA_REG(pThis, CORBWP) = 0x0; + HDA_REG(pThis, RIRBWP) = 0x0; + /* Some guests (like Haiku) don't set RINTCNT explicitly but expect an interrupt after each + * RIRB response -- so initialize RINTCNT to 1 by default. */ + HDA_REG(pThis, RINTCNT) = 0x1; + + /* + * Stop any audio currently playing and/or recording. + */ + pThis->SinkFront.pStream = NULL; + if (pThis->SinkFront.pMixSink) + AudioMixerSinkReset(pThis->SinkFront.pMixSink); +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + pThis->SinkMicIn.pStream = NULL; + if (pThis->SinkMicIn.pMixSink) + AudioMixerSinkReset(pThis->SinkMicIn.pMixSink); +# endif + pThis->SinkLineIn.pStream = NULL; + if (pThis->SinkLineIn.pMixSink) + AudioMixerSinkReset(pThis->SinkLineIn.pMixSink); +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + pThis->SinkCenterLFE = NULL; + if (pThis->SinkCenterLFE.pMixSink) + AudioMixerSinkReset(pThis->SinkCenterLFE.pMixSink); + pThis->SinkRear.pStream = NULL; + if (pThis->SinkRear.pMixSink) + AudioMixerSinkReset(pThis->SinkRear.pMixSink); +# endif + + /* + * Reset the codec. + */ + if ( pThis->pCodec + && pThis->pCodec->pfnReset) + { + pThis->pCodec->pfnReset(pThis->pCodec); + } + + /* + * Set some sensible defaults for which HDA sinks + * are connected to which stream number. + * + * We use SD0 for input and SD4 for output by default. + * These stream numbers can be changed by the guest dynamically lateron. + */ +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_MIC_IN , 1 /* SD0 */, 0 /* Channel */); +# endif + hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_LINE_IN , 1 /* SD0 */, 0 /* Channel */); + + hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_FRONT , 5 /* SD4 */, 0 /* Channel */); +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_CENTER_LFE, 5 /* SD4 */, 0 /* Channel */); + hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_REAR , 5 /* SD4 */, 0 /* Channel */); +# endif + + /* Reset CORB. */ + pThis->cbCorbBuf = HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE; + RT_BZERO(pThis->pu32CorbBuf, pThis->cbCorbBuf); + + /* Reset RIRB. */ + pThis->cbRirbBuf = HDA_RIRB_SIZE * HDA_RIRB_ELEMENT_SIZE; + RT_BZERO(pThis->pu64RirbBuf, pThis->cbRirbBuf); + + /* Clear our internal response interrupt counter. */ + pThis->u16RespIntCnt = 0; + + for (uint8_t uSD = 0; uSD < HDA_MAX_STREAMS; ++uSD) + { + int rc2 = hdaR3StreamEnable(&pThis->aStreams[uSD], false /* fEnable */); + if (RT_SUCCESS(rc2)) + { + /* Remove the RUN bit from SDnCTL in case the stream was in a running state before. */ + HDA_STREAM_REG(pThis, CTL, uSD) &= ~HDA_SDCTL_RUN; + hdaR3StreamReset(pThis, &pThis->aStreams[uSD], uSD); + } + } + + /* Clear stream tags <-> objects mapping table. */ + RT_ZERO(pThis->aTags); + + /* Emulation of codec "wake up" (HDA spec 5.5.1 and 6.5). */ + HDA_REG(pThis, STATESTS) = 0x1; + + LogFlowFuncLeave(); + LogRel(("HDA: Reset\n")); +} + +#endif /* IN_RING3 */ + +/* MMIO callbacks */ + +/** + * @callback_method_impl{FNIOMMMIOREAD, Looks up and calls the appropriate handler.} + * + * @note During implementation, we discovered so-called "forgotten" or "hole" + * registers whose description is not listed in the RPM, datasheet, or + * spec. + */ +PDMBOTHCBDECL(int) hdaMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + int rc; + RT_NOREF_PV(pvUser); + Assert(pThis->uAlignmentCheckMagic == HDASTATE_ALIGNMENT_CHECK_MAGIC); + + /* + * Look up and log. + */ + uint32_t offReg = GCPhysAddr - pThis->MMIOBaseAddr; + int idxRegDsc = hdaRegLookup(offReg); /* Register descriptor index. */ +#ifdef LOG_ENABLED + unsigned const cbLog = cb; + uint32_t offRegLog = offReg; +#endif + + Log3Func(("offReg=%#x cb=%#x\n", offReg, cb)); + Assert(cb == 4); Assert((offReg & 3) == 0); + + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_READ); + + if (!(HDA_REG(pThis, GCTL) & HDA_GCTL_CRST) && idxRegDsc != HDA_REG_GCTL) + LogFunc(("Access to registers except GCTL is blocked while reset\n")); + + if (idxRegDsc == -1) + LogRel(("HDA: Invalid read access @0x%x (bytes=%u)\n", offReg, cb)); + + if (idxRegDsc != -1) + { + /* Leave lock before calling read function. */ + DEVHDA_UNLOCK(pThis); + + /* ASSUMES gapless DWORD at end of map. */ + if (g_aHdaRegMap[idxRegDsc].size == 4) + { + /* + * Straight forward DWORD access. + */ + rc = g_aHdaRegMap[idxRegDsc].pfnRead(pThis, idxRegDsc, (uint32_t *)pv); + Log3Func(("\tRead %s => %x (%Rrc)\n", g_aHdaRegMap[idxRegDsc].abbrev, *(uint32_t *)pv, rc)); + } + else + { + /* + * Multi register read (unless there are trailing gaps). + * ASSUMES that only DWORD reads have sideeffects. + */ +#ifdef IN_RING3 + uint32_t u32Value = 0; + unsigned cbLeft = 4; + do + { + uint32_t const cbReg = g_aHdaRegMap[idxRegDsc].size; + uint32_t u32Tmp = 0; + + rc = g_aHdaRegMap[idxRegDsc].pfnRead(pThis, idxRegDsc, &u32Tmp); + Log3Func(("\tRead %s[%db] => %x (%Rrc)*\n", g_aHdaRegMap[idxRegDsc].abbrev, cbReg, u32Tmp, rc)); + if (rc != VINF_SUCCESS) + break; + u32Value |= (u32Tmp & g_afMasks[cbReg]) << ((4 - cbLeft) * 8); + + cbLeft -= cbReg; + offReg += cbReg; + idxRegDsc++; + } while (cbLeft > 0 && g_aHdaRegMap[idxRegDsc].offset == offReg); + + if (rc == VINF_SUCCESS) + *(uint32_t *)pv = u32Value; + else + Assert(!IOM_SUCCESS(rc)); +#else /* !IN_RING3 */ + /* Take the easy way out. */ + rc = VINF_IOM_R3_MMIO_READ; +#endif /* !IN_RING3 */ + } + } + else + { + DEVHDA_UNLOCK(pThis); + + rc = VINF_IOM_MMIO_UNUSED_FF; + Log3Func(("\tHole at %x is accessed for read\n", offReg)); + } + + /* + * Log the outcome. + */ +#ifdef LOG_ENABLED + if (cbLog == 4) + Log3Func(("\tReturning @%#05x -> %#010x %Rrc\n", offRegLog, *(uint32_t *)pv, rc)); + else if (cbLog == 2) + Log3Func(("\tReturning @%#05x -> %#06x %Rrc\n", offRegLog, *(uint16_t *)pv, rc)); + else if (cbLog == 1) + Log3Func(("\tReturning @%#05x -> %#04x %Rrc\n", offRegLog, *(uint8_t *)pv, rc)); +#endif + return rc; +} + + +DECLINLINE(int) hdaWriteReg(PHDASTATE pThis, int idxRegDsc, uint32_t u32Value, char const *pszLog) +{ + DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE); + + if (!(HDA_REG(pThis, GCTL) & HDA_GCTL_CRST) && idxRegDsc != HDA_REG_GCTL) + { + Log(("hdaWriteReg: Warning: Access to %s is blocked while controller is in reset mode\n", g_aHdaRegMap[idxRegDsc].abbrev)); + LogRel2(("HDA: Warning: Access to register %s is blocked while controller is in reset mode\n", + g_aHdaRegMap[idxRegDsc].abbrev)); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; + } + + /* + * Handle RD (register description) flags. + */ + + /* For SDI / SDO: Check if writes to those registers are allowed while SDCTL's RUN bit is set. */ + if (idxRegDsc >= HDA_NUM_GENERAL_REGS) + { + const uint32_t uSDCTL = HDA_STREAM_REG(pThis, CTL, HDA_SD_NUM_FROM_REG(pThis, CTL, idxRegDsc)); + + /* + * Some OSes (like Win 10 AU) violate the spec by writing stuff to registers which are not supposed to be be touched + * while SDCTL's RUN bit is set. So just ignore those values. + */ + + /* Is the RUN bit currently set? */ + if ( RT_BOOL(uSDCTL & HDA_SDCTL_RUN) + /* Are writes to the register denied if RUN bit is set? */ + && !(g_aHdaRegMap[idxRegDsc].fFlags & HDA_RD_FLAG_SD_WRITE_RUN)) + { + Log(("hdaWriteReg: Warning: Access to %s is blocked! %R[sdctl]\n", g_aHdaRegMap[idxRegDsc].abbrev, uSDCTL)); + LogRel2(("HDA: Warning: Access to register %s is blocked while the stream's RUN bit is set\n", + g_aHdaRegMap[idxRegDsc].abbrev)); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; + } + } + + /* Leave the lock before calling write function. */ + /** @todo r=bird: Why do we need to do that?? There is no + * explanation why this is necessary here... + * + * More or less all write functions retake the lock, so why not let + * those who need to drop the lock or take additional locks release + * it? See, releasing a lock you already got always runs the risk + * of someone else grabbing it and forcing you to wait, better to + * do the two-three things a write handle needs to do than enter + * and exit the lock all the time. */ + DEVHDA_UNLOCK(pThis); + +#ifdef LOG_ENABLED + uint32_t const idxRegMem = g_aHdaRegMap[idxRegDsc].mem_idx; + uint32_t const u32OldValue = pThis->au32Regs[idxRegMem]; +#endif + int rc = g_aHdaRegMap[idxRegDsc].pfnWrite(pThis, idxRegDsc, u32Value); + Log3Func(("Written value %#x to %s[%d byte]; %x => %x%s, rc=%d\n", u32Value, g_aHdaRegMap[idxRegDsc].abbrev, + g_aHdaRegMap[idxRegDsc].size, u32OldValue, pThis->au32Regs[idxRegMem], pszLog, rc)); + RT_NOREF(pszLog); + return rc; +} + + +/** + * @callback_method_impl{FNIOMMMIOWRITE, Looks up and calls the appropriate handler.} + */ +PDMBOTHCBDECL(int) hdaMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void const *pv, unsigned cb) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + int rc; + RT_NOREF_PV(pvUser); + Assert(pThis->uAlignmentCheckMagic == HDASTATE_ALIGNMENT_CHECK_MAGIC); + + /* + * The behavior of accesses that aren't aligned on natural boundraries is + * undefined. Just reject them outright. + */ + /** @todo IOM could check this, it could also split the 8 byte accesses for us. */ + Assert(cb == 1 || cb == 2 || cb == 4 || cb == 8); + if (GCPhysAddr & (cb - 1)) + return PDMDevHlpDBGFStop(pDevIns, RT_SRC_POS, "misaligned write access: GCPhysAddr=%RGp cb=%u\n", GCPhysAddr, cb); + + /* + * Look up and log the access. + */ + uint32_t offReg = GCPhysAddr - pThis->MMIOBaseAddr; + int idxRegDsc = hdaRegLookup(offReg); +#if defined(IN_RING3) || defined(LOG_ENABLED) + uint32_t idxRegMem = idxRegDsc != -1 ? g_aHdaRegMap[idxRegDsc].mem_idx : UINT32_MAX; +#endif + uint64_t u64Value; + if (cb == 4) u64Value = *(uint32_t const *)pv; + else if (cb == 2) u64Value = *(uint16_t const *)pv; + else if (cb == 1) u64Value = *(uint8_t const *)pv; + else if (cb == 8) u64Value = *(uint64_t const *)pv; + else + { + u64Value = 0; /* shut up gcc. */ + AssertReleaseMsgFailed(("%u\n", cb)); + } + +#ifdef LOG_ENABLED + uint32_t const u32LogOldValue = idxRegDsc >= 0 ? pThis->au32Regs[idxRegMem] : UINT32_MAX; + if (idxRegDsc == -1) + Log3Func(("@%#05x u32=%#010x cb=%d\n", offReg, *(uint32_t const *)pv, cb)); + else if (cb == 4) + Log3Func(("@%#05x u32=%#010x %s\n", offReg, *(uint32_t *)pv, g_aHdaRegMap[idxRegDsc].abbrev)); + else if (cb == 2) + Log3Func(("@%#05x u16=%#06x (%#010x) %s\n", offReg, *(uint16_t *)pv, *(uint32_t *)pv, g_aHdaRegMap[idxRegDsc].abbrev)); + else if (cb == 1) + Log3Func(("@%#05x u8=%#04x (%#010x) %s\n", offReg, *(uint8_t *)pv, *(uint32_t *)pv, g_aHdaRegMap[idxRegDsc].abbrev)); + + if (idxRegDsc >= 0 && g_aHdaRegMap[idxRegDsc].size != cb) + Log3Func(("\tsize=%RU32 != cb=%u!!\n", g_aHdaRegMap[idxRegDsc].size, cb)); +#endif + + /* + * Try for a direct hit first. + */ + if (idxRegDsc != -1 && g_aHdaRegMap[idxRegDsc].size == cb) + { + rc = hdaWriteReg(pThis, idxRegDsc, u64Value, ""); + Log3Func(("\t%#x -> %#x\n", u32LogOldValue, idxRegMem != UINT32_MAX ? pThis->au32Regs[idxRegMem] : UINT32_MAX)); + } + /* + * Partial or multiple register access, loop thru the requested memory. + */ + else + { +#ifdef IN_RING3 + /* + * If it's an access beyond the start of the register, shift the input + * value and fill in missing bits. Natural alignment rules means we + * will only see 1 or 2 byte accesses of this kind, so no risk of + * shifting out input values. + */ + if (idxRegDsc == -1 && (idxRegDsc = hdaR3RegLookupWithin(offReg)) != -1) + { + uint32_t const cbBefore = offReg - g_aHdaRegMap[idxRegDsc].offset; Assert(cbBefore > 0 && cbBefore < 4); + offReg -= cbBefore; + idxRegMem = g_aHdaRegMap[idxRegDsc].mem_idx; + u64Value <<= cbBefore * 8; + u64Value |= pThis->au32Regs[idxRegMem] & g_afMasks[cbBefore]; + Log3Func(("\tWithin register, supplied %u leading bits: %#llx -> %#llx ...\n", + cbBefore * 8, ~g_afMasks[cbBefore] & u64Value, u64Value)); + } + + /* Loop thru the write area, it may cover multiple registers. */ + rc = VINF_SUCCESS; + for (;;) + { + uint32_t cbReg; + if (idxRegDsc != -1) + { + idxRegMem = g_aHdaRegMap[idxRegDsc].mem_idx; + cbReg = g_aHdaRegMap[idxRegDsc].size; + if (cb < cbReg) + { + u64Value |= pThis->au32Regs[idxRegMem] & g_afMasks[cbReg] & ~g_afMasks[cb]; + Log3Func(("\tSupplying missing bits (%#x): %#llx -> %#llx ...\n", + g_afMasks[cbReg] & ~g_afMasks[cb], u64Value & g_afMasks[cb], u64Value)); + } +# ifdef LOG_ENABLED + uint32_t uLogOldVal = pThis->au32Regs[idxRegMem]; +# endif + rc = hdaWriteReg(pThis, idxRegDsc, u64Value, "*"); + Log3Func(("\t%#x -> %#x\n", uLogOldVal, pThis->au32Regs[idxRegMem])); + } + else + { + LogRel(("HDA: Invalid write access @0x%x\n", offReg)); + cbReg = 1; + } + if (rc != VINF_SUCCESS) + break; + if (cbReg >= cb) + break; + + /* Advance. */ + offReg += cbReg; + cb -= cbReg; + u64Value >>= cbReg * 8; + if (idxRegDsc == -1) + idxRegDsc = hdaRegLookup(offReg); + else + { + idxRegDsc++; + if ( (unsigned)idxRegDsc >= RT_ELEMENTS(g_aHdaRegMap) + || g_aHdaRegMap[idxRegDsc].offset != offReg) + { + idxRegDsc = -1; + } + } + } + +#else /* !IN_RING3 */ + /* Take the simple way out. */ + rc = VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ + } + + return rc; +} + + +/* PCI callback. */ + +#ifdef IN_RING3 +/** + * @callback_method_impl{FNPCIIOREGIONMAP} + */ +static DECLCALLBACK(int) hdaR3PciIoRegionMap(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion, + RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType) +{ + RT_NOREF(iRegion, enmType); + PHDASTATE pThis = RT_FROM_MEMBER(pPciDev, HDASTATE, PciDev); + + /* + * 18.2 of the ICH6 datasheet defines the valid access widths as byte, word, and double word. + * + * Let IOM talk DWORDs when reading, saves a lot of complications. On + * writing though, we have to do it all ourselves because of sideeffects. + */ + Assert(enmType == PCI_ADDRESS_SPACE_MEM); + int rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL /*pvUser*/, + IOMMMIO_FLAGS_READ_DWORD + | IOMMMIO_FLAGS_WRITE_PASSTHRU, + hdaMMIOWrite, hdaMMIORead, "HDA"); + + if (RT_FAILURE(rc)) + return rc; + + if (pThis->fRZEnabled) + { + rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, NIL_RTR0PTR /*pvUser*/, + "hdaMMIOWrite", "hdaMMIORead"); + if (RT_FAILURE(rc)) + return rc; + + rc = PDMDevHlpMMIORegisterRC(pDevIns, GCPhysAddress, cb, NIL_RTRCPTR /*pvUser*/, + "hdaMMIOWrite", "hdaMMIORead"); + if (RT_FAILURE(rc)) + return rc; + } + + pThis->MMIOBaseAddr = GCPhysAddress; + return VINF_SUCCESS; +} + + +/* Saved state workers and callbacks. */ + +static int hdaR3SaveStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PHDASTREAM pStream) +{ + RT_NOREF(pDevIns); +#if defined(LOG_ENABLED) + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); +#endif + + Log2Func(("[SD%RU8]\n", pStream->u8SD)); + + /* Save stream ID. */ + int rc = SSMR3PutU8(pSSM, pStream->u8SD); + AssertRCReturn(rc, rc); + Assert(pStream->u8SD < HDA_MAX_STREAMS); + + rc = SSMR3PutStructEx(pSSM, &pStream->State, sizeof(HDASTREAMSTATE), 0 /*fFlags*/, g_aSSMStreamStateFields7, NULL); + AssertRCReturn(rc, rc); + + rc = SSMR3PutStructEx(pSSM, &pStream->State.BDLE.Desc, sizeof(HDABDLEDESC), + 0 /*fFlags*/, g_aSSMBDLEDescFields7, NULL); + AssertRCReturn(rc, rc); + + rc = SSMR3PutStructEx(pSSM, &pStream->State.BDLE.State, sizeof(HDABDLESTATE), + 0 /*fFlags*/, g_aSSMBDLEStateFields7, NULL); + AssertRCReturn(rc, rc); + + rc = SSMR3PutStructEx(pSSM, &pStream->State.Period, sizeof(HDASTREAMPERIOD), + 0 /* fFlags */, g_aSSMStreamPeriodFields7, NULL); + AssertRCReturn(rc, rc); + + uint32_t cbCircBufSize = 0; + uint32_t cbCircBufUsed = 0; + + if (pStream->State.pCircBuf) + { + cbCircBufSize = (uint32_t)RTCircBufSize(pStream->State.pCircBuf); + cbCircBufUsed = (uint32_t)RTCircBufUsed(pStream->State.pCircBuf); + } + + rc = SSMR3PutU32(pSSM, cbCircBufSize); + AssertRCReturn(rc, rc); + + rc = SSMR3PutU32(pSSM, cbCircBufUsed); + AssertRCReturn(rc, rc); + + if (cbCircBufUsed) + { + /* + * We now need to get the circular buffer's data without actually modifying + * the internal read / used offsets -- otherwise we would end up with broken audio + * data after saving the state. + * + * So get the current read offset and serialize the buffer data manually based on that. + */ + size_t const offBuf = RTCircBufOffsetRead(pStream->State.pCircBuf); + void *pvBuf; + size_t cbBuf; + RTCircBufAcquireReadBlock(pStream->State.pCircBuf, cbCircBufUsed, &pvBuf, &cbBuf); + Assert(cbBuf); + if (cbBuf) + { + rc = SSMR3PutMem(pSSM, pvBuf, cbBuf); + AssertRC(rc); + if ( RT_SUCCESS(rc) + && cbBuf < cbCircBufUsed) + { + rc = SSMR3PutMem(pSSM, (uint8_t *)pvBuf - offBuf, cbCircBufUsed - cbBuf); + } + } + RTCircBufReleaseReadBlock(pStream->State.pCircBuf, 0 /* Don't advance read pointer -- see comment above */); + } + + Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", + pStream->u8SD, + HDA_STREAM_REG(pThis, LPIB, pStream->u8SD), HDA_STREAM_REG(pThis, CBL, pStream->u8SD), HDA_STREAM_REG(pThis, LVI, pStream->u8SD))); + +#ifdef LOG_ENABLED + hdaR3BDLEDumpAll(pThis, pStream->u64BDLBase, pStream->u16LVI + 1); +#endif + + return rc; +} + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) hdaR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + + /* Save Codec nodes states. */ + hdaCodecSaveState(pThis->pCodec, pSSM); + + /* Save MMIO registers. */ + SSMR3PutU32(pSSM, RT_ELEMENTS(pThis->au32Regs)); + SSMR3PutMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs)); + + /* Save controller-specifc internals. */ + SSMR3PutU64(pSSM, pThis->u64WalClk); + SSMR3PutU8(pSSM, pThis->u8IRQL); + + /* Save number of streams. */ + SSMR3PutU32(pSSM, HDA_MAX_STREAMS); + + /* Save stream states. */ + for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++) + { + int rc = hdaR3SaveStream(pDevIns, pSSM, &pThis->aStreams[i]); + AssertRCReturn(rc, rc); + } + + return VINF_SUCCESS; +} + +/** + * Does required post processing when loading a saved state. + * + * @param pThis Pointer to HDA state. + */ +static int hdaR3LoadExecPost(PHDASTATE pThis) +{ + int rc = VINF_SUCCESS; + + /* + * Enable all previously active streams. + */ + for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++) + { + PHDASTREAM pStream = hdaGetStreamFromSD(pThis, i); + if (pStream) + { + int rc2; + + bool fActive = RT_BOOL(HDA_STREAM_REG(pThis, CTL, i) & HDA_SDCTL_RUN); + if (fActive) + { +#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + /* Make sure to also create the async I/O thread before actually enabling the stream. */ + rc2 = hdaR3StreamAsyncIOCreate(pStream); + AssertRC(rc2); + + /* ... and enabling it. */ + hdaR3StreamAsyncIOEnable(pStream, true /* fEnable */); +#endif + /* Resume the stream's period. */ + hdaR3StreamPeriodResume(&pStream->State.Period); + + /* (Re-)enable the stream. */ + rc2 = hdaR3StreamEnable(pStream, true /* fEnable */); + AssertRC(rc2); + + /* Add the stream to the device setup. */ + rc2 = hdaR3AddStream(pThis, &pStream->State.Cfg); + AssertRC(rc2); + +#ifdef HDA_USE_DMA_ACCESS_HANDLER + /* (Re-)install the DMA handler. */ + hdaR3StreamRegisterDMAHandlers(pThis, pStream); +#endif + if (hdaR3StreamTransferIsScheduled(pStream)) + hdaR3TimerSet(pThis, pStream, hdaR3StreamTransferGetNext(pStream), true /* fForce */); + + /* Also keep track of the currently active streams. */ + pThis->cStreamsActive++; + } + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Handles loading of all saved state versions older than the current one. + * + * @param pThis Pointer to HDA state. + * @param pSSM Pointer to SSM handle. + * @param uVersion Saved state version to load. + * @param uPass Loading stage to handle. + */ +static int hdaR3LoadExecLegacy(PHDASTATE pThis, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + RT_NOREF(uPass); + + int rc = VINF_SUCCESS; + + /* + * Load MMIO registers. + */ + uint32_t cRegs; + switch (uVersion) + { + case HDA_SSM_VERSION_1: + /* Starting with r71199, we would save 112 instead of 113 + registers due to some code cleanups. This only affected trunk + builds in the 4.1 development period. */ + cRegs = 113; + if (SSMR3HandleRevision(pSSM) >= 71199) + { + uint32_t uVer = SSMR3HandleVersion(pSSM); + if ( VBOX_FULL_VERSION_GET_MAJOR(uVer) == 4 + && VBOX_FULL_VERSION_GET_MINOR(uVer) == 0 + && VBOX_FULL_VERSION_GET_BUILD(uVer) >= 51) + cRegs = 112; + } + break; + + case HDA_SSM_VERSION_2: + case HDA_SSM_VERSION_3: + cRegs = 112; + AssertCompile(RT_ELEMENTS(pThis->au32Regs) >= 112); + break; + + /* Since version 4 we store the register count to stay flexible. */ + case HDA_SSM_VERSION_4: + case HDA_SSM_VERSION_5: + case HDA_SSM_VERSION_6: + rc = SSMR3GetU32(pSSM, &cRegs); AssertRCReturn(rc, rc); + if (cRegs != RT_ELEMENTS(pThis->au32Regs)) + LogRel(("HDA: SSM version cRegs is %RU32, expected %RU32\n", cRegs, RT_ELEMENTS(pThis->au32Regs))); + break; + + default: + LogRel(("HDA: Warning: Unsupported / too new saved state version (%RU32)\n", uVersion)); + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + } + + if (cRegs >= RT_ELEMENTS(pThis->au32Regs)) + { + SSMR3GetMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs)); + SSMR3Skip(pSSM, sizeof(uint32_t) * (cRegs - RT_ELEMENTS(pThis->au32Regs))); + } + else + SSMR3GetMem(pSSM, pThis->au32Regs, sizeof(uint32_t) * cRegs); + + /* Make sure to update the base addresses first before initializing any streams down below. */ + pThis->u64CORBBase = RT_MAKE_U64(HDA_REG(pThis, CORBLBASE), HDA_REG(pThis, CORBUBASE)); + pThis->u64RIRBBase = RT_MAKE_U64(HDA_REG(pThis, RIRBLBASE), HDA_REG(pThis, RIRBUBASE)); + pThis->u64DPBase = RT_MAKE_U64(HDA_REG(pThis, DPLBASE) & DPBASE_ADDR_MASK, HDA_REG(pThis, DPUBASE)); + + /* Also make sure to update the DMA position bit if this was enabled when saving the state. */ + pThis->fDMAPosition = RT_BOOL(HDA_REG(pThis, DPLBASE) & RT_BIT_32(0)); + + /* + * Note: Saved states < v5 store LVI (u32BdleMaxCvi) for + * *every* BDLE state, whereas it only needs to be stored + * *once* for every stream. Most of the BDLE state we can + * get out of the registers anyway, so just ignore those values. + * + * Also, only the current BDLE was saved, regardless whether + * there were more than one (and there are at least two entries, + * according to the spec). + */ +#define HDA_SSM_LOAD_BDLE_STATE_PRE_V5(v, x) \ + { \ + rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* Begin marker */ \ + AssertRCReturn(rc, rc); \ + rc = SSMR3GetU64(pSSM, &x.Desc.u64BufAddr); /* u64BdleCviAddr */ \ + AssertRCReturn(rc, rc); \ + rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* u32BdleMaxCvi */ \ + AssertRCReturn(rc, rc); \ + rc = SSMR3GetU32(pSSM, &x.State.u32BDLIndex); /* u32BdleCvi */ \ + AssertRCReturn(rc, rc); \ + rc = SSMR3GetU32(pSSM, &x.Desc.u32BufSize); /* u32BdleCviLen */ \ + AssertRCReturn(rc, rc); \ + rc = SSMR3GetU32(pSSM, &x.State.u32BufOff); /* u32BdleCviPos */ \ + AssertRCReturn(rc, rc); \ + bool fIOC; \ + rc = SSMR3GetBool(pSSM, &fIOC); /* fBdleCviIoc */ \ + AssertRCReturn(rc, rc); \ + x.Desc.fFlags = fIOC ? HDA_BDLE_FLAG_IOC : 0; \ + rc = SSMR3GetU32(pSSM, &x.State.cbBelowFIFOW); /* cbUnderFifoW */ \ + AssertRCReturn(rc, rc); \ + rc = SSMR3Skip(pSSM, sizeof(uint8_t) * 256); /* FIFO */ \ + AssertRCReturn(rc, rc); \ + rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* End marker */ \ + AssertRCReturn(rc, rc); \ + } + + /* + * Load BDLEs (Buffer Descriptor List Entries) and DMA counters. + */ + switch (uVersion) + { + case HDA_SSM_VERSION_1: + case HDA_SSM_VERSION_2: + case HDA_SSM_VERSION_3: + case HDA_SSM_VERSION_4: + { + /* Only load the internal states. + * The rest will be initialized from the saved registers later. */ + + /* Note 1: Only the *current* BDLE for a stream was saved! */ + /* Note 2: The stream's saving order is/was fixed, so don't touch! */ + + /* Output */ + PHDASTREAM pStream = &pThis->aStreams[4]; + rc = hdaR3StreamInit(pStream, 4 /* Stream descriptor, hardcoded */); + if (RT_FAILURE(rc)) + break; + HDA_SSM_LOAD_BDLE_STATE_PRE_V5(uVersion, pStream->State.BDLE); + pStream->State.uCurBDLE = pStream->State.BDLE.State.u32BDLIndex; + + /* Microphone-In */ + pStream = &pThis->aStreams[2]; + rc = hdaR3StreamInit(pStream, 2 /* Stream descriptor, hardcoded */); + if (RT_FAILURE(rc)) + break; + HDA_SSM_LOAD_BDLE_STATE_PRE_V5(uVersion, pStream->State.BDLE); + pStream->State.uCurBDLE = pStream->State.BDLE.State.u32BDLIndex; + + /* Line-In */ + pStream = &pThis->aStreams[0]; + rc = hdaR3StreamInit(pStream, 0 /* Stream descriptor, hardcoded */); + if (RT_FAILURE(rc)) + break; + HDA_SSM_LOAD_BDLE_STATE_PRE_V5(uVersion, pStream->State.BDLE); + pStream->State.uCurBDLE = pStream->State.BDLE.State.u32BDLIndex; + break; + } + +#undef HDA_SSM_LOAD_BDLE_STATE_PRE_V5 + + default: /* Since v5 we support flexible stream and BDLE counts. */ + { + uint32_t cStreams; + rc = SSMR3GetU32(pSSM, &cStreams); + if (RT_FAILURE(rc)) + break; + + if (cStreams > HDA_MAX_STREAMS) + cStreams = HDA_MAX_STREAMS; /* Sanity. */ + + /* Load stream states. */ + for (uint32_t i = 0; i < cStreams; i++) + { + uint8_t uStreamID; + rc = SSMR3GetU8(pSSM, &uStreamID); + if (RT_FAILURE(rc)) + break; + + PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uStreamID); + HDASTREAM StreamDummy; + + if (!pStream) + { + pStream = &StreamDummy; + LogRel2(("HDA: Warning: Stream ID=%RU32 not supported, skipping to load ...\n", uStreamID)); + } + + rc = hdaR3StreamInit(pStream, uStreamID); + if (RT_FAILURE(rc)) + { + LogRel(("HDA: Stream #%RU32: Initialization of stream %RU8 failed, rc=%Rrc\n", i, uStreamID, rc)); + break; + } + + /* + * Load BDLEs (Buffer Descriptor List Entries) and DMA counters. + */ + + if (uVersion == HDA_SSM_VERSION_5) + { + /* Get the current BDLE entry and skip the rest. */ + uint16_t cBDLE; + + rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* Begin marker */ + AssertRC(rc); + rc = SSMR3GetU16(pSSM, &cBDLE); /* cBDLE */ + AssertRC(rc); + rc = SSMR3GetU16(pSSM, &pStream->State.uCurBDLE); /* uCurBDLE */ + AssertRC(rc); + rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* End marker */ + AssertRC(rc); + + uint32_t u32BDLEIndex; + for (uint16_t a = 0; a < cBDLE; a++) + { + rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* Begin marker */ + AssertRC(rc); + rc = SSMR3GetU32(pSSM, &u32BDLEIndex); /* u32BDLIndex */ + AssertRC(rc); + + /* Does the current BDLE index match the current BDLE to process? */ + if (u32BDLEIndex == pStream->State.uCurBDLE) + { + rc = SSMR3GetU32(pSSM, &pStream->State.BDLE.State.cbBelowFIFOW); /* cbBelowFIFOW */ + AssertRC(rc); + rc = SSMR3Skip(pSSM, sizeof(uint8_t) * 256); /* FIFO, deprecated */ + AssertRC(rc); + rc = SSMR3GetU32(pSSM, &pStream->State.BDLE.State.u32BufOff); /* u32BufOff */ + AssertRC(rc); + rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* End marker */ + AssertRC(rc); + } + else /* Skip not current BDLEs. */ + { + rc = SSMR3Skip(pSSM, sizeof(uint32_t) /* cbBelowFIFOW */ + + sizeof(uint8_t) * 256 /* au8FIFO */ + + sizeof(uint32_t) /* u32BufOff */ + + sizeof(uint32_t)); /* End marker */ + AssertRC(rc); + } + } + } + else + { + rc = SSMR3GetStructEx(pSSM, &pStream->State, sizeof(HDASTREAMSTATE), + 0 /* fFlags */, g_aSSMStreamStateFields6, NULL); + if (RT_FAILURE(rc)) + break; + + /* Get HDABDLEDESC. */ + uint32_t uMarker; + rc = SSMR3GetU32(pSSM, &uMarker); /* Begin marker. */ + AssertRC(rc); + Assert(uMarker == UINT32_C(0x19200102) /* SSMR3STRUCT_BEGIN */); + rc = SSMR3GetU64(pSSM, &pStream->State.BDLE.Desc.u64BufAddr); + AssertRC(rc); + rc = SSMR3GetU32(pSSM, &pStream->State.BDLE.Desc.u32BufSize); + AssertRC(rc); + bool fFlags = false; + rc = SSMR3GetBool(pSSM, &fFlags); /* Saved states < v7 only stored the IOC as boolean flag. */ + AssertRC(rc); + pStream->State.BDLE.Desc.fFlags = fFlags ? HDA_BDLE_FLAG_IOC : 0; + rc = SSMR3GetU32(pSSM, &uMarker); /* End marker. */ + AssertRC(rc); + Assert(uMarker == UINT32_C(0x19920406) /* SSMR3STRUCT_END */); + + rc = SSMR3GetStructEx(pSSM, &pStream->State.BDLE.State, sizeof(HDABDLESTATE), + 0 /* fFlags */, g_aSSMBDLEStateFields6, NULL); + if (RT_FAILURE(rc)) + break; + + Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", + uStreamID, + HDA_STREAM_REG(pThis, LPIB, uStreamID), HDA_STREAM_REG(pThis, CBL, uStreamID), HDA_STREAM_REG(pThis, LVI, uStreamID))); +#ifdef LOG_ENABLED + hdaR3BDLEDumpAll(pThis, pStream->u64BDLBase, pStream->u16LVI + 1); +#endif + } + + } /* for cStreams */ + break; + } /* default */ + } + + return rc; +} + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) hdaR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + + LogRel2(("hdaR3LoadExec: uVersion=%RU32, uPass=0x%x\n", uVersion, uPass)); + + /* + * Load Codec nodes states. + */ + int rc = hdaCodecLoadState(pThis->pCodec, pSSM, uVersion); + if (RT_FAILURE(rc)) + { + LogRel(("HDA: Failed loading codec state (version %RU32, pass 0x%x), rc=%Rrc\n", uVersion, uPass, rc)); + return rc; + } + + if (uVersion < HDA_SSM_VERSION) /* Handle older saved states? */ + { + rc = hdaR3LoadExecLegacy(pThis, pSSM, uVersion, uPass); + if (RT_SUCCESS(rc)) + rc = hdaR3LoadExecPost(pThis); + + return rc; + } + + /* + * Load MMIO registers. + */ + uint32_t cRegs; + rc = SSMR3GetU32(pSSM, &cRegs); AssertRCReturn(rc, rc); + if (cRegs != RT_ELEMENTS(pThis->au32Regs)) + LogRel(("HDA: SSM version cRegs is %RU32, expected %RU32\n", cRegs, RT_ELEMENTS(pThis->au32Regs))); + + if (cRegs >= RT_ELEMENTS(pThis->au32Regs)) + { + SSMR3GetMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs)); + SSMR3Skip(pSSM, sizeof(uint32_t) * (cRegs - RT_ELEMENTS(pThis->au32Regs))); + } + else + SSMR3GetMem(pSSM, pThis->au32Regs, sizeof(uint32_t) * cRegs); + + /* Make sure to update the base addresses first before initializing any streams down below. */ + pThis->u64CORBBase = RT_MAKE_U64(HDA_REG(pThis, CORBLBASE), HDA_REG(pThis, CORBUBASE)); + pThis->u64RIRBBase = RT_MAKE_U64(HDA_REG(pThis, RIRBLBASE), HDA_REG(pThis, RIRBUBASE)); + pThis->u64DPBase = RT_MAKE_U64(HDA_REG(pThis, DPLBASE) & DPBASE_ADDR_MASK, HDA_REG(pThis, DPUBASE)); + + /* Also make sure to update the DMA position bit if this was enabled when saving the state. */ + pThis->fDMAPosition = RT_BOOL(HDA_REG(pThis, DPLBASE) & RT_BIT_32(0)); + + /* + * Load controller-specifc internals. + * Don't annoy other team mates (forgot this for state v7). + */ + if ( SSMR3HandleRevision(pSSM) >= 116273 + || SSMR3HandleVersion(pSSM) >= VBOX_FULL_VERSION_MAKE(5, 2, 0)) + { + rc = SSMR3GetU64(pSSM, &pThis->u64WalClk); + AssertRC(rc); + + rc = SSMR3GetU8(pSSM, &pThis->u8IRQL); + AssertRC(rc); + } + + /* + * Load streams. + */ + uint32_t cStreams; + rc = SSMR3GetU32(pSSM, &cStreams); + AssertRC(rc); + + if (cStreams > HDA_MAX_STREAMS) + cStreams = HDA_MAX_STREAMS; /* Sanity. */ + + Log2Func(("cStreams=%RU32\n", cStreams)); + + /* Load stream states. */ + for (uint32_t i = 0; i < cStreams; i++) + { + uint8_t uStreamID; + rc = SSMR3GetU8(pSSM, &uStreamID); + AssertRC(rc); + + PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uStreamID); + HDASTREAM StreamDummy; + + if (!pStream) + { + pStream = &StreamDummy; + LogRel2(("HDA: Warning: Loading of stream #%RU8 not supported, skipping to load ...\n", uStreamID)); + } + + rc = hdaR3StreamInit(pStream, uStreamID); + if (RT_FAILURE(rc)) + { + LogRel(("HDA: Stream #%RU8: Loading initialization failed, rc=%Rrc\n", uStreamID, rc)); + /* Continue. */ + } + + rc = SSMR3GetStructEx(pSSM, &pStream->State, sizeof(HDASTREAMSTATE), + 0 /* fFlags */, g_aSSMStreamStateFields7, NULL); + AssertRC(rc); + + /* + * Load BDLEs (Buffer Descriptor List Entries) and DMA counters. + */ + rc = SSMR3GetStructEx(pSSM, &pStream->State.BDLE.Desc, sizeof(HDABDLEDESC), + 0 /* fFlags */, g_aSSMBDLEDescFields7, NULL); + AssertRC(rc); + + rc = SSMR3GetStructEx(pSSM, &pStream->State.BDLE.State, sizeof(HDABDLESTATE), + 0 /* fFlags */, g_aSSMBDLEStateFields7, NULL); + AssertRC(rc); + + Log2Func(("[SD%RU8] %R[bdle]\n", pStream->u8SD, &pStream->State.BDLE)); + + /* + * Load period state. + */ + hdaR3StreamPeriodInit(&pStream->State.Period, + pStream->u8SD, pStream->u16LVI, pStream->u32CBL, &pStream->State.Cfg); + + rc = SSMR3GetStructEx(pSSM, &pStream->State.Period, sizeof(HDASTREAMPERIOD), + 0 /* fFlags */, g_aSSMStreamPeriodFields7, NULL); + AssertRC(rc); + + /* + * Load internal (FIFO) buffer. + */ + uint32_t cbCircBufSize = 0; + rc = SSMR3GetU32(pSSM, &cbCircBufSize); /* cbCircBuf */ + AssertRC(rc); + + uint32_t cbCircBufUsed = 0; + rc = SSMR3GetU32(pSSM, &cbCircBufUsed); /* cbCircBuf */ + AssertRC(rc); + + if (cbCircBufSize) /* If 0, skip the buffer. */ + { + /* Paranoia. */ + AssertReleaseMsg(cbCircBufSize <= _1M, + ("HDA: Saved state contains bogus DMA buffer size (%RU32) for stream #%RU8", + cbCircBufSize, uStreamID)); + AssertReleaseMsg(cbCircBufUsed <= cbCircBufSize, + ("HDA: Saved state contains invalid DMA buffer usage (%RU32/%RU32) for stream #%RU8", + cbCircBufUsed, cbCircBufSize, uStreamID)); + + /* Do we need to cre-create the circular buffer do fit the data size? */ + if ( pStream->State.pCircBuf + && cbCircBufSize != (uint32_t)RTCircBufSize(pStream->State.pCircBuf)) + { + RTCircBufDestroy(pStream->State.pCircBuf); + pStream->State.pCircBuf = NULL; + } + + rc = RTCircBufCreate(&pStream->State.pCircBuf, cbCircBufSize); + AssertRC(rc); + + if ( RT_SUCCESS(rc) + && cbCircBufUsed) + { + void *pvBuf; + size_t cbBuf; + + RTCircBufAcquireWriteBlock(pStream->State.pCircBuf, cbCircBufUsed, &pvBuf, &cbBuf); + + if (cbBuf) + { + rc = SSMR3GetMem(pSSM, pvBuf, cbBuf); + AssertRC(rc); + } + + RTCircBufReleaseWriteBlock(pStream->State.pCircBuf, cbBuf); + + Assert(cbBuf == cbCircBufUsed); + } + } + + Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", + uStreamID, + HDA_STREAM_REG(pThis, LPIB, uStreamID), HDA_STREAM_REG(pThis, CBL, uStreamID), HDA_STREAM_REG(pThis, LVI, uStreamID))); +#ifdef LOG_ENABLED + hdaR3BDLEDumpAll(pThis, pStream->u64BDLBase, pStream->u16LVI + 1); +#endif + /** @todo (Re-)initialize active periods? */ + + } /* for cStreams */ + + rc = hdaR3LoadExecPost(pThis); + AssertRC(rc); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/* IPRT format type handlers. */ + +/** + * @callback_method_impl{FNRTSTRFORMATTYPE} + */ +static DECLCALLBACK(size_t) hdaR3StrFmtBDLE(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser) +{ + RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); + PHDABDLE pBDLE = (PHDABDLE)pvValue; + return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "BDLE(idx:%RU32, off:%RU32, fifow:%RU32, IOC:%RTbool, DMA[%RU32 bytes @ 0x%x])", + pBDLE->State.u32BDLIndex, pBDLE->State.u32BufOff, pBDLE->State.cbBelowFIFOW, + pBDLE->Desc.fFlags & HDA_BDLE_FLAG_IOC, pBDLE->Desc.u32BufSize, pBDLE->Desc.u64BufAddr); +} + +/** + * @callback_method_impl{FNRTSTRFORMATTYPE} + */ +static DECLCALLBACK(size_t) hdaR3StrFmtSDCTL(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser) +{ + RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); + uint32_t uSDCTL = (uint32_t)(uintptr_t)pvValue; + return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "SDCTL(raw:%#x, DIR:%s, TP:%RTbool, STRIPE:%x, DEIE:%RTbool, FEIE:%RTbool, IOCE:%RTbool, RUN:%RTbool, RESET:%RTbool)", + uSDCTL, + uSDCTL & HDA_SDCTL_DIR ? "OUT" : "IN", + RT_BOOL(uSDCTL & HDA_SDCTL_TP), + (uSDCTL & HDA_SDCTL_STRIPE_MASK) >> HDA_SDCTL_STRIPE_SHIFT, + RT_BOOL(uSDCTL & HDA_SDCTL_DEIE), + RT_BOOL(uSDCTL & HDA_SDCTL_FEIE), + RT_BOOL(uSDCTL & HDA_SDCTL_IOCE), + RT_BOOL(uSDCTL & HDA_SDCTL_RUN), + RT_BOOL(uSDCTL & HDA_SDCTL_SRST)); +} + +/** + * @callback_method_impl{FNRTSTRFORMATTYPE} + */ +static DECLCALLBACK(size_t) hdaR3StrFmtSDFIFOS(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser) +{ + RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); + uint32_t uSDFIFOS = (uint32_t)(uintptr_t)pvValue; + return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "SDFIFOS(raw:%#x, sdfifos:%RU8 B)", uSDFIFOS, uSDFIFOS ? uSDFIFOS + 1 : 0); +} + +/** + * @callback_method_impl{FNRTSTRFORMATTYPE} + */ +static DECLCALLBACK(size_t) hdaR3StrFmtSDFIFOW(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser) +{ + RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); + uint32_t uSDFIFOW = (uint32_t)(uintptr_t)pvValue; + return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "SDFIFOW(raw: %#0x, sdfifow:%d B)", uSDFIFOW, hdaSDFIFOWToBytes(uSDFIFOW)); +} + +/** + * @callback_method_impl{FNRTSTRFORMATTYPE} + */ +static DECLCALLBACK(size_t) hdaR3StrFmtSDSTS(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, + const char *pszType, void const *pvValue, + int cchWidth, int cchPrecision, unsigned fFlags, + void *pvUser) +{ + RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser); + uint32_t uSdSts = (uint32_t)(uintptr_t)pvValue; + return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, + "SDSTS(raw:%#0x, fifordy:%RTbool, dese:%RTbool, fifoe:%RTbool, bcis:%RTbool)", + uSdSts, + RT_BOOL(uSdSts & HDA_SDSTS_FIFORDY), + RT_BOOL(uSdSts & HDA_SDSTS_DESE), + RT_BOOL(uSdSts & HDA_SDSTS_FIFOE), + RT_BOOL(uSdSts & HDA_SDSTS_BCIS)); +} + +/* Debug info dumpers */ + +static int hdaR3DbgLookupRegByName(const char *pszArgs) +{ + int iReg = 0; + for (; iReg < HDA_NUM_REGS; ++iReg) + if (!RTStrICmp(g_aHdaRegMap[iReg].abbrev, pszArgs)) + return iReg; + return -1; +} + + +static void hdaR3DbgPrintRegister(PHDASTATE pThis, PCDBGFINFOHLP pHlp, int iHdaIndex) +{ + Assert( pThis + && iHdaIndex >= 0 + && iHdaIndex < HDA_NUM_REGS); + pHlp->pfnPrintf(pHlp, "%s: 0x%x\n", g_aHdaRegMap[iHdaIndex].abbrev, pThis->au32Regs[g_aHdaRegMap[iHdaIndex].mem_idx]); +} + +/** + * @callback_method_impl{FNDBGFHANDLERDEV} + */ +static DECLCALLBACK(void) hdaR3DbgInfo(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + int iHdaRegisterIndex = hdaR3DbgLookupRegByName(pszArgs); + if (iHdaRegisterIndex != -1) + hdaR3DbgPrintRegister(pThis, pHlp, iHdaRegisterIndex); + else + { + for(iHdaRegisterIndex = 0; (unsigned int)iHdaRegisterIndex < HDA_NUM_REGS; ++iHdaRegisterIndex) + hdaR3DbgPrintRegister(pThis, pHlp, iHdaRegisterIndex); + } +} + +static void hdaR3DbgPrintStream(PHDASTATE pThis, PCDBGFINFOHLP pHlp, int iIdx) +{ + Assert( pThis + && iIdx >= 0 + && iIdx < HDA_MAX_STREAMS); + + const PHDASTREAM pStream = &pThis->aStreams[iIdx]; + + pHlp->pfnPrintf(pHlp, "Stream #%d:\n", iIdx); + pHlp->pfnPrintf(pHlp, "\tSD%dCTL : %R[sdctl]\n", iIdx, HDA_STREAM_REG(pThis, CTL, iIdx)); + pHlp->pfnPrintf(pHlp, "\tSD%dCTS : %R[sdsts]\n", iIdx, HDA_STREAM_REG(pThis, STS, iIdx)); + pHlp->pfnPrintf(pHlp, "\tSD%dFIFOS: %R[sdfifos]\n", iIdx, HDA_STREAM_REG(pThis, FIFOS, iIdx)); + pHlp->pfnPrintf(pHlp, "\tSD%dFIFOW: %R[sdfifow]\n", iIdx, HDA_STREAM_REG(pThis, FIFOW, iIdx)); + pHlp->pfnPrintf(pHlp, "\tBDLE : %R[bdle]\n", &pStream->State.BDLE); +} + +static void hdaR3DbgPrintBDLE(PHDASTATE pThis, PCDBGFINFOHLP pHlp, int iIdx) +{ + Assert( pThis + && iIdx >= 0 + && iIdx < HDA_MAX_STREAMS); + + const PHDASTREAM pStream = &pThis->aStreams[iIdx]; + const PHDABDLE pBDLE = &pStream->State.BDLE; + + pHlp->pfnPrintf(pHlp, "Stream #%d BDLE:\n", iIdx); + + uint64_t u64BaseDMA = RT_MAKE_U64(HDA_STREAM_REG(pThis, BDPL, iIdx), + HDA_STREAM_REG(pThis, BDPU, iIdx)); + uint16_t u16LVI = HDA_STREAM_REG(pThis, LVI, iIdx); + uint32_t u32CBL = HDA_STREAM_REG(pThis, CBL, iIdx); + + if (!u64BaseDMA) + return; + + pHlp->pfnPrintf(pHlp, "\tCurrent: %R[bdle]\n\n", pBDLE); + + pHlp->pfnPrintf(pHlp, "\tMemory:\n"); + + uint32_t cbBDLE = 0; + for (uint16_t i = 0; i < u16LVI + 1; i++) + { + HDABDLEDESC bd; + PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), u64BaseDMA + i * sizeof(HDABDLEDESC), &bd, sizeof(bd)); + + pHlp->pfnPrintf(pHlp, "\t\t%s #%03d BDLE(adr:0x%llx, size:%RU32, ioc:%RTbool)\n", + pBDLE->State.u32BDLIndex == i ? "*" : " ", i, bd.u64BufAddr, bd.u32BufSize, bd.fFlags & HDA_BDLE_FLAG_IOC); + + cbBDLE += bd.u32BufSize; + } + + pHlp->pfnPrintf(pHlp, "Total: %RU32 bytes\n", cbBDLE); + + if (cbBDLE != u32CBL) + pHlp->pfnPrintf(pHlp, "Warning: %RU32 bytes does not match CBL (%RU32)!\n", cbBDLE, u32CBL); + + pHlp->pfnPrintf(pHlp, "DMA counters (base @ 0x%llx):\n", u64BaseDMA); + if (!u64BaseDMA) /* No DMA base given? Bail out. */ + { + pHlp->pfnPrintf(pHlp, "\tNo counters found\n"); + return; + } + + for (int i = 0; i < u16LVI + 1; i++) + { + uint32_t uDMACnt; + PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), (pThis->u64DPBase & DPBASE_ADDR_MASK) + (i * 2 * sizeof(uint32_t)), + &uDMACnt, sizeof(uDMACnt)); + + pHlp->pfnPrintf(pHlp, "\t#%03d DMA @ 0x%x\n", i , uDMACnt); + } +} + +static int hdaR3DbgLookupStrmIdx(PHDASTATE pThis, const char *pszArgs) +{ + RT_NOREF(pThis, pszArgs); + /** @todo Add args parsing. */ + return -1; +} + +/** + * @callback_method_impl{FNDBGFHANDLERDEV} + */ +static DECLCALLBACK(void) hdaR3DbgInfoStream(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + int iHdaStreamdex = hdaR3DbgLookupStrmIdx(pThis, pszArgs); + if (iHdaStreamdex != -1) + hdaR3DbgPrintStream(pThis, pHlp, iHdaStreamdex); + else + for(iHdaStreamdex = 0; iHdaStreamdex < HDA_MAX_STREAMS; ++iHdaStreamdex) + hdaR3DbgPrintStream(pThis, pHlp, iHdaStreamdex); +} + +/** + * @callback_method_impl{FNDBGFHANDLERDEV} + */ +static DECLCALLBACK(void) hdaR3DbgInfoBDLE(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + int iHdaStreamdex = hdaR3DbgLookupStrmIdx(pThis, pszArgs); + if (iHdaStreamdex != -1) + hdaR3DbgPrintBDLE(pThis, pHlp, iHdaStreamdex); + else + for (iHdaStreamdex = 0; iHdaStreamdex < HDA_MAX_STREAMS; ++iHdaStreamdex) + hdaR3DbgPrintBDLE(pThis, pHlp, iHdaStreamdex); +} + +/** + * @callback_method_impl{FNDBGFHANDLERDEV} + */ +static DECLCALLBACK(void) hdaR3DbgInfoCodecNodes(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + + if (pThis->pCodec->pfnDbgListNodes) + pThis->pCodec->pfnDbgListNodes(pThis->pCodec, pHlp, pszArgs); + else + pHlp->pfnPrintf(pHlp, "Codec implementation doesn't provide corresponding callback\n"); +} + +/** + * @callback_method_impl{FNDBGFHANDLERDEV} + */ +static DECLCALLBACK(void) hdaR3DbgInfoCodecSelector(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + + if (pThis->pCodec->pfnDbgSelector) + pThis->pCodec->pfnDbgSelector(pThis->pCodec, pHlp, pszArgs); + else + pHlp->pfnPrintf(pHlp, "Codec implementation doesn't provide corresponding callback\n"); +} + +/** + * @callback_method_impl{FNDBGFHANDLERDEV} + */ +static DECLCALLBACK(void) hdaR3DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + + if (pThis->pMixer) + AudioMixerDebug(pThis->pMixer, pHlp, pszArgs); + else + pHlp->pfnPrintf(pHlp, "Mixer not available\n"); +} + + +/* PDMIBASE */ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) hdaR3QueryInterface(struct PDMIBASE *pInterface, const char *pszIID) +{ + PHDASTATE pThis = RT_FROM_MEMBER(pInterface, HDASTATE, IBase); + Assert(&pThis->IBase == pInterface); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase); + return NULL; +} + + +/* PDMDEVREG */ + +/** + * Attach command, internal version. + * + * This is called to let the device attach to a driver for a specified LUN + * during runtime. This is not called during VM construction, the device + * constructor has to attach to all the available drivers. + * + * @returns VBox status code. + * @param pThis HDA state. + * @param uLUN The logical unit which is being detached. + * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines. + * @param ppDrv Attached driver instance on success. Optional. + */ +static int hdaR3AttachInternal(PHDASTATE pThis, unsigned uLUN, uint32_t fFlags, PHDADRIVER *ppDrv) +{ + RT_NOREF(fFlags); + + /* + * Attach driver. + */ + char *pszDesc; + if (RTStrAPrintf(&pszDesc, "Audio driver port (HDA) for LUN#%u", uLUN) <= 0) + AssertLogRelFailedReturn(VERR_NO_MEMORY); + + PPDMIBASE pDrvBase; + int rc = PDMDevHlpDriverAttach(pThis->pDevInsR3, uLUN, + &pThis->IBase, &pDrvBase, pszDesc); + if (RT_SUCCESS(rc)) + { + PHDADRIVER pDrv = (PHDADRIVER)RTMemAllocZ(sizeof(HDADRIVER)); + if (pDrv) + { + pDrv->pDrvBase = pDrvBase; + pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR); + AssertMsg(pDrv->pConnector != NULL, ("Configuration error: LUN#%u has no host audio interface, rc=%Rrc\n", uLUN, rc)); + pDrv->pHDAState = pThis; + pDrv->uLUN = uLUN; + + /* + * For now we always set the driver at LUN 0 as our primary + * host backend. This might change in the future. + */ + if (pDrv->uLUN == 0) + pDrv->fFlags |= PDMAUDIODRVFLAGS_PRIMARY; + + LogFunc(("LUN#%u: pCon=%p, drvFlags=0x%x\n", uLUN, pDrv->pConnector, pDrv->fFlags)); + + /* Attach to driver list if not attached yet. */ + if (!pDrv->fAttached) + { + RTListAppend(&pThis->lstDrv, &pDrv->Node); + pDrv->fAttached = true; + } + + if (ppDrv) + *ppDrv = pDrv; + } + else + rc = VERR_NO_MEMORY; + } + else if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + LogFunc(("No attached driver for LUN #%u\n", uLUN)); + + if (RT_FAILURE(rc)) + { + /* Only free this string on failure; + * must remain valid for the live of the driver instance. */ + RTStrFree(pszDesc); + } + + LogFunc(("uLUN=%u, fFlags=0x%x, rc=%Rrc\n", uLUN, fFlags, rc)); + return rc; +} + +/** + * Detach command, internal version. + * + * This is called to let the device detach from a driver for a specified LUN + * during runtime. + * + * @returns VBox status code. + * @param pThis HDA state. + * @param pDrv Driver to detach from device. + * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines. + */ +static int hdaR3DetachInternal(PHDASTATE pThis, PHDADRIVER pDrv, uint32_t fFlags) +{ + RT_NOREF(fFlags); + + /* First, remove the driver from our list and destory it's associated streams. + * This also will un-set the driver as a recording source (if associated). */ + hdaR3MixerRemoveDrv(pThis, pDrv); + + /* Next, search backwards for a capable (attached) driver which now will be the + * new recording source. */ + PHDADRIVER pDrvCur; + RTListForEachReverse(&pThis->lstDrv, pDrvCur, HDADRIVER, Node) + { + if (!pDrvCur->pConnector) + continue; + + PDMAUDIOBACKENDCFG Cfg; + int rc2 = pDrvCur->pConnector->pfnGetConfig(pDrvCur->pConnector, &Cfg); + if (RT_FAILURE(rc2)) + continue; + + PHDADRIVERSTREAM pDrvStrm; +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + pDrvStrm = &pDrvCur->MicIn; + if ( pDrvStrm + && pDrvStrm->pMixStrm) + { + rc2 = AudioMixerSinkSetRecordingSource(pThis->SinkMicIn.pMixSink, pDrvStrm->pMixStrm); + if (RT_SUCCESS(rc2)) + LogRel2(("HDA: Set new recording source for 'Mic In' to '%s'\n", Cfg.szName)); + } +# endif + pDrvStrm = &pDrvCur->LineIn; + if ( pDrvStrm + && pDrvStrm->pMixStrm) + { + rc2 = AudioMixerSinkSetRecordingSource(pThis->SinkLineIn.pMixSink, pDrvStrm->pMixStrm); + if (RT_SUCCESS(rc2)) + LogRel2(("HDA: Set new recording source for 'Line In' to '%s'\n", Cfg.szName)); + } + } + + LogFunc(("uLUN=%u, fFlags=0x%x\n", pDrv->uLUN, fFlags)); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnAttach} + */ +static DECLCALLBACK(int) hdaR3Attach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + + DEVHDA_LOCK_RETURN(pThis, VERR_IGNORED); + + LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags)); + + PHDADRIVER pDrv; + int rc2 = hdaR3AttachInternal(pThis, uLUN, fFlags, &pDrv); + if (RT_SUCCESS(rc2)) + rc2 = hdaR3MixerAddDrv(pThis, pDrv); + + if (RT_FAILURE(rc2)) + LogFunc(("Failed with %Rrc\n", rc2)); + + DEVHDA_UNLOCK(pThis); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnDetach} + */ +static DECLCALLBACK(void) hdaR3Detach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + + DEVHDA_LOCK(pThis); + + LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags)); + + PHDADRIVER pDrv, pDrvNext; + RTListForEachSafe(&pThis->lstDrv, pDrv, pDrvNext, HDADRIVER, Node) + { + if (pDrv->uLUN == uLUN) + { + int rc2 = hdaR3DetachInternal(pThis, pDrv, fFlags); + if (RT_SUCCESS(rc2)) + { + RTMemFree(pDrv); + pDrv = NULL; + } + + break; + } + } + + DEVHDA_UNLOCK(pThis); +} + +/** + * Powers off the device. + * + * @param pDevIns Device instance to power off. + */ +static DECLCALLBACK(void) hdaR3PowerOff(PPDMDEVINS pDevIns) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + + DEVHDA_LOCK_RETURN_VOID(pThis); + + LogRel2(("HDA: Powering off ...\n")); + + /* Ditto goes for the codec, which in turn uses the mixer. */ + hdaCodecPowerOff(pThis->pCodec); + + /* + * Note: Destroy the mixer while powering off and *not* in hdaR3Destruct, + * giving the mixer the chance to release any references held to + * PDM audio streams it maintains. + */ + if (pThis->pMixer) + { + AudioMixerDestroy(pThis->pMixer); + pThis->pMixer = NULL; + } + + DEVHDA_UNLOCK(pThis); +} + + +/** + * Re-attaches (replaces) a driver with a new driver. + * + * This is only used by to attach the Null driver when it failed to attach the + * one that was configured. + * + * @returns VBox status code. + * @param pThis Device instance to re-attach driver to. + * @param pDrv Driver instance used for attaching to. + * If NULL is specified, a new driver will be created and appended + * to the driver list. + * @param uLUN The logical unit which is being re-detached. + * @param pszDriver New driver name to attach. + */ +static int hdaR3ReattachInternal(PHDASTATE pThis, PHDADRIVER pDrv, uint8_t uLUN, const char *pszDriver) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pszDriver, VERR_INVALID_POINTER); + + int rc; + + if (pDrv) + { + rc = hdaR3DetachInternal(pThis, pDrv, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + rc = PDMDevHlpDriverDetach(pThis->pDevInsR3, PDMIBASE_2_PDMDRV(pDrv->pDrvBase), 0 /* fFlags */); + + if (RT_FAILURE(rc)) + return rc; + + pDrv = NULL; + } + + PVM pVM = PDMDevHlpGetVM(pThis->pDevInsR3); + PCFGMNODE pRoot = CFGMR3GetRoot(pVM); + PCFGMNODE pDev0 = CFGMR3GetChild(pRoot, "Devices/hda/0/"); + + /* Remove LUN branch. */ + CFGMR3RemoveNode(CFGMR3GetChildF(pDev0, "LUN#%u/", uLUN)); + +#define RC_CHECK() if (RT_FAILURE(rc)) { AssertReleaseRC(rc); break; } + + do + { + PCFGMNODE pLunL0; + rc = CFGMR3InsertNodeF(pDev0, &pLunL0, "LUN#%u/", uLUN); RC_CHECK(); + rc = CFGMR3InsertString(pLunL0, "Driver", "AUDIO"); RC_CHECK(); + rc = CFGMR3InsertNode(pLunL0, "Config/", NULL); RC_CHECK(); + + PCFGMNODE pLunL1, pLunL2; + rc = CFGMR3InsertNode (pLunL0, "AttachedDriver/", &pLunL1); RC_CHECK(); + rc = CFGMR3InsertNode (pLunL1, "Config/", &pLunL2); RC_CHECK(); + rc = CFGMR3InsertString(pLunL1, "Driver", pszDriver); RC_CHECK(); + + rc = CFGMR3InsertString(pLunL2, "AudioDriver", pszDriver); RC_CHECK(); + + } while (0); + + if (RT_SUCCESS(rc)) + rc = hdaR3AttachInternal(pThis, uLUN, 0 /* fFlags */, NULL /* ppDrv */); + + LogFunc(("pThis=%p, uLUN=%u, pszDriver=%s, rc=%Rrc\n", pThis, uLUN, pszDriver, rc)); + +#undef RC_CHECK + + return rc; +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + */ +static DECLCALLBACK(void) hdaR3Reset(PPDMDEVINS pDevIns) +{ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + + LogFlowFuncEnter(); + + DEVHDA_LOCK_RETURN_VOID(pThis); + + /* + * 18.2.6,7 defines that values of this registers might be cleared on power on/reset + * hdaR3Reset shouldn't affects these registers. + */ + HDA_REG(pThis, WAKEEN) = 0x0; + + hdaR3GCTLReset(pThis); + + /* Indicate that HDA is not in reset. The firmware is supposed to (un)reset HDA, + * but we can take a shortcut. + */ + HDA_REG(pThis, GCTL) = HDA_GCTL_CRST; + + DEVHDA_UNLOCK(pThis); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnRelocate} + */ +static DECLCALLBACK(void) hdaR3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta) +{ + NOREF(offDelta); + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnDestruct} + */ +static DECLCALLBACK(int) hdaR3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + DEVHDA_LOCK(pThis); /** @todo r=bird: this will fail on early constructor failure. */ + + PHDADRIVER pDrv; + while (!RTListIsEmpty(&pThis->lstDrv)) + { + pDrv = RTListGetFirst(&pThis->lstDrv, HDADRIVER, Node); + + RTListNodeRemove(&pDrv->Node); + RTMemFree(pDrv); + } + + if (pThis->pCodec) + { + hdaCodecDestruct(pThis->pCodec); + + RTMemFree(pThis->pCodec); + pThis->pCodec = NULL; + } + + RTMemFree(pThis->pu32CorbBuf); + pThis->pu32CorbBuf = NULL; + + RTMemFree(pThis->pu64RirbBuf); + pThis->pu64RirbBuf = NULL; + + for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++) + hdaR3StreamDestroy(&pThis->aStreams[i]); + + DEVHDA_UNLOCK(pThis); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct} + */ +static DECLCALLBACK(int) hdaR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */ + PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE); + Assert(iInstance == 0); RT_NOREF(iInstance); + + /* + * Initialize the state sufficently to make the destructor work. + */ + pThis->uAlignmentCheckMagic = HDASTATE_ALIGNMENT_CHECK_MAGIC; + RTListInit(&pThis->lstDrv); + /** @todo r=bird: There are probably other things which should be + * initialized here before we start failing. */ + + /* + * Validations. + */ + if (!CFGMR3AreValuesValid(pCfg, "RZEnabled\0" + "TimerHz\0" + "PosAdjustEnabled\0" + "PosAdjustFrames\0" + "DebugEnabled\0" + "DebugPathOut\0")) + { + return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, + N_ ("Invalid configuration for the Intel HDA device")); + } + + int rc = CFGMR3QueryBoolDef(pCfg, "RZEnabled", &pThis->fRZEnabled, true); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("HDA configuration error: failed to read RCEnabled as boolean")); + + + rc = CFGMR3QueryU16Def(pCfg, "TimerHz", &pThis->uTimerHz, HDA_TIMER_HZ_DEFAULT /* Default value, if not set. */); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("HDA configuration error: failed to read Hertz (Hz) rate as unsigned integer")); + + if (pThis->uTimerHz != HDA_TIMER_HZ_DEFAULT) + LogRel(("HDA: Using custom device timer rate (%RU16Hz)\n", pThis->uTimerHz)); + + rc = CFGMR3QueryBoolDef(pCfg, "PosAdjustEnabled", &pThis->fPosAdjustEnabled, true); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("HDA configuration error: failed to read position adjustment enabled as boolean")); + + if (!pThis->fPosAdjustEnabled) + LogRel(("HDA: Position adjustment is disabled\n")); + + rc = CFGMR3QueryU16Def(pCfg, "PosAdjustFrames", &pThis->cPosAdjustFrames, HDA_POS_ADJUST_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("HDA configuration error: failed to read position adjustment frames as unsigned integer")); + + if (pThis->cPosAdjustFrames) + LogRel(("HDA: Using custom position adjustment (%RU16 audio frames)\n", pThis->cPosAdjustFrames)); + + rc = CFGMR3QueryBoolDef(pCfg, "DebugEnabled", &pThis->Dbg.fEnabled, false); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("HDA configuration error: failed to read debugging enabled flag as boolean")); + + rc = CFGMR3QueryStringDef(pCfg, "DebugPathOut", pThis->Dbg.szOutPath, sizeof(pThis->Dbg.szOutPath), + VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("HDA configuration error: failed to read debugging output path flag as string")); + + if (!strlen(pThis->Dbg.szOutPath)) + RTStrPrintf(pThis->Dbg.szOutPath, sizeof(pThis->Dbg.szOutPath), VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH); + + if (pThis->Dbg.fEnabled) + LogRel2(("HDA: Debug output will be saved to '%s'\n", pThis->Dbg.szOutPath)); + + /* + * Use an own critical section for the device instead of the default + * one provided by PDM. This allows fine-grained locking in combination + * with TM when timer-specific stuff is being called in e.g. the MMIO handlers. + */ + rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CritSect, RT_SRC_POS, "HDA"); + AssertRCReturn(rc, rc); + + rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns)); + AssertRCReturn(rc, rc); + + /* + * Initialize data (most of it anyway). + */ + pThis->pDevInsR3 = pDevIns; + pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns); + pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns); + /* IBase */ + pThis->IBase.pfnQueryInterface = hdaR3QueryInterface; + + /* PCI Device */ + PCIDevSetVendorId (&pThis->PciDev, HDA_PCI_VENDOR_ID); /* nVidia */ + PCIDevSetDeviceId (&pThis->PciDev, HDA_PCI_DEVICE_ID); /* HDA */ + + PCIDevSetCommand (&pThis->PciDev, 0x0000); /* 04 rw,ro - pcicmd. */ + PCIDevSetStatus (&pThis->PciDev, VBOX_PCI_STATUS_CAP_LIST); /* 06 rwc?,ro? - pcists. */ + PCIDevSetRevisionId (&pThis->PciDev, 0x01); /* 08 ro - rid. */ + PCIDevSetClassProg (&pThis->PciDev, 0x00); /* 09 ro - pi. */ + PCIDevSetClassSub (&pThis->PciDev, 0x03); /* 0a ro - scc; 03 == HDA. */ + PCIDevSetClassBase (&pThis->PciDev, 0x04); /* 0b ro - bcc; 04 == multimedia. */ + PCIDevSetHeaderType (&pThis->PciDev, 0x00); /* 0e ro - headtyp. */ + PCIDevSetBaseAddress (&pThis->PciDev, 0, /* 10 rw - MMIO */ + false /* fIoSpace */, false /* fPrefetchable */, true /* f64Bit */, 0x00000000); + PCIDevSetInterruptLine (&pThis->PciDev, 0x00); /* 3c rw. */ + PCIDevSetInterruptPin (&pThis->PciDev, 0x01); /* 3d ro - INTA#. */ + +#if defined(HDA_AS_PCI_EXPRESS) + PCIDevSetCapabilityList (&pThis->PciDev, 0x80); +#elif defined(VBOX_WITH_MSI_DEVICES) + PCIDevSetCapabilityList (&pThis->PciDev, 0x60); +#else + PCIDevSetCapabilityList (&pThis->PciDev, 0x50); /* ICH6 datasheet 18.1.16 */ +#endif + + /// @todo r=michaln: If there are really no PCIDevSetXx for these, the meaning + /// of these values needs to be properly documented! + /* HDCTL off 0x40 bit 0 selects signaling mode (1-HDA, 0 - Ac97) 18.1.19 */ + PCIDevSetByte(&pThis->PciDev, 0x40, 0x01); + + /* Power Management */ + PCIDevSetByte(&pThis->PciDev, 0x50 + 0, VBOX_PCI_CAP_ID_PM); + PCIDevSetByte(&pThis->PciDev, 0x50 + 1, 0x0); /* next */ + PCIDevSetWord(&pThis->PciDev, 0x50 + 2, VBOX_PCI_PM_CAP_DSI | 0x02 /* version, PM1.1 */ ); + +#ifdef HDA_AS_PCI_EXPRESS + /* PCI Express */ + PCIDevSetByte(&pThis->PciDev, 0x80 + 0, VBOX_PCI_CAP_ID_EXP); /* PCI_Express */ + PCIDevSetByte(&pThis->PciDev, 0x80 + 1, 0x60); /* next */ + /* Device flags */ + PCIDevSetWord(&pThis->PciDev, 0x80 + 2, + /* version */ 0x1 | + /* Root Complex Integrated Endpoint */ (VBOX_PCI_EXP_TYPE_ROOT_INT_EP << 4) | + /* MSI */ (100) << 9 ); + /* Device capabilities */ + PCIDevSetDWord(&pThis->PciDev, 0x80 + 4, VBOX_PCI_EXP_DEVCAP_FLRESET); + /* Device control */ + PCIDevSetWord( &pThis->PciDev, 0x80 + 8, 0); + /* Device status */ + PCIDevSetWord( &pThis->PciDev, 0x80 + 10, 0); + /* Link caps */ + PCIDevSetDWord(&pThis->PciDev, 0x80 + 12, 0); + /* Link control */ + PCIDevSetWord( &pThis->PciDev, 0x80 + 16, 0); + /* Link status */ + PCIDevSetWord( &pThis->PciDev, 0x80 + 18, 0); + /* Slot capabilities */ + PCIDevSetDWord(&pThis->PciDev, 0x80 + 20, 0); + /* Slot control */ + PCIDevSetWord( &pThis->PciDev, 0x80 + 24, 0); + /* Slot status */ + PCIDevSetWord( &pThis->PciDev, 0x80 + 26, 0); + /* Root control */ + PCIDevSetWord( &pThis->PciDev, 0x80 + 28, 0); + /* Root capabilities */ + PCIDevSetWord( &pThis->PciDev, 0x80 + 30, 0); + /* Root status */ + PCIDevSetDWord(&pThis->PciDev, 0x80 + 32, 0); + /* Device capabilities 2 */ + PCIDevSetDWord(&pThis->PciDev, 0x80 + 36, 0); + /* Device control 2 */ + PCIDevSetQWord(&pThis->PciDev, 0x80 + 40, 0); + /* Link control 2 */ + PCIDevSetQWord(&pThis->PciDev, 0x80 + 48, 0); + /* Slot control 2 */ + PCIDevSetWord( &pThis->PciDev, 0x80 + 56, 0); +#endif + + /* + * Register the PCI device. + */ + rc = PDMDevHlpPCIRegister(pDevIns, &pThis->PciDev); + if (RT_FAILURE(rc)) + return rc; + + rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, 0x4000, PCI_ADDRESS_SPACE_MEM, hdaR3PciIoRegionMap); + if (RT_FAILURE(rc)) + return rc; + +#ifdef VBOX_WITH_MSI_DEVICES + PDMMSIREG MsiReg; + RT_ZERO(MsiReg); + MsiReg.cMsiVectors = 1; + MsiReg.iMsiCapOffset = 0x60; + MsiReg.iMsiNextOffset = 0x50; + rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg); + if (RT_FAILURE(rc)) + { + /* That's OK, we can work without MSI */ + PCIDevSetCapabilityList(&pThis->PciDev, 0x50); + } +#endif + + rc = PDMDevHlpSSMRegister(pDevIns, HDA_SSM_VERSION, sizeof(*pThis), hdaR3SaveExec, hdaR3LoadExec); + if (RT_FAILURE(rc)) + return rc; + +#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + LogRel(("HDA: Asynchronous I/O enabled\n")); +#endif + + uint8_t uLUN; + for (uLUN = 0; uLUN < UINT8_MAX; ++uLUN) + { + LogFunc(("Trying to attach driver for LUN #%RU32 ...\n", uLUN)); + rc = hdaR3AttachInternal(pThis, uLUN, 0 /* fFlags */, NULL /* ppDrv */); + if (RT_FAILURE(rc)) + { + if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + rc = VINF_SUCCESS; + else if (rc == VERR_AUDIO_BACKEND_INIT_FAILED) + { + hdaR3ReattachInternal(pThis, NULL /* pDrv */, uLUN, "NullAudio"); + PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", + N_("Host audio backend initialization has failed. Selecting the NULL audio backend " + "with the consequence that no sound is audible")); + /* Attaching to the NULL audio backend will never fail. */ + rc = VINF_SUCCESS; + } + break; + } + } + + LogFunc(("cLUNs=%RU8, rc=%Rrc\n", uLUN, rc)); + + if (RT_SUCCESS(rc)) + { + rc = AudioMixerCreate("HDA Mixer", 0 /* uFlags */, &pThis->pMixer); + if (RT_SUCCESS(rc)) + { + /* + * Add mixer output sinks. + */ +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + rc = AudioMixerCreateSink(pThis->pMixer, "[Playback] Front", + AUDMIXSINKDIR_OUTPUT, &pThis->SinkFront.pMixSink); + AssertRC(rc); + rc = AudioMixerCreateSink(pThis->pMixer, "[Playback] Center / Subwoofer", + AUDMIXSINKDIR_OUTPUT, &pThis->SinkCenterLFE.pMixSink); + AssertRC(rc); + rc = AudioMixerCreateSink(pThis->pMixer, "[Playback] Rear", + AUDMIXSINKDIR_OUTPUT, &pThis->SinkRear.pMixSink); + AssertRC(rc); +#else + rc = AudioMixerCreateSink(pThis->pMixer, "[Playback] PCM Output", + AUDMIXSINKDIR_OUTPUT, &pThis->SinkFront.pMixSink); + AssertRC(rc); +#endif + /* + * Add mixer input sinks. + */ + rc = AudioMixerCreateSink(pThis->pMixer, "[Recording] Line In", + AUDMIXSINKDIR_INPUT, &pThis->SinkLineIn.pMixSink); + AssertRC(rc); +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + rc = AudioMixerCreateSink(pThis->pMixer, "[Recording] Microphone In", + AUDMIXSINKDIR_INPUT, &pThis->SinkMicIn.pMixSink); + AssertRC(rc); +#endif + /* There is no master volume control. Set the master to max. */ + PDMAUDIOVOLUME vol = { false, 255, 255 }; + rc = AudioMixerSetMasterVolume(pThis->pMixer, &vol); + AssertRC(rc); + } + } + + if (RT_SUCCESS(rc)) + { + /* Allocate CORB buffer. */ + pThis->cbCorbBuf = HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE; + pThis->pu32CorbBuf = (uint32_t *)RTMemAllocZ(pThis->cbCorbBuf); + if (pThis->pu32CorbBuf) + { + /* Allocate RIRB buffer. */ + pThis->cbRirbBuf = HDA_RIRB_SIZE * HDA_RIRB_ELEMENT_SIZE; + pThis->pu64RirbBuf = (uint64_t *)RTMemAllocZ(pThis->cbRirbBuf); + if (pThis->pu64RirbBuf) + { + /* Allocate codec. */ + pThis->pCodec = (PHDACODEC)RTMemAllocZ(sizeof(HDACODEC)); + if (!pThis->pCodec) + rc = PDMDEV_SET_ERROR(pDevIns, VERR_NO_MEMORY, N_("Out of memory allocating HDA codec state")); + } + else + rc = PDMDEV_SET_ERROR(pDevIns, VERR_NO_MEMORY, N_("Out of memory allocating RIRB")); + } + else + rc = PDMDEV_SET_ERROR(pDevIns, VERR_NO_MEMORY, N_("Out of memory allocating CORB")); + + if (RT_SUCCESS(rc)) + { + /* Set codec callbacks to this controller. */ + pThis->pCodec->pfnCbMixerAddStream = hdaR3MixerAddStream; + pThis->pCodec->pfnCbMixerRemoveStream = hdaR3MixerRemoveStream; + pThis->pCodec->pfnCbMixerControl = hdaR3MixerControl; + pThis->pCodec->pfnCbMixerSetVolume = hdaR3MixerSetVolume; + + pThis->pCodec->pHDAState = pThis; /* Assign HDA controller state to codec. */ + + /* Construct the codec. */ + rc = hdaCodecConstruct(pDevIns, pThis->pCodec, 0 /* Codec index */, pCfg); + if (RT_FAILURE(rc)) + AssertRCReturn(rc, rc); + + /* ICH6 datasheet defines 0 values for SVID and SID (18.1.14-15), which together with values returned for + verb F20 should provide device/codec recognition. */ + Assert(pThis->pCodec->u16VendorId); + Assert(pThis->pCodec->u16DeviceId); + PCIDevSetSubSystemVendorId(&pThis->PciDev, pThis->pCodec->u16VendorId); /* 2c ro - intel.) */ + PCIDevSetSubSystemId( &pThis->PciDev, pThis->pCodec->u16DeviceId); /* 2e ro. */ + } + } + + if (RT_SUCCESS(rc)) + { + /* + * Create all hardware streams. + */ + static const char * const s_apszNames[] = + { + "HDA SD0", "HDA SD1", "HDA SD2", "HDA SD3", + "HDA SD4", "HDA SD5", "HDA SD6", "HDA SD7", + }; + AssertCompile(RT_ELEMENTS(s_apszNames) == HDA_MAX_STREAMS); + for (uint8_t i = 0; i < HDA_MAX_STREAMS; ++i) + { + /* Create the emulation timer (per stream). + * + * Note: Use TMCLOCK_VIRTUAL_SYNC here, as the guest's HDA driver + * relies on exact (virtual) DMA timing and uses DMA Position Buffers + * instead of the LPIB registers. + */ + rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, hdaR3Timer, &pThis->aStreams[i], + TMTIMER_FLAGS_NO_CRIT_SECT, s_apszNames[i], &pThis->pTimer[i]); + AssertRCReturn(rc, rc); + + /* Use our own critcal section for the device timer. + * That way we can control more fine-grained when to lock what. */ + rc = TMR3TimerSetCritSect(pThis->pTimer[i], &pThis->CritSect); + AssertRCReturn(rc, rc); + + rc = hdaR3StreamCreate(&pThis->aStreams[i], pThis, i /* u8SD */); + AssertRC(rc); + } + +#ifdef VBOX_WITH_AUDIO_HDA_ONETIME_INIT + /* + * Initialize the driver chain. + */ + PHDADRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, HDADRIVER, Node) + { + /* + * Only primary drivers are critical for the VM to run. Everything else + * might not worth showing an own error message box in the GUI. + */ + if (!(pDrv->fFlags & PDMAUDIODRVFLAGS_PRIMARY)) + continue; + + PPDMIAUDIOCONNECTOR pCon = pDrv->pConnector; + AssertPtr(pCon); + + bool fValidLineIn = AudioMixerStreamIsValid(pDrv->LineIn.pMixStrm); +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + bool fValidMicIn = AudioMixerStreamIsValid(pDrv->MicIn.pMixStrm); +# endif + bool fValidOut = AudioMixerStreamIsValid(pDrv->Front.pMixStrm); +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + /** @todo Anything to do here? */ +# endif + + if ( !fValidLineIn +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + && !fValidMicIn +# endif + && !fValidOut) + { + LogRel(("HDA: Falling back to NULL backend (no sound audible)\n")); + + hdaR3Reset(pDevIns); + hdaR3ReattachInternal(pThis, pDrv, pDrv->uLUN, "NullAudio"); + + PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", + N_("No audio devices could be opened. Selecting the NULL audio backend " + "with the consequence that no sound is audible")); + } + else + { + bool fWarn = false; + + PDMAUDIOBACKENDCFG backendCfg; + int rc2 = pCon->pfnGetConfig(pCon, &backendCfg); + if (RT_SUCCESS(rc2)) + { + if (backendCfg.cMaxStreamsIn) + { +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + /* If the audio backend supports two or more input streams at once, + * warn if one of our two inputs (microphone-in and line-in) failed to initialize. */ + if (backendCfg.cMaxStreamsIn >= 2) + fWarn = !fValidLineIn || !fValidMicIn; + /* If the audio backend only supports one input stream at once (e.g. pure ALSA, and + * *not* ALSA via PulseAudio plugin!), only warn if both of our inputs failed to initialize. + * One of the two simply is not in use then. */ + else if (backendCfg.cMaxStreamsIn == 1) + fWarn = !fValidLineIn && !fValidMicIn; + /* Don't warn if our backend is not able of supporting any input streams at all. */ +# else /* !VBOX_WITH_AUDIO_HDA_MIC_IN */ + /* We only have line-in as input source. */ + fWarn = !fValidLineIn; +# endif /* VBOX_WITH_AUDIO_HDA_MIC_IN */ + } + + if ( !fWarn + && backendCfg.cMaxStreamsOut) + { + fWarn = !fValidOut; + } + } + else + { + LogRel(("HDA: Unable to retrieve audio backend configuration for LUN #%RU8, rc=%Rrc\n", pDrv->uLUN, rc2)); + fWarn = true; + } + + if (fWarn) + { + char szMissingStreams[255]; + size_t len = 0; + if (!fValidLineIn) + { + LogRel(("HDA: WARNING: Unable to open PCM line input for LUN #%RU8!\n", pDrv->uLUN)); + len = RTStrPrintf(szMissingStreams, sizeof(szMissingStreams), "PCM Input"); + } +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + if (!fValidMicIn) + { + LogRel(("HDA: WARNING: Unable to open PCM microphone input for LUN #%RU8!\n", pDrv->uLUN)); + len += RTStrPrintf(szMissingStreams + len, + sizeof(szMissingStreams) - len, len ? ", PCM Microphone" : "PCM Microphone"); + } +# endif /* VBOX_WITH_AUDIO_HDA_MIC_IN */ + if (!fValidOut) + { + LogRel(("HDA: WARNING: Unable to open PCM output for LUN #%RU8!\n", pDrv->uLUN)); + len += RTStrPrintf(szMissingStreams + len, + sizeof(szMissingStreams) - len, len ? ", PCM Output" : "PCM Output"); + } + + PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", + N_("Some HDA audio streams (%s) could not be opened. Guest applications generating audio " + "output or depending on audio input may hang. Make sure your host audio device " + "is working properly. Check the logfile for error messages of the audio " + "subsystem"), szMissingStreams); + } + } + } +#endif /* VBOX_WITH_AUDIO_HDA_ONETIME_INIT */ + } + + if (RT_SUCCESS(rc)) + { + hdaR3Reset(pDevIns); + + /* + * Debug and string formatter types. + */ + PDMDevHlpDBGFInfoRegister(pDevIns, "hda", "HDA info. (hda [register case-insensitive])", hdaR3DbgInfo); + PDMDevHlpDBGFInfoRegister(pDevIns, "hdabdle", "HDA stream BDLE info. (hdabdle [stream number])", hdaR3DbgInfoBDLE); + PDMDevHlpDBGFInfoRegister(pDevIns, "hdastream", "HDA stream info. (hdastream [stream number])", hdaR3DbgInfoStream); + PDMDevHlpDBGFInfoRegister(pDevIns, "hdcnodes", "HDA codec nodes.", hdaR3DbgInfoCodecNodes); + PDMDevHlpDBGFInfoRegister(pDevIns, "hdcselector", "HDA codec's selector states [node number].", hdaR3DbgInfoCodecSelector); + PDMDevHlpDBGFInfoRegister(pDevIns, "hdamixer", "HDA mixer state.", hdaR3DbgInfoMixer); + + rc = RTStrFormatTypeRegister("bdle", hdaR3StrFmtBDLE, NULL); + AssertRC(rc); + rc = RTStrFormatTypeRegister("sdctl", hdaR3StrFmtSDCTL, NULL); + AssertRC(rc); + rc = RTStrFormatTypeRegister("sdsts", hdaR3StrFmtSDSTS, NULL); + AssertRC(rc); + rc = RTStrFormatTypeRegister("sdfifos", hdaR3StrFmtSDFIFOS, NULL); + AssertRC(rc); + rc = RTStrFormatTypeRegister("sdfifow", hdaR3StrFmtSDFIFOW, NULL); + AssertRC(rc); + + /* + * Some debug assertions. + */ + for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++) + { + struct HDAREGDESC const *pReg = &g_aHdaRegMap[i]; + struct HDAREGDESC const *pNextReg = i + 1 < RT_ELEMENTS(g_aHdaRegMap) ? &g_aHdaRegMap[i + 1] : NULL; + + /* binary search order. */ + AssertReleaseMsg(!pNextReg || pReg->offset + pReg->size <= pNextReg->offset, + ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n", + i, pReg->offset, pReg->size, i + 1, pNextReg->offset, pNextReg->size)); + + /* alignment. */ + AssertReleaseMsg( pReg->size == 1 + || (pReg->size == 2 && (pReg->offset & 1) == 0) + || (pReg->size == 3 && (pReg->offset & 3) == 0) + || (pReg->size == 4 && (pReg->offset & 3) == 0), + ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size)); + + /* registers are packed into dwords - with 3 exceptions with gaps at the end of the dword. */ + AssertRelease(((pReg->offset + pReg->size) & 3) == 0 || pNextReg); + if (pReg->offset & 3) + { + struct HDAREGDESC const *pPrevReg = i > 0 ? &g_aHdaRegMap[i - 1] : NULL; + AssertReleaseMsg(pPrevReg, ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size)); + if (pPrevReg) + AssertReleaseMsg(pPrevReg->offset + pPrevReg->size == pReg->offset, + ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n", + i - 1, pPrevReg->offset, pPrevReg->size, i + 1, pReg->offset, pReg->size)); + } +#if 0 + if ((pReg->offset + pReg->size) & 3) + { + AssertReleaseMsg(pNextReg, ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size)); + if (pNextReg) + AssertReleaseMsg(pReg->offset + pReg->size == pNextReg->offset, + ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n", + i, pReg->offset, pReg->size, i + 1, pNextReg->offset, pNextReg->size)); + } +#endif + /* The final entry is a full DWORD, no gaps! Allows shortcuts. */ + AssertReleaseMsg(pNextReg || ((pReg->offset + pReg->size) & 3) == 0, + ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size)); + } + } + +# ifdef VBOX_WITH_STATISTICS + if (RT_SUCCESS(rc)) + { + /* + * Register statistics. + */ + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTimer, STAMTYPE_PROFILE, "/Devices/HDA/Timer", STAMUNIT_TICKS_PER_CALL, "Profiling hdaR3Timer."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIn, STAMTYPE_PROFILE, "/Devices/HDA/Input", STAMUNIT_TICKS_PER_CALL, "Profiling input."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatOut, STAMTYPE_PROFILE, "/Devices/HDA/Output", STAMUNIT_TICKS_PER_CALL, "Profiling output."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "/Devices/HDA/BytesRead" , STAMUNIT_BYTES, "Bytes read from HDA emulation."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, "/Devices/HDA/BytesWritten", STAMUNIT_BYTES, "Bytes written to HDA emulation."); + } +# endif + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * The device registration structure. + */ +const PDMDEVREG g_DeviceHDA = +{ + /* u32Version */ + PDM_DEVREG_VERSION, + /* szName */ + "hda", + /* szRCMod */ + "VBoxDDRC.rc", + /* szR0Mod */ + "VBoxDDR0.r0", + /* pszDescription */ + "Intel HD Audio Controller", + /* fFlags */ + PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0, + /* fClass */ + PDM_DEVREG_CLASS_AUDIO, + /* cMaxInstances */ + 1, + /* cbInstance */ + sizeof(HDASTATE), + /* pfnConstruct */ + hdaR3Construct, + /* pfnDestruct */ + hdaR3Destruct, + /* pfnRelocate */ + hdaR3Relocate, + /* pfnMemSetup */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + hdaR3Reset, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + hdaR3Attach, + /* pfnDetach */ + hdaR3Detach, + /* pfnQueryInterface. */ + NULL, + /* pfnInitComplete */ + NULL, + /* pfnPowerOff */ + hdaR3PowerOff, + /* pfnSoftReset */ + NULL, + /* u32VersionEnd */ + PDM_DEVREG_VERSION +}; + +#endif /* IN_RING3 */ +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ + diff --git a/src/VBox/Devices/Audio/DevHDA.h b/src/VBox/Devices/Audio/DevHDA.h new file mode 100644 index 00000000..1b03f60c --- /dev/null +++ b/src/VBox/Devices/Audio/DevHDA.h @@ -0,0 +1,222 @@ +/* $Id: DevHDA.h $ */ +/** @file + * DevHDA.h - VBox Intel HD Audio Controller. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_DevHDA_h +#define VBOX_INCLUDED_SRC_Audio_DevHDA_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/path.h> + +#include <VBox/vmm/pdmdev.h> + +#include "AudioMixer.h" + +#include "HDACodec.h" +#include "HDAStream.h" +#include "HDAStreamMap.h" +#include "HDAStreamPeriod.h" + + + +/** + * Structure defining an HDA mixer sink. + * Its purpose is to know which audio mixer sink is bound to + * which SDn (SDI/SDO) device stream. + * + * This is needed in order to handle interleaved streams + * (that is, multiple channels in one stream) or non-interleaved + * streams (each channel has a dedicated stream). + * + * This is only known to the actual device emulation level. + */ +typedef struct HDAMIXERSINK +{ + R3PTRTYPE(PHDASTREAM) pStream; + /** Pointer to the actual audio mixer sink. */ + R3PTRTYPE(PAUDMIXSINK) pMixSink; +} HDAMIXERSINK, *PHDAMIXERSINK; + +/** + * Structure for mapping a stream tag to an HDA stream. + */ +typedef struct HDATAG +{ + /** Own stream tag. */ + uint8_t uTag; + uint8_t Padding[7]; + /** Pointer to associated stream. */ + R3PTRTYPE(PHDASTREAM) pStream; +} HDATAG, *PHDATAG; + +/** @todo Make STAM values out of this? */ +typedef struct HDASTATEDBGINFO +{ +#ifdef DEBUG + /** Timestamp (in ns) of the last timer callback (hdaTimer). + * Used to calculate the time actually elapsed between two timer callbacks. */ + uint64_t tsTimerLastCalledNs; + /** IRQ debugging information. */ + struct + { + /** Timestamp (in ns) of last processed (asserted / deasserted) IRQ. */ + uint64_t tsProcessedLastNs; + /** Timestamp (in ns) of last asserted IRQ. */ + uint64_t tsAssertedNs; + /** How many IRQs have been asserted already. */ + uint64_t cAsserted; + /** Accumulated elapsed time (in ns) of all IRQ being asserted. */ + uint64_t tsAssertedTotalNs; + /** Timestamp (in ns) of last deasserted IRQ. */ + uint64_t tsDeassertedNs; + /** How many IRQs have been deasserted already. */ + uint64_t cDeasserted; + /** Accumulated elapsed time (in ns) of all IRQ being deasserted. */ + uint64_t tsDeassertedTotalNs; + } IRQ; +#endif + /** Whether debugging is enabled or not. */ + bool fEnabled; + /** Path where to dump the debug output to. + * Defaults to VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH. */ + char szOutPath[RTPATH_MAX + 1]; +} HDASTATEDBGINFO, *PHDASTATEDBGINFO; + +/** + * ICH Intel HD Audio Controller state. + */ +typedef struct HDASTATE +{ + /** The PCI device structure. */ + PDMPCIDEV PciDev; + /** R3 Pointer to the device instance. */ + PPDMDEVINSR3 pDevInsR3; + /** R0 Pointer to the device instance. */ + PPDMDEVINSR0 pDevInsR0; + /** R0 Pointer to the device instance. */ + PPDMDEVINSRC pDevInsRC; + /** Padding for alignment. */ + uint32_t u32Padding; + /** Critical section protecting the HDA state. */ + PDMCRITSECT CritSect; + /** The base interface for LUN\#0. */ + PDMIBASE IBase; + RTGCPHYS MMIOBaseAddr; + /** The HDA's register set. */ + uint32_t au32Regs[HDA_NUM_REGS]; + /** Internal stream states. */ + HDASTREAM aStreams[HDA_MAX_STREAMS]; + /** Mapping table between stream tags and stream states. */ + HDATAG aTags[HDA_MAX_TAGS]; + /** CORB buffer base address. */ + uint64_t u64CORBBase; + /** RIRB buffer base address. */ + uint64_t u64RIRBBase; + /** DMA base address. + * Made out of DPLBASE + DPUBASE (3.3.32 + 3.3.33). */ + uint64_t u64DPBase; + /** Pointer to CORB buffer. */ + R3PTRTYPE(uint32_t *) pu32CorbBuf; + /** Size in bytes of CORB buffer. */ + uint32_t cbCorbBuf; + /** Padding for alignment. */ + uint32_t u32Padding1; + /** Pointer to RIRB buffer. */ + R3PTRTYPE(uint64_t *) pu64RirbBuf; + /** Size in bytes of RIRB buffer. */ + uint32_t cbRirbBuf; + /** DMA position buffer enable bit. */ + bool fDMAPosition; + /** Flag whether the R0 and RC parts are enabled. */ + bool fRZEnabled; + /** Reserved. */ + bool fPadding1b; + /** Number of active (running) SDn streams. */ + uint8_t cStreamsActive; + /** The stream timers for pumping data thru the attached LUN drivers. */ + PTMTIMERR3 pTimer[HDA_MAX_STREAMS]; +#ifdef VBOX_WITH_STATISTICS + STAMPROFILE StatTimer; + STAMPROFILE StatIn; + STAMPROFILE StatOut; + STAMCOUNTER StatBytesRead; + STAMCOUNTER StatBytesWritten; +#endif + /** Pointer to HDA codec to use. */ + R3PTRTYPE(PHDACODEC) pCodec; + /** List of associated LUN drivers (HDADRIVER). */ + RTLISTANCHORR3 lstDrv; + /** The device' software mixer. */ + R3PTRTYPE(PAUDIOMIXER) pMixer; + /** HDA sink for (front) output. */ + HDAMIXERSINK SinkFront; +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + /** HDA sink for center / LFE output. */ + HDAMIXERSINK SinkCenterLFE; + /** HDA sink for rear output. */ + HDAMIXERSINK SinkRear; +#endif + /** HDA mixer sink for line input. */ + HDAMIXERSINK SinkLineIn; +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + /** Audio mixer sink for microphone input. */ + HDAMIXERSINK SinkMicIn; +#endif + /** Last updated wall clock (WALCLK) counter. */ + uint64_t u64WalClk; + /** Response Interrupt Count (RINTCNT). */ + uint16_t u16RespIntCnt; + /** Position adjustment (in audio frames). + * + * This is not an official feature of the HDA specs, but used by + * certain OS drivers (e.g. snd_hda_intel) to work around certain + * quirks by "real" HDA hardware implementations. + * + * The position adjustment specifies how many audio frames + * a stream is ahead from its actual reading/writing position when + * starting a stream. + */ + uint16_t cPosAdjustFrames; + /** Whether the position adjustment is enabled or not. */ + bool fPosAdjustEnabled; +#ifdef VBOX_STRICT + /** Wall clock (WALCLK) stale count. + * This indicates the number of set wall clock + * values which did not actually move the counter forward (stale). */ + uint8_t u8WalClkStaleCnt; + uint8_t Padding1[2]; +#else + uint8_t Padding1[3]; +#endif + /** Current IRQ level. */ + uint8_t u8IRQL; + /** The device timer Hz rate. Defaults to HDA_TIMER_HZ_DEFAULT. */ + uint16_t uTimerHz; + /** Padding for alignment. */ + uint8_t au8Padding3[3]; + HDASTATEDBGINFO Dbg; + /** This is for checking that the build was correctly configured in all contexts. + * This is set to HDASTATE_ALIGNMENT_CHECK_MAGIC. */ + uint64_t uAlignmentCheckMagic; +} HDASTATE, *PHDASTATE; + +/** Value for HDASTATE:uAlignmentCheckMagic. */ +#define HDASTATE_ALIGNMENT_CHECK_MAGIC UINT64_C(0x1298afb75893e059) + +#endif /* !VBOX_INCLUDED_SRC_Audio_DevHDA_h */ + diff --git a/src/VBox/Devices/Audio/DevHDACommon.cpp b/src/VBox/Devices/Audio/DevHDACommon.cpp new file mode 100644 index 00000000..d32dcbc8 --- /dev/null +++ b/src/VBox/Devices/Audio/DevHDACommon.cpp @@ -0,0 +1,719 @@ +/* $Id: DevHDACommon.cpp $ */ +/** @file + * DevHDACommon.cpp - Shared HDA device functions. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/errcore.h> + +#define LOG_GROUP LOG_GROUP_DEV_HDA +#include <VBox/log.h> + +#include "DrvAudio.h" + +#include "DevHDA.h" +#include "DevHDACommon.h" + +#include "HDAStream.h" + + +#ifndef LOG_ENABLED +/** + * Processes (de/asserts) the interrupt according to the HDA's current state. + * + * @returns IPRT status code. + * @param pThis HDA state. + */ +int hdaProcessInterrupt(PHDASTATE pThis) +#else +/** + * Processes (de/asserts) the interrupt according to the HDA's current state. + * Debug version. + * + * @returns IPRT status code. + * @param pThis HDA state. + * @param pszSource Caller information. + */ +int hdaProcessInterrupt(PHDASTATE pThis, const char *pszSource) +#endif +{ + uint32_t uIntSts = hdaGetINTSTS(pThis); + + HDA_REG(pThis, INTSTS) = uIntSts; + + /* NB: It is possible to have GIS set even when CIE/SIEn are all zero; the GIS bit does + * not control the interrupt signal. See Figure 4 on page 54 of the HDA 1.0a spec. + */ + /* Global Interrupt Enable (GIE) set? */ + if ( (HDA_REG(pThis, INTCTL) & HDA_INTCTL_GIE) + && (HDA_REG(pThis, INTSTS) & HDA_REG(pThis, INTCTL) & (HDA_INTCTL_CIE | HDA_STRMINT_MASK))) + { + Log3Func(("Asserted (%s)\n", pszSource)); + + PDMDevHlpPCISetIrq(pThis->CTX_SUFF(pDevIns), 0, 1 /* Assert */); + pThis->u8IRQL = 1; + +#ifdef DEBUG + pThis->Dbg.IRQ.tsAssertedNs = RTTimeNanoTS(); + pThis->Dbg.IRQ.tsProcessedLastNs = pThis->Dbg.IRQ.tsAssertedNs; +#endif + } + else + { + Log3Func(("Deasserted (%s)\n", pszSource)); + + PDMDevHlpPCISetIrq(pThis->CTX_SUFF(pDevIns), 0, 0 /* Deassert */); + pThis->u8IRQL = 0; + } + + return VINF_SUCCESS; +} + +/** + * Retrieves the currently set value for the wall clock. + * + * @return IPRT status code. + * @return Currently set wall clock value. + * @param pThis HDA state. + * + * @remark Operation is atomic. + */ +uint64_t hdaWalClkGetCurrent(PHDASTATE pThis) +{ + return ASMAtomicReadU64(&pThis->u64WalClk); +} + +#ifdef IN_RING3 + +/** + * Sets the actual WALCLK register to the specified wall clock value. + * The specified wall clock value only will be set (unless fForce is set to true) if all + * handled HDA streams have passed (in time) that value. This guarantees that the WALCLK + * register stays in sync with all handled HDA streams. + * + * @return true if the WALCLK register has been updated, false if not. + * @param pThis HDA state. + * @param u64WalClk Wall clock value to set WALCLK register to. + * @param fForce Whether to force setting the wall clock value or not. + */ +bool hdaR3WalClkSet(PHDASTATE pThis, uint64_t u64WalClk, bool fForce) +{ + const bool fFrontPassed = hdaR3StreamPeriodHasPassedAbsWalClk (&hdaR3GetStreamFromSink(pThis, &pThis->SinkFront)->State.Period, + u64WalClk); + const uint64_t u64FrontAbsWalClk = hdaR3StreamPeriodGetAbsElapsedWalClk(&hdaR3GetStreamFromSink(pThis, &pThis->SinkFront)->State.Period); +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND +# error "Implement me!" +# endif + + const bool fLineInPassed = hdaR3StreamPeriodHasPassedAbsWalClk (&hdaR3GetStreamFromSink(pThis, &pThis->SinkLineIn)->State.Period, u64WalClk); + const uint64_t u64LineInAbsWalClk = hdaR3StreamPeriodGetAbsElapsedWalClk(&hdaR3GetStreamFromSink(pThis, &pThis->SinkLineIn)->State.Period); +# ifdef VBOX_WITH_HDA_MIC_IN + const bool fMicInPassed = hdaR3StreamPeriodHasPassedAbsWalClk (&hdaR3GetStreamFromSink(pThis, &pThis->SinkMicIn)->State.Period, u64WalClk); + const uint64_t u64MicInAbsWalClk = hdaR3StreamPeriodGetAbsElapsedWalClk(&hdaR3GetStreamFromSink(pThis, &pThis->SinkMicIn)->State.Period); +# endif + +# ifdef VBOX_STRICT + const uint64_t u64WalClkCur = ASMAtomicReadU64(&pThis->u64WalClk); +# endif + + /* Only drive the WALCLK register forward if all (active) stream periods have passed + * the specified point in time given by u64WalClk. */ + if ( ( fFrontPassed +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND +# error "Implement me!" +# endif + && fLineInPassed +# ifdef VBOX_WITH_HDA_MIC_IN + && fMicInPassed +# endif + ) + || fForce) + { + if (!fForce) + { + /* Get the maximum value of all periods we need to handle. + * Not the most elegant solution, but works for now ... */ + u64WalClk = RT_MAX(u64WalClk, u64FrontAbsWalClk); +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND +# error "Implement me!" +# endif + u64WalClk = RT_MAX(u64WalClk, u64LineInAbsWalClk); +# ifdef VBOX_WITH_HDA_MIC_IN + u64WalClk = RT_MAX(u64WalClk, u64MicInAbsWalClk); +# endif + +# ifdef VBOX_STRICT + AssertMsg(u64WalClk >= u64WalClkCur, + ("Setting WALCLK to a value going backwards does not make any sense (old %RU64 vs. new %RU64)\n", + u64WalClkCur, u64WalClk)); + if (u64WalClk == u64WalClkCur) /* Setting a stale value? */ + { + if (pThis->u8WalClkStaleCnt++ > 3) + AssertMsgFailed(("Setting WALCLK to a stale value (%RU64) too often isn't a good idea really. " + "Good luck with stuck audio stuff.\n", u64WalClk)); + } + else + pThis->u8WalClkStaleCnt = 0; +# endif + } + + /* Set the new WALCLK value. */ + ASMAtomicWriteU64(&pThis->u64WalClk, u64WalClk); + } + + const uint64_t u64WalClkNew = hdaWalClkGetCurrent(pThis); + + Log3Func(("Cur: %RU64, New: %RU64 (force %RTbool) -> %RU64 %s\n", + u64WalClkCur, u64WalClk, fForce, + u64WalClkNew, u64WalClkNew == u64WalClk ? "[OK]" : "[DELAYED]")); + + return (u64WalClkNew == u64WalClk); +} + +/** + * Returns the default (mixer) sink from a given SD#. + * Returns NULL if no sink is found. + * + * @return PHDAMIXERSINK + * @param pThis HDA state. + * @param uSD SD# to return mixer sink for. + * NULL if not found / handled. + */ +PHDAMIXERSINK hdaR3GetDefaultSink(PHDASTATE pThis, uint8_t uSD) +{ + if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN) + { + const uint8_t uFirstSDI = 0; + + if (uSD == uFirstSDI) /* First SDI. */ + return &pThis->SinkLineIn; +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + if (uSD == uFirstSDI + 1) + return &pThis->SinkMicIn; +# else + /* If we don't have a dedicated Mic-In sink, use the always present Line-In sink. */ + return &pThis->SinkLineIn; +# endif + } + else + { + const uint8_t uFirstSDO = HDA_MAX_SDI; + + if (uSD == uFirstSDO) + return &pThis->SinkFront; +# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + if (uSD == uFirstSDO + 1) + return &pThis->SinkCenterLFE; + if (uSD == uFirstSDO + 2) + return &pThis->SinkRear; +# endif + } + + return NULL; +} + +#endif /* IN_RING3 */ + +/** + * Returns the audio direction of a specified stream descriptor. + * + * The register layout specifies that input streams (SDI) come first, + * followed by the output streams (SDO). So every stream ID below HDA_MAX_SDI + * is an input stream, whereas everything >= HDA_MAX_SDI is an output stream. + * + * Note: SDnFMT register does not provide that information, so we have to judge + * for ourselves. + * + * @return Audio direction. + */ +PDMAUDIODIR hdaGetDirFromSD(uint8_t uSD) +{ + AssertReturn(uSD < HDA_MAX_STREAMS, PDMAUDIODIR_UNKNOWN); + + if (uSD < HDA_MAX_SDI) + return PDMAUDIODIR_IN; + + return PDMAUDIODIR_OUT; +} + +/** + * Returns the HDA stream of specified stream descriptor number. + * + * @return Pointer to HDA stream, or NULL if none found. + */ +PHDASTREAM hdaGetStreamFromSD(PHDASTATE pThis, uint8_t uSD) +{ + AssertPtrReturn(pThis, NULL); + AssertReturn(uSD < HDA_MAX_STREAMS, NULL); + + if (uSD >= HDA_MAX_STREAMS) + { + AssertMsgFailed(("Invalid / non-handled SD%RU8\n", uSD)); + return NULL; + } + + return &pThis->aStreams[uSD]; +} + +#ifdef IN_RING3 + +/** + * Returns the HDA stream of specified HDA sink. + * + * @return Pointer to HDA stream, or NULL if none found. + */ +PHDASTREAM hdaR3GetStreamFromSink(PHDASTATE pThis, PHDAMIXERSINK pSink) +{ + AssertPtrReturn(pThis, NULL); + AssertPtrReturn(pSink, NULL); + + /** @todo Do something with the channel mapping here? */ + return pSink->pStream; +} + +/** + * Reads DMA data from a given HDA output stream. + * + * @return IPRT status code. + * @param pThis HDA state. + * @param pStream HDA output stream to read DMA data from. + * @param pvBuf Where to store the read data. + * @param cbBuf How much to read in bytes. + * @param pcbRead Returns read bytes from DMA. Optional. + */ +int hdaR3DMARead(PHDASTATE pThis, PHDASTREAM pStream, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + /* pcbRead is optional. */ + + PHDABDLE pBDLE = &pStream->State.BDLE; + + int rc = VINF_SUCCESS; + + uint32_t cbReadTotal = 0; + uint32_t cbLeft = RT_MIN(cbBuf, pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff); + +# ifdef HDA_DEBUG_SILENCE + uint64_t csSilence = 0; + + pStream->Dbg.cSilenceThreshold = 100; + pStream->Dbg.cbSilenceReadMin = _1M; +# endif + + RTGCPHYS addrChunk = pBDLE->Desc.u64BufAddr + pBDLE->State.u32BufOff; + + while (cbLeft) + { + uint32_t cbChunk = RT_MIN(cbLeft, pStream->u16FIFOS); + + rc = PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), addrChunk, (uint8_t *)pvBuf + cbReadTotal, cbChunk); + if (RT_FAILURE(rc)) + break; + +# ifdef HDA_DEBUG_SILENCE + uint16_t *pu16Buf = (uint16_t *)pvBuf; + for (size_t i = 0; i < cbChunk / sizeof(uint16_t); i++) + { + if (*pu16Buf == 0) + csSilence++; + else + break; + pu16Buf++; + } +# endif + if (pStream->Dbg.Runtime.fEnabled) + DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMARaw, (uint8_t *)pvBuf + cbReadTotal, cbChunk, 0 /* fFlags */); + + STAM_COUNTER_ADD(&pThis->StatBytesRead, cbChunk); + addrChunk = (addrChunk + cbChunk) % pBDLE->Desc.u32BufSize; + + Assert(cbLeft >= cbChunk); + cbLeft -= cbChunk; + + cbReadTotal += cbChunk; + } + +# ifdef HDA_DEBUG_SILENCE + if (csSilence) + pStream->Dbg.csSilence += csSilence; + + if ( csSilence == 0 + && pStream->Dbg.csSilence > pStream->Dbg.cSilenceThreshold + && pStream->Dbg.cbReadTotal >= pStream->Dbg.cbSilenceReadMin) + { + LogFunc(("Silent block detected: %RU64 audio samples\n", pStream->Dbg.csSilence)); + pStream->Dbg.csSilence = 0; + } +# endif + + if (RT_SUCCESS(rc)) + { + if (pcbRead) + *pcbRead = cbReadTotal; + } + + return rc; +} + +/** + * Writes audio data from an HDA input stream's FIFO to its associated DMA area. + * + * @return IPRT status code. + * @param pThis HDA state. + * @param pStream HDA input stream to write audio data to. + * @param pvBuf Data to write. + * @param cbBuf How much (in bytes) to write. + * @param pcbWritten Returns written bytes on success. Optional. + */ +int hdaR3DMAWrite(PHDASTATE pThis, PHDASTREAM pStream, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + /* pcbWritten is optional. */ + + PHDABDLE pBDLE = &pStream->State.BDLE; + + int rc = VINF_SUCCESS; + + uint32_t cbWrittenTotal = 0; + uint32_t cbLeft = RT_MIN(cbBuf, pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff); + + RTGCPHYS addrChunk = pBDLE->Desc.u64BufAddr + pBDLE->State.u32BufOff; + + while (cbLeft) + { + uint32_t cbChunk = RT_MIN(cbLeft, pStream->u16FIFOS); + + /* Sanity checks. */ + Assert(cbChunk <= pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff); + + if (pStream->Dbg.Runtime.fEnabled) + DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMARaw, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk, 0 /* fFlags */); + + rc = PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns), + addrChunk, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk); + if (RT_FAILURE(rc)) + break; + + STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbChunk); + addrChunk = (addrChunk + cbChunk) % pBDLE->Desc.u32BufSize; + + Assert(cbLeft >= cbChunk); + cbLeft -= (uint32_t)cbChunk; + + cbWrittenTotal += (uint32_t)cbChunk; + } + + if (RT_SUCCESS(rc)) + { + if (pcbWritten) + *pcbWritten = cbWrittenTotal; + } + else + LogFunc(("Failed with %Rrc\n", rc)); + + return rc; +} + +#endif /* IN_RING3 */ + +/** + * Returns a new INTSTS value based on the current device state. + * + * @returns Determined INTSTS register value. + * @param pThis HDA state. + * + * @remark This function does *not* set INTSTS! + */ +uint32_t hdaGetINTSTS(PHDASTATE pThis) +{ + uint32_t intSts = 0; + + /* Check controller interrupts (RIRB, STATEST). */ + if (HDA_REG(pThis, RIRBSTS) & HDA_REG(pThis, RIRBCTL) & (HDA_RIRBCTL_ROIC | HDA_RIRBCTL_RINTCTL)) + { + intSts |= HDA_INTSTS_CIS; /* Set the Controller Interrupt Status (CIS). */ + } + + /* Check SDIN State Change Status Flags. */ + if (HDA_REG(pThis, STATESTS) & HDA_REG(pThis, WAKEEN)) + { + intSts |= HDA_INTSTS_CIS; /* Touch Controller Interrupt Status (CIS). */ + } + + /* For each stream, check if any interrupt status bit is set and enabled. */ + for (uint8_t iStrm = 0; iStrm < HDA_MAX_STREAMS; ++iStrm) + { + if (HDA_STREAM_REG(pThis, STS, iStrm) & HDA_STREAM_REG(pThis, CTL, iStrm) & (HDA_SDCTL_DEIE | HDA_SDCTL_FEIE | HDA_SDCTL_IOCE)) + { + Log3Func(("[SD%d] interrupt status set\n", iStrm)); + intSts |= RT_BIT(iStrm); + } + } + + if (intSts) + intSts |= HDA_INTSTS_GIS; /* Set the Global Interrupt Status (GIS). */ + + Log3Func(("-> 0x%x\n", intSts)); + + return intSts; +} + +#ifdef IN_RING3 + +/** + * Converts an HDA stream's SDFMT register into a given PCM properties structure. + * + * @return IPRT status code. + * @param u16SDFMT The HDA stream's SDFMT value to convert. + * @param pProps PCM properties structure to hold converted result on success. + */ +int hdaR3SDFMTToPCMProps(uint16_t u16SDFMT, PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, VERR_INVALID_POINTER); + +# define EXTRACT_VALUE(v, mask, shift) ((v & ((mask) << (shift))) >> (shift)) + + int rc = VINF_SUCCESS; + + uint32_t u32Hz = EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BASE_RATE_MASK, HDA_SDFMT_BASE_RATE_SHIFT) + ? 44100 : 48000; + uint32_t u32HzMult = 1; + uint32_t u32HzDiv = 1; + + switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_MULT_MASK, HDA_SDFMT_MULT_SHIFT)) + { + case 0: u32HzMult = 1; break; + case 1: u32HzMult = 2; break; + case 2: u32HzMult = 3; break; + case 3: u32HzMult = 4; break; + default: + LogFunc(("Unsupported multiplier %x\n", + EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_MULT_MASK, HDA_SDFMT_MULT_SHIFT))); + rc = VERR_NOT_SUPPORTED; + break; + } + switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_DIV_MASK, HDA_SDFMT_DIV_SHIFT)) + { + case 0: u32HzDiv = 1; break; + case 1: u32HzDiv = 2; break; + case 2: u32HzDiv = 3; break; + case 3: u32HzDiv = 4; break; + case 4: u32HzDiv = 5; break; + case 5: u32HzDiv = 6; break; + case 6: u32HzDiv = 7; break; + case 7: u32HzDiv = 8; break; + default: + LogFunc(("Unsupported divisor %x\n", + EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_DIV_MASK, HDA_SDFMT_DIV_SHIFT))); + rc = VERR_NOT_SUPPORTED; + break; + } + + uint8_t cBytes = 0; + switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BITS_MASK, HDA_SDFMT_BITS_SHIFT)) + { + case 0: + cBytes = 1; + break; + case 1: + cBytes = 2; + break; + case 4: + cBytes = 4; + break; + default: + AssertMsgFailed(("Unsupported bits per sample %x\n", + EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BITS_MASK, HDA_SDFMT_BITS_SHIFT))); + rc = VERR_NOT_SUPPORTED; + break; + } + + if (RT_SUCCESS(rc)) + { + RT_BZERO(pProps, sizeof(PDMAUDIOPCMPROPS)); + + pProps->cBytes = cBytes; + pProps->fSigned = true; + pProps->cChannels = (u16SDFMT & 0xf) + 1; + pProps->uHz = u32Hz * u32HzMult / u32HzDiv; + pProps->cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pProps->cBytes, pProps->cChannels); + } + +# undef EXTRACT_VALUE + return rc; +} + +# ifdef LOG_ENABLED +void hdaR3BDLEDumpAll(PHDASTATE pThis, uint64_t u64BDLBase, uint16_t cBDLE) +{ + LogFlowFunc(("BDLEs @ 0x%x (%RU16):\n", u64BDLBase, cBDLE)); + if (!u64BDLBase) + return; + + uint32_t cbBDLE = 0; + for (uint16_t i = 0; i < cBDLE; i++) + { + HDABDLEDESC bd; + PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), u64BDLBase + i * sizeof(HDABDLEDESC), &bd, sizeof(bd)); + + LogFunc(("\t#%03d BDLE(adr:0x%llx, size:%RU32, ioc:%RTbool)\n", + i, bd.u64BufAddr, bd.u32BufSize, bd.fFlags & HDA_BDLE_FLAG_IOC)); + + cbBDLE += bd.u32BufSize; + } + + LogFlowFunc(("Total: %RU32 bytes\n", cbBDLE)); + + if (!pThis->u64DPBase) /* No DMA base given? Bail out. */ + return; + + LogFlowFunc(("DMA counters:\n")); + + for (int i = 0; i < cBDLE; i++) + { + uint32_t uDMACnt; + PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), (pThis->u64DPBase & DPBASE_ADDR_MASK) + (i * 2 * sizeof(uint32_t)), + &uDMACnt, sizeof(uDMACnt)); + + LogFlowFunc(("\t#%03d DMA @ 0x%x\n", i , uDMACnt)); + } +} +# endif /* LOG_ENABLED */ + +/** + * Fetches a Bundle Descriptor List Entry (BDLE) from the DMA engine. + * + * @param pThis Pointer to HDA state. + * @param pBDLE Where to store the fetched result. + * @param u64BaseDMA Address base of DMA engine to use. + * @param u16Entry BDLE entry to fetch. + */ +int hdaR3BDLEFetch(PHDASTATE pThis, PHDABDLE pBDLE, uint64_t u64BaseDMA, uint16_t u16Entry) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pBDLE, VERR_INVALID_POINTER); + AssertReturn(u64BaseDMA, VERR_INVALID_PARAMETER); + + if (!u64BaseDMA) + { + LogRel2(("HDA: Unable to fetch BDLE #%RU16 - no base DMA address set (yet)\n", u16Entry)); + return VERR_NOT_FOUND; + } + /** @todo Compare u16Entry with LVI. */ + + int rc = PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), u64BaseDMA + (u16Entry * sizeof(HDABDLEDESC)), + &pBDLE->Desc, sizeof(pBDLE->Desc)); + + if (RT_SUCCESS(rc)) + { + /* Reset internal state. */ + RT_ZERO(pBDLE->State); + pBDLE->State.u32BDLIndex = u16Entry; + } + + Log3Func(("Entry #%d @ 0x%x: %R[bdle], rc=%Rrc\n", u16Entry, u64BaseDMA + (u16Entry * sizeof(HDABDLEDESC)), pBDLE, rc)); + + + return VINF_SUCCESS; +} + +/** + * Tells whether a given BDLE is complete or not. + * + * @return true if BDLE is complete, false if not. + * @param pBDLE BDLE to retrieve status for. + */ +bool hdaR3BDLEIsComplete(PHDABDLE pBDLE) +{ + bool fIsComplete = false; + + if ( !pBDLE->Desc.u32BufSize /* There can be BDLEs with 0 size. */ + || (pBDLE->State.u32BufOff >= pBDLE->Desc.u32BufSize)) + { + Assert(pBDLE->State.u32BufOff == pBDLE->Desc.u32BufSize); + fIsComplete = true; + } + + Log3Func(("%R[bdle] => %s\n", pBDLE, fIsComplete ? "COMPLETE" : "INCOMPLETE")); + + return fIsComplete; +} + +/** + * Tells whether a given BDLE needs an interrupt or not. + * + * @return true if BDLE needs an interrupt, false if not. + * @param pBDLE BDLE to retrieve status for. + */ +bool hdaR3BDLENeedsInterrupt(PHDABDLE pBDLE) +{ + return (pBDLE->Desc.fFlags & HDA_BDLE_FLAG_IOC); +} + +/** + * Sets the virtual device timer to a new expiration time. + * + * @returns Whether the new expiration time was set or not. + * @param pThis HDA state. + * @param pStream HDA stream to set timer for. + * @param tsExpire New (virtual) expiration time to set. + * @param fForce Whether to force setting the expiration time or not. + * + * @remark This function takes all active HDA streams and their + * current timing into account. This is needed to make sure + * that all streams can match their needed timing. + * + * To achieve this, the earliest (lowest) timestamp of all + * active streams found will be used for the next scheduling slot. + * + * Forcing a new expiration time will override the above mechanism. + */ +bool hdaR3TimerSet(PHDASTATE pThis, PHDASTREAM pStream, uint64_t tsExpire, bool fForce) +{ + AssertPtrReturn(pThis, false); + AssertPtrReturn(pStream, false); + + uint64_t tsExpireMin = tsExpire; + + if (!fForce) + { + if (hdaR3StreamTransferIsScheduled(pStream)) + tsExpireMin = RT_MIN(tsExpireMin, hdaR3StreamTransferGetNext(pStream)); + } + + AssertPtr(pThis->pTimer[pStream->u8SD]); + + const uint64_t tsNow = TMTimerGet(pThis->pTimer[pStream->u8SD]); + + /* + * Make sure to not go backwards in time, as this will assert in TMTimerSet(). + * This in theory could happen in hdaR3StreamTransferGetNext() from above. + */ + if (tsExpireMin < tsNow) + tsExpireMin = tsNow; + + int rc = TMTimerSet(pThis->pTimer[pStream->u8SD], tsExpireMin); + AssertRC(rc); + + return RT_SUCCESS(rc); +} + +#endif /* IN_RING3 */ diff --git a/src/VBox/Devices/Audio/DevHDACommon.h b/src/VBox/Devices/Audio/DevHDACommon.h new file mode 100644 index 00000000..c667d57a --- /dev/null +++ b/src/VBox/Devices/Audio/DevHDACommon.h @@ -0,0 +1,658 @@ +/* $Id: DevHDACommon.h $ */ +/** @file + * DevHDACommon.h - Shared HDA device defines / functions. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_DevHDACommon_h +#define VBOX_INCLUDED_SRC_Audio_DevHDACommon_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "AudioMixer.h" +#include <VBox/log.h> /* LOG_ENABLED */ + +/** See 302349 p 6.2. */ +typedef struct HDAREGDESC +{ + /** Register offset in the register space. */ + uint32_t offset; + /** Size in bytes. Registers of size > 4 are in fact tables. */ + uint32_t size; + /** Readable bits. */ + uint32_t readable; + /** Writable bits. */ + uint32_t writable; + /** Register descriptor (RD) flags of type HDA_RD_FLAG_. + * These are used to specify the handling (read/write) + * policy of the register. */ + uint32_t fFlags; + /** Read callback. */ + int (*pfnRead)(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value); + /** Write callback. */ + int (*pfnWrite)(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value); + /** Index into the register storage array. */ + uint32_t mem_idx; + /** Abbreviated name. */ + const char *abbrev; + /** Descripton. */ + const char *desc; +} HDAREGDESC, *PHDAREGDESC; + +/** + * HDA register aliases (HDA spec 3.3.45). + * @remarks Sorted by offReg. + */ +typedef struct HDAREGALIAS +{ + /** The alias register offset. */ + uint32_t offReg; + /** The register index. */ + int idxAlias; +} HDAREGALIAS, *PHDAREGALIAS; + +/** + * At the moment we support 4 input + 4 output streams max, which is 8 in total. + * Bidirectional streams are currently *not* supported. + * + * Note: When changing any of those values, be prepared for some saved state + * fixups / trouble! + */ +#define HDA_MAX_SDI 4 +#define HDA_MAX_SDO 4 +#define HDA_MAX_STREAMS (HDA_MAX_SDI + HDA_MAX_SDO) +AssertCompile(HDA_MAX_SDI <= HDA_MAX_SDO); + +/** Number of general registers. */ +#define HDA_NUM_GENERAL_REGS 34 +/** Number of total registers in the HDA's register map. */ +#define HDA_NUM_REGS (HDA_NUM_GENERAL_REGS + (HDA_MAX_STREAMS * 10 /* Each stream descriptor has 10 registers */)) +/** Total number of stream tags (channels). Index 0 is reserved / invalid. */ +#define HDA_MAX_TAGS 16 + +/** + * ICH6 datasheet defines limits for FIFOS registers (18.2.39). + * Formula: size - 1 + * Other values not listed are not supported. + */ +/** Maximum FIFO size (in bytes). */ +#define HDA_FIFO_MAX 256 + +/** Default timer frequency (in Hz). + * + * Lowering this value can ask for trouble, as backends then can run + * into data underruns. + * + * Note: For handling surround setups (e.g. 5.1 speaker setups) we need + * a higher Hz rate, as the device emulation otherwise will come into + * timing trouble, making the output (DMA reads) crackling. */ +#define HDA_TIMER_HZ_DEFAULT 100 + +/** Default position adjustment (in audio samples). + * + * For snd_hda_intel (Linux guests), the first BDL entry always is being used as + * so-called BDL adjustment, which can vary, and is being used for chipsets which + * misbehave and/or are incorrectly implemented. + * + * The BDL adjustment entry *always* has the IOC (Interrupt on Completion) bit set. + * + * For Intel Baytrail / Braswell implementations the BDL default adjustment is 32 frames, whereas + * for ICH / PCH it's only one (1) frame. + * + * See default_bdl_pos_adj() and snd_hdac_stream_setup_periods() for more information. + * + * By default we apply some simple heuristics in hdaStreamInit(). + */ +#define HDA_POS_ADJUST_DEFAULT 0 + +/** HDA's (fixed) audio frame size in bytes. + * We only support 16-bit stereo frames at the moment. */ +#define HDA_FRAME_SIZE_DEFAULT 4 + +/** Offset of the SD0 register map. */ +#define HDA_REG_DESC_SD0_BASE 0x80 + +/** Turn a short global register name into an memory index and a stringized name. */ +#define HDA_REG_IDX(abbrev) HDA_MEM_IND_NAME(abbrev), #abbrev + +/** Turns a short stream register name into an memory index and a stringized name. */ +#define HDA_REG_IDX_STRM(reg, suff) HDA_MEM_IND_NAME(reg ## suff), #reg #suff + +/** Same as above for a register *not* stored in memory. */ +#define HDA_REG_IDX_NOMEM(abbrev) 0, #abbrev + +extern const HDAREGDESC g_aHdaRegMap[HDA_NUM_REGS]; + +/** + * NB: Register values stored in memory (au32Regs[]) are indexed through + * the HDA_RMX_xxx macros (also HDA_MEM_IND_NAME()). On the other hand, the + * register descriptors in g_aHdaRegMap[] are indexed through the + * HDA_REG_xxx macros (also HDA_REG_IND_NAME()). + * + * The au32Regs[] layout is kept unchanged for saved state + * compatibility. + */ + +/* Registers */ +#define HDA_REG_IND_NAME(x) HDA_REG_##x +#define HDA_MEM_IND_NAME(x) HDA_RMX_##x +#define HDA_REG_IND(pThis, x) ((pThis)->au32Regs[g_aHdaRegMap[x].mem_idx]) +#define HDA_REG(pThis, x) (HDA_REG_IND((pThis), HDA_REG_IND_NAME(x))) + + +#define HDA_REG_GCAP 0 /* Range 0x00 - 0x01 */ +#define HDA_RMX_GCAP 0 +/** + * GCAP HDASpec 3.3.2 This macro encodes the following information about HDA in a compact manner: + * + * oss (15:12) - Number of output streams supported. + * iss (11:8) - Number of input streams supported. + * bss (7:3) - Number of bidirectional streams supported. + * bds (2:1) - Number of serial data out (SDO) signals supported. + * b64sup (0) - 64 bit addressing supported. + */ +#define HDA_MAKE_GCAP(oss, iss, bss, bds, b64sup) \ + ( (((oss) & 0xF) << 12) \ + | (((iss) & 0xF) << 8) \ + | (((bss) & 0x1F) << 3) \ + | (((bds) & 0x3) << 2) \ + | ((b64sup) & 1)) + +#define HDA_REG_VMIN 1 /* 0x02 */ +#define HDA_RMX_VMIN 1 + +#define HDA_REG_VMAJ 2 /* 0x03 */ +#define HDA_RMX_VMAJ 2 + +#define HDA_REG_OUTPAY 3 /* 0x04-0x05 */ +#define HDA_RMX_OUTPAY 3 + +#define HDA_REG_INPAY 4 /* 0x06-0x07 */ +#define HDA_RMX_INPAY 4 + +#define HDA_REG_GCTL 5 /* 0x08-0x0B */ +#define HDA_RMX_GCTL 5 +#define HDA_GCTL_UNSOL RT_BIT(8) /* Accept Unsolicited Response Enable */ +#define HDA_GCTL_FCNTRL RT_BIT(1) /* Flush Control */ +#define HDA_GCTL_CRST RT_BIT(0) /* Controller Reset */ + +#define HDA_REG_WAKEEN 6 /* 0x0C */ +#define HDA_RMX_WAKEEN 6 + +#define HDA_REG_STATESTS 7 /* 0x0E */ +#define HDA_RMX_STATESTS 7 +#define HDA_STATESTS_SCSF_MASK 0x7 /* State Change Status Flags (6.2.8). */ + +#define HDA_REG_GSTS 8 /* 0x10-0x11*/ +#define HDA_RMX_GSTS 8 +#define HDA_GSTS_FSTS RT_BIT(1) /* Flush Status */ + +#define HDA_REG_OUTSTRMPAY 9 /* 0x18 */ +#define HDA_RMX_OUTSTRMPAY 112 + +#define HDA_REG_INSTRMPAY 10 /* 0x1a */ +#define HDA_RMX_INSTRMPAY 113 + +#define HDA_REG_INTCTL 11 /* 0x20 */ +#define HDA_RMX_INTCTL 9 +#define HDA_INTCTL_GIE RT_BIT(31) /* Global Interrupt Enable */ +#define HDA_INTCTL_CIE RT_BIT(30) /* Controller Interrupt Enable */ +/** Bits 0-29 correspond to streams 0-29. */ +#define HDA_STRMINT_MASK 0xFF /* Streams 0-7 implemented. Applies to INTCTL and INTSTS. */ + +#define HDA_REG_INTSTS 12 /* 0x24 */ +#define HDA_RMX_INTSTS 10 +#define HDA_INTSTS_GIS RT_BIT(31) /* Global Interrupt Status */ +#define HDA_INTSTS_CIS RT_BIT(30) /* Controller Interrupt Status */ + +#define HDA_REG_WALCLK 13 /* 0x30 */ +/**NB: HDA_RMX_WALCLK is not defined because the register is not stored in memory. */ + +/** + * Note: The HDA specification defines a SSYNC register at offset 0x38. The + * ICH6/ICH9 datahseet defines SSYNC at offset 0x34. The Linux HDA driver matches + * the datasheet. + */ +#define HDA_REG_SSYNC 14 /* 0x34 */ +#define HDA_RMX_SSYNC 12 + +#define HDA_REG_CORBLBASE 15 /* 0x40 */ +#define HDA_RMX_CORBLBASE 13 + +#define HDA_REG_CORBUBASE 16 /* 0x44 */ +#define HDA_RMX_CORBUBASE 14 + +#define HDA_REG_CORBWP 17 /* 0x48 */ +#define HDA_RMX_CORBWP 15 + +#define HDA_REG_CORBRP 18 /* 0x4A */ +#define HDA_RMX_CORBRP 16 +#define HDA_CORBRP_RST RT_BIT(15) /* CORB Read Pointer Reset */ + +#define HDA_REG_CORBCTL 19 /* 0x4C */ +#define HDA_RMX_CORBCTL 17 +#define HDA_CORBCTL_DMA RT_BIT(1) /* Enable CORB DMA Engine */ +#define HDA_CORBCTL_CMEIE RT_BIT(0) /* CORB Memory Error Interrupt Enable */ + +#define HDA_REG_CORBSTS 20 /* 0x4D */ +#define HDA_RMX_CORBSTS 18 + +#define HDA_REG_CORBSIZE 21 /* 0x4E */ +#define HDA_RMX_CORBSIZE 19 +#define HDA_CORBSIZE_SZ_CAP 0xF0 +#define HDA_CORBSIZE_SZ 0x3 + +/** Number of CORB buffer entries. */ +#define HDA_CORB_SIZE 256 +/** CORB element size (in bytes). */ +#define HDA_CORB_ELEMENT_SIZE 4 +/** Number of RIRB buffer entries. */ +#define HDA_RIRB_SIZE 256 +/** RIRB element size (in bytes). */ +#define HDA_RIRB_ELEMENT_SIZE 8 + +#define HDA_REG_RIRBLBASE 22 /* 0x50 */ +#define HDA_RMX_RIRBLBASE 20 + +#define HDA_REG_RIRBUBASE 23 /* 0x54 */ +#define HDA_RMX_RIRBUBASE 21 + +#define HDA_REG_RIRBWP 24 /* 0x58 */ +#define HDA_RMX_RIRBWP 22 +#define HDA_RIRBWP_RST RT_BIT(15) /* RIRB Write Pointer Reset */ + +#define HDA_REG_RINTCNT 25 /* 0x5A */ +#define HDA_RMX_RINTCNT 23 + +/** Maximum number of Response Interrupts. */ +#define HDA_MAX_RINTCNT 256 + +#define HDA_REG_RIRBCTL 26 /* 0x5C */ +#define HDA_RMX_RIRBCTL 24 +#define HDA_RIRBCTL_ROIC RT_BIT(2) /* Response Overrun Interrupt Control */ +#define HDA_RIRBCTL_RDMAEN RT_BIT(1) /* RIRB DMA Enable */ +#define HDA_RIRBCTL_RINTCTL RT_BIT(0) /* Response Interrupt Control */ + +#define HDA_REG_RIRBSTS 27 /* 0x5D */ +#define HDA_RMX_RIRBSTS 25 +#define HDA_RIRBSTS_RIRBOIS RT_BIT(2) /* Response Overrun Interrupt Status */ +#define HDA_RIRBSTS_RINTFL RT_BIT(0) /* Response Interrupt Flag */ + +#define HDA_REG_RIRBSIZE 28 /* 0x5E */ +#define HDA_RMX_RIRBSIZE 26 + +#define HDA_REG_IC 29 /* 0x60 */ +#define HDA_RMX_IC 27 + +#define HDA_REG_IR 30 /* 0x64 */ +#define HDA_RMX_IR 28 + +#define HDA_REG_IRS 31 /* 0x68 */ +#define HDA_RMX_IRS 29 +#define HDA_IRS_IRV RT_BIT(1) /* Immediate Result Valid */ +#define HDA_IRS_ICB RT_BIT(0) /* Immediate Command Busy */ + +#define HDA_REG_DPLBASE 32 /* 0x70 */ +#define HDA_RMX_DPLBASE 30 + +#define HDA_REG_DPUBASE 33 /* 0x74 */ +#define HDA_RMX_DPUBASE 31 + +#define DPBASE_ADDR_MASK (~(uint64_t)0x7f) + +#define HDA_STREAM_REG_DEF(name, num) (HDA_REG_SD##num##name) +#define HDA_STREAM_RMX_DEF(name, num) (HDA_RMX_SD##num##name) +/** Note: sdnum here _MUST_ be stream reg number [0,7]. */ +#define HDA_STREAM_REG(pThis, name, sdnum) (HDA_REG_IND((pThis), HDA_REG_SD0##name + (sdnum) * 10)) + +#define HDA_SD_NUM_FROM_REG(pThis, func, reg) ((reg - HDA_STREAM_REG_DEF(func, 0)) / 10) + +/** @todo Condense marcos! */ + +#define HDA_REG_SD0CTL HDA_NUM_GENERAL_REGS /* 0x80; other streams offset by 0x20 */ +#define HDA_RMX_SD0CTL 32 +#define HDA_RMX_SD1CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 10) +#define HDA_RMX_SD2CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 20) +#define HDA_RMX_SD3CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 30) +#define HDA_RMX_SD4CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 40) +#define HDA_RMX_SD5CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 50) +#define HDA_RMX_SD6CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 60) +#define HDA_RMX_SD7CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 70) + +#define HDA_SDCTL_NUM_MASK 0xF +#define HDA_SDCTL_NUM_SHIFT 20 +#define HDA_SDCTL_DIR RT_BIT(19) /* Direction (Bidirectional streams only!) */ +#define HDA_SDCTL_TP RT_BIT(18) /* Traffic Priority (PCI Express) */ +#define HDA_SDCTL_STRIPE_MASK 0x3 +#define HDA_SDCTL_STRIPE_SHIFT 16 +#define HDA_SDCTL_DEIE RT_BIT(4) /* Descriptor Error Interrupt Enable */ +#define HDA_SDCTL_FEIE RT_BIT(3) /* FIFO Error Interrupt Enable */ +#define HDA_SDCTL_IOCE RT_BIT(2) /* Interrupt On Completion Enable */ +#define HDA_SDCTL_RUN RT_BIT(1) /* Stream Run */ +#define HDA_SDCTL_SRST RT_BIT(0) /* Stream Reset */ + +#define HDA_REG_SD0STS 35 /* 0x83; other streams offset by 0x20 */ +#define HDA_RMX_SD0STS 33 +#define HDA_RMX_SD1STS (HDA_STREAM_RMX_DEF(STS, 0) + 10) +#define HDA_RMX_SD2STS (HDA_STREAM_RMX_DEF(STS, 0) + 20) +#define HDA_RMX_SD3STS (HDA_STREAM_RMX_DEF(STS, 0) + 30) +#define HDA_RMX_SD4STS (HDA_STREAM_RMX_DEF(STS, 0) + 40) +#define HDA_RMX_SD5STS (HDA_STREAM_RMX_DEF(STS, 0) + 50) +#define HDA_RMX_SD6STS (HDA_STREAM_RMX_DEF(STS, 0) + 60) +#define HDA_RMX_SD7STS (HDA_STREAM_RMX_DEF(STS, 0) + 70) + +#define HDA_SDSTS_FIFORDY RT_BIT(5) /* FIFO Ready */ +#define HDA_SDSTS_DESE RT_BIT(4) /* Descriptor Error */ +#define HDA_SDSTS_FIFOE RT_BIT(3) /* FIFO Error */ +#define HDA_SDSTS_BCIS RT_BIT(2) /* Buffer Completion Interrupt Status */ + +#define HDA_REG_SD0LPIB 36 /* 0x84; other streams offset by 0x20 */ +#define HDA_REG_SD1LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 10) /* 0xA4 */ +#define HDA_REG_SD2LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 20) /* 0xC4 */ +#define HDA_REG_SD3LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 30) /* 0xE4 */ +#define HDA_REG_SD4LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 40) /* 0x104 */ +#define HDA_REG_SD5LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 50) /* 0x124 */ +#define HDA_REG_SD6LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 60) /* 0x144 */ +#define HDA_REG_SD7LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 70) /* 0x164 */ +#define HDA_RMX_SD0LPIB 34 +#define HDA_RMX_SD1LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 10) +#define HDA_RMX_SD2LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 20) +#define HDA_RMX_SD3LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 30) +#define HDA_RMX_SD4LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 40) +#define HDA_RMX_SD5LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 50) +#define HDA_RMX_SD6LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 60) +#define HDA_RMX_SD7LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 70) + +#define HDA_REG_SD0CBL 37 /* 0x88; other streams offset by 0x20 */ +#define HDA_RMX_SD0CBL 35 +#define HDA_RMX_SD1CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 10) +#define HDA_RMX_SD2CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 20) +#define HDA_RMX_SD3CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 30) +#define HDA_RMX_SD4CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 40) +#define HDA_RMX_SD5CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 50) +#define HDA_RMX_SD6CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 60) +#define HDA_RMX_SD7CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 70) + +#define HDA_REG_SD0LVI 38 /* 0x8C; other streams offset by 0x20 */ +#define HDA_RMX_SD0LVI 36 +#define HDA_RMX_SD1LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 10) +#define HDA_RMX_SD2LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 20) +#define HDA_RMX_SD3LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 30) +#define HDA_RMX_SD4LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 40) +#define HDA_RMX_SD5LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 50) +#define HDA_RMX_SD6LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 60) +#define HDA_RMX_SD7LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 70) + +#define HDA_REG_SD0FIFOW 39 /* 0x8E; other streams offset by 0x20 */ +#define HDA_RMX_SD0FIFOW 37 +#define HDA_RMX_SD1FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 10) +#define HDA_RMX_SD2FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 20) +#define HDA_RMX_SD3FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 30) +#define HDA_RMX_SD4FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 40) +#define HDA_RMX_SD5FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 50) +#define HDA_RMX_SD6FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 60) +#define HDA_RMX_SD7FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 70) + +/* + * ICH6 datasheet defined limits for FIFOW values (18.2.38). + */ +#define HDA_SDFIFOW_8B 0x2 +#define HDA_SDFIFOW_16B 0x3 +#define HDA_SDFIFOW_32B 0x4 + +#define HDA_REG_SD0FIFOS 40 /* 0x90; other streams offset by 0x20 */ +#define HDA_RMX_SD0FIFOS 38 +#define HDA_RMX_SD1FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 10) +#define HDA_RMX_SD2FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 20) +#define HDA_RMX_SD3FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 30) +#define HDA_RMX_SD4FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 40) +#define HDA_RMX_SD5FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 50) +#define HDA_RMX_SD6FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 60) +#define HDA_RMX_SD7FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 70) + +#define HDA_SDIFIFO_120B 0x77 /* 8-, 16-, 20-, 24-, 32-bit Input Streams */ +#define HDA_SDIFIFO_160B 0x9F /* 20-, 24-bit Input Streams Streams */ + +#define HDA_SDOFIFO_16B 0x0F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ +#define HDA_SDOFIFO_32B 0x1F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ +#define HDA_SDOFIFO_64B 0x3F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ +#define HDA_SDOFIFO_128B 0x7F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ +#define HDA_SDOFIFO_192B 0xBF /* 8-, 16-, 20-, 24-, 32-bit Output Streams */ +#define HDA_SDOFIFO_256B 0xFF /* 20-, 24-bit Output Streams */ + +#define HDA_REG_SD0FMT 41 /* 0x92; other streams offset by 0x20 */ +#define HDA_RMX_SD0FMT 39 +#define HDA_RMX_SD1FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 10) +#define HDA_RMX_SD2FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 20) +#define HDA_RMX_SD3FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 30) +#define HDA_RMX_SD4FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 40) +#define HDA_RMX_SD5FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 50) +#define HDA_RMX_SD6FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 60) +#define HDA_RMX_SD7FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 70) + +#define HDA_REG_SD0BDPL 42 /* 0x98; other streams offset by 0x20 */ +#define HDA_RMX_SD0BDPL 40 +#define HDA_RMX_SD1BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 10) +#define HDA_RMX_SD2BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 20) +#define HDA_RMX_SD3BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 30) +#define HDA_RMX_SD4BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 40) +#define HDA_RMX_SD5BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 50) +#define HDA_RMX_SD6BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 60) +#define HDA_RMX_SD7BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 70) + +#define HDA_REG_SD0BDPU 43 /* 0x9C; other streams offset by 0x20 */ +#define HDA_RMX_SD0BDPU 41 +#define HDA_RMX_SD1BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 10) +#define HDA_RMX_SD2BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 20) +#define HDA_RMX_SD3BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 30) +#define HDA_RMX_SD4BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 40) +#define HDA_RMX_SD5BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 50) +#define HDA_RMX_SD6BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 60) +#define HDA_RMX_SD7BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 70) + +#define HDA_CODEC_CAD_SHIFT 28 +/** Encodes the (required) LUN into a codec command. */ +#define HDA_CODEC_CMD(cmd, lun) ((cmd) | (lun << HDA_CODEC_CAD_SHIFT)) + +#define HDA_SDFMT_NON_PCM_SHIFT 15 +#define HDA_SDFMT_NON_PCM_MASK 0x1 +#define HDA_SDFMT_BASE_RATE_SHIFT 14 +#define HDA_SDFMT_BASE_RATE_MASK 0x1 +#define HDA_SDFMT_MULT_SHIFT 11 +#define HDA_SDFMT_MULT_MASK 0x7 +#define HDA_SDFMT_DIV_SHIFT 8 +#define HDA_SDFMT_DIV_MASK 0x7 +#define HDA_SDFMT_BITS_SHIFT 4 +#define HDA_SDFMT_BITS_MASK 0x7 +#define HDA_SDFMT_CHANNELS_MASK 0xF + +#define HDA_SDFMT_TYPE RT_BIT(15) +#define HDA_SDFMT_TYPE_PCM (0) +#define HDA_SDFMT_TYPE_NON_PCM (1) + +#define HDA_SDFMT_BASE RT_BIT(14) +#define HDA_SDFMT_BASE_48KHZ (0) +#define HDA_SDFMT_BASE_44KHZ (1) + +#define HDA_SDFMT_MULT_1X (0) +#define HDA_SDFMT_MULT_2X (1) +#define HDA_SDFMT_MULT_3X (2) +#define HDA_SDFMT_MULT_4X (3) + +#define HDA_SDFMT_DIV_1X (0) +#define HDA_SDFMT_DIV_2X (1) +#define HDA_SDFMT_DIV_3X (2) +#define HDA_SDFMT_DIV_4X (3) +#define HDA_SDFMT_DIV_5X (4) +#define HDA_SDFMT_DIV_6X (5) +#define HDA_SDFMT_DIV_7X (6) +#define HDA_SDFMT_DIV_8X (7) + +#define HDA_SDFMT_8_BIT (0) +#define HDA_SDFMT_16_BIT (1) +#define HDA_SDFMT_20_BIT (2) +#define HDA_SDFMT_24_BIT (3) +#define HDA_SDFMT_32_BIT (4) + +#define HDA_SDFMT_CHAN_MONO (0) +#define HDA_SDFMT_CHAN_STEREO (1) + +/** Emits a SDnFMT register format. + * Also being used in the codec's converter format. */ +#define HDA_SDFMT_MAKE(_afNonPCM, _aBaseRate, _aMult, _aDiv, _aBits, _aChan) \ + ( (((_afNonPCM) & HDA_SDFMT_NON_PCM_MASK) << HDA_SDFMT_NON_PCM_SHIFT) \ + | (((_aBaseRate) & HDA_SDFMT_BASE_RATE_MASK) << HDA_SDFMT_BASE_RATE_SHIFT) \ + | (((_aMult) & HDA_SDFMT_MULT_MASK) << HDA_SDFMT_MULT_SHIFT) \ + | (((_aDiv) & HDA_SDFMT_DIV_MASK) << HDA_SDFMT_DIV_SHIFT) \ + | (((_aBits) & HDA_SDFMT_BITS_MASK) << HDA_SDFMT_BITS_SHIFT) \ + | ( (_aChan) & HDA_SDFMT_CHANNELS_MASK)) + +/** Interrupt on completion (IOC) flag. */ +#define HDA_BDLE_FLAG_IOC RT_BIT(0) + + + +/** The HDA controller. */ +typedef struct HDASTATE *PHDASTATE; +/** The HDA stream. */ +typedef struct HDASTREAM *PHDASTREAM; + +typedef struct HDAMIXERSINK *PHDAMIXERSINK; + + +/** + * Internal state of a Buffer Descriptor List Entry (BDLE), + * needed to keep track of the data needed for the actual device + * emulation. + */ +typedef struct HDABDLESTATE +{ + /** Own index within the BDL (Buffer Descriptor List). */ + uint32_t u32BDLIndex; + /** Number of bytes below the stream's FIFO watermark (SDFIFOW). + * Used to check if we need fill up the FIFO again. */ + uint32_t cbBelowFIFOW; + /** Current offset in DMA buffer (in bytes).*/ + uint32_t u32BufOff; + uint32_t Padding; +} HDABDLESTATE, *PHDABDLESTATE; + +/** + * BDL description structure. + * Do not touch this, as this must match to the HDA specs. + */ +typedef struct HDABDLEDESC +{ + /** Starting address of the actual buffer. Must be 128-bit aligned. */ + uint64_t u64BufAddr; + /** Size of the actual buffer (in bytes). */ + uint32_t u32BufSize; + /** Bit 0: Interrupt on completion; the controller will generate + * an interrupt when the last byte of the buffer has been + * fetched by the DMA engine. + * + * Rest is reserved for further use and must be 0. */ + uint32_t fFlags; +} HDABDLEDESC, *PHDABDLEDESC; +AssertCompileSize(HDABDLEDESC, 16); /* Always 16 byte. Also must be aligned on 128-byte boundary. */ + +/** + * Buffer Descriptor List Entry (BDLE) (3.6.3). + */ +typedef struct HDABDLE +{ + /** The actual BDL description. */ + HDABDLEDESC Desc; + /** Internal state of this BDLE. + * Not part of the actual BDLE registers. */ + HDABDLESTATE State; +} HDABDLE; +AssertCompileSizeAlignment(HDABDLE, 8); +/** Pointer to a buffer descriptor list entry (BDLE). */ +typedef HDABDLE *PHDABDLE; + +/** @name Object lookup functions. + * @{ + */ +#ifdef IN_RING3 +PHDAMIXERSINK hdaR3GetDefaultSink(PHDASTATE pThis, uint8_t uSD); +#endif +PDMAUDIODIR hdaGetDirFromSD(uint8_t uSD); +PHDASTREAM hdaGetStreamFromSD(PHDASTATE pThis, uint8_t uSD); +#ifdef IN_RING3 +PHDASTREAM hdaR3GetStreamFromSink(PHDASTATE pThis, PHDAMIXERSINK pSink); +#endif +/** @} */ + +/** @name Interrupt functions. + * @{ + */ +#ifdef LOG_ENABLED +int hdaProcessInterrupt(PHDASTATE pThis, const char *pszSource); +#else +int hdaProcessInterrupt(PHDASTATE pThis); +#endif +/** @} */ + +/** @name Wall clock (WALCLK) functions. + * @{ + */ +uint64_t hdaWalClkGetCurrent(PHDASTATE pThis); +#ifdef IN_RING3 +bool hdaR3WalClkSet(PHDASTATE pThis, uint64_t u64WalClk, bool fForce); +#endif +/** @} */ + +/** @name DMA utility functions. + * @{ + */ +#ifdef IN_RING3 +int hdaR3DMARead(PHDASTATE pThis, PHDASTREAM pStream, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead); +int hdaR3DMAWrite(PHDASTATE pThis, PHDASTREAM pStream, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten); +#endif +/** @} */ + +/** @name Register functions. + * @{ + */ +uint32_t hdaGetINTSTS(PHDASTATE pThis); +#ifdef IN_RING3 +int hdaR3SDFMTToPCMProps(uint16_t u16SDFMT, PPDMAUDIOPCMPROPS pProps); +#endif /* IN_RING3 */ +/** @} */ + +/** @name BDLE (Buffer Descriptor List Entry) functions. + * @{ + */ +#ifdef IN_RING3 +# ifdef LOG_ENABLED +void hdaR3BDLEDumpAll(PHDASTATE pThis, uint64_t u64BDLBase, uint16_t cBDLE); +# endif +int hdaR3BDLEFetch(PHDASTATE pThis, PHDABDLE pBDLE, uint64_t u64BaseDMA, uint16_t u16Entry); +bool hdaR3BDLEIsComplete(PHDABDLE pBDLE); +bool hdaR3BDLENeedsInterrupt(PHDABDLE pBDLE); +#endif /* IN_RING3 */ +/** @} */ + +/** @name Device timer functions. + * @{ + */ +#ifdef IN_RING3 +bool hdaR3TimerSet(PHDASTATE pThis, PHDASTREAM pStream, uint64_t u64Expire, bool fForce); +#endif /* IN_RING3 */ +/** @} */ + +#endif /* !VBOX_INCLUDED_SRC_Audio_DevHDACommon_h */ + diff --git a/src/VBox/Devices/Audio/DevIchAc97.cpp b/src/VBox/Devices/Audio/DevIchAc97.cpp new file mode 100644 index 00000000..6eee6f7a --- /dev/null +++ b/src/VBox/Devices/Audio/DevIchAc97.cpp @@ -0,0 +1,4575 @@ +/* $Id: DevIchAc97.cpp $ */ +/** @file + * DevIchAc97 - VBox ICH AC97 Audio Controller. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_AC97 +#include <VBox/log.h> +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include <iprt/assert.h> +#ifdef IN_RING3 +# ifdef DEBUG +# include <iprt/file.h> +# endif +# include <iprt/mem.h> +# include <iprt/semaphore.h> +# include <iprt/string.h> +# include <iprt/uuid.h> +#endif + +#include "VBoxDD.h" + +#include "AudioMixBuffer.h" +#include "AudioMixer.h" +#include "DrvAudio.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** Current saved state version. */ +#define AC97_SSM_VERSION 1 + +/** Default timer frequency (in Hz). */ +#define AC97_TIMER_HZ_DEFAULT 100 + +/** Maximum number of streams we support. */ +#define AC97_MAX_STREAMS 3 + +/** Maximum FIFO size (in bytes). */ +#define AC97_FIFO_MAX 256 + +#define AC97_SR_FIFOE RT_BIT(4) /**< rwc, FIFO error. */ +#define AC97_SR_BCIS RT_BIT(3) /**< rwc, Buffer completion interrupt status. */ +#define AC97_SR_LVBCI RT_BIT(2) /**< rwc, Last valid buffer completion interrupt. */ +#define AC97_SR_CELV RT_BIT(1) /**< ro, Current equals last valid. */ +#define AC97_SR_DCH RT_BIT(0) /**< ro, Controller halted. */ +#define AC97_SR_VALID_MASK (RT_BIT(5) - 1) +#define AC97_SR_WCLEAR_MASK (AC97_SR_FIFOE | AC97_SR_BCIS | AC97_SR_LVBCI) +#define AC97_SR_RO_MASK (AC97_SR_DCH | AC97_SR_CELV) +#define AC97_SR_INT_MASK (AC97_SR_FIFOE | AC97_SR_BCIS | AC97_SR_LVBCI) + +#define AC97_CR_IOCE RT_BIT(4) /**< rw, Interrupt On Completion Enable. */ +#define AC97_CR_FEIE RT_BIT(3) /**< rw FIFO Error Interrupt Enable. */ +#define AC97_CR_LVBIE RT_BIT(2) /**< rw Last Valid Buffer Interrupt Enable. */ +#define AC97_CR_RR RT_BIT(1) /**< rw Reset Registers. */ +#define AC97_CR_RPBM RT_BIT(0) /**< rw Run/Pause Bus Master. */ +#define AC97_CR_VALID_MASK (RT_BIT(5) - 1) +#define AC97_CR_DONT_CLEAR_MASK (AC97_CR_IOCE | AC97_CR_FEIE | AC97_CR_LVBIE) + +#define AC97_GC_WR 4 /**< rw Warm reset. */ +#define AC97_GC_CR 2 /**< rw Cold reset. */ +#define AC97_GC_VALID_MASK (RT_BIT(6) - 1) + +#define AC97_GS_MD3 RT_BIT(17) /**< rw */ +#define AC97_GS_AD3 RT_BIT(16) /**< rw */ +#define AC97_GS_RCS RT_BIT(15) /**< rwc */ +#define AC97_GS_B3S12 RT_BIT(14) /**< ro */ +#define AC97_GS_B2S12 RT_BIT(13) /**< ro */ +#define AC97_GS_B1S12 RT_BIT(12) /**< ro */ +#define AC97_GS_S1R1 RT_BIT(11) /**< rwc */ +#define AC97_GS_S0R1 RT_BIT(10) /**< rwc */ +#define AC97_GS_S1CR RT_BIT(9) /**< ro */ +#define AC97_GS_S0CR RT_BIT(8) /**< ro */ +#define AC97_GS_MINT RT_BIT(7) /**< ro */ +#define AC97_GS_POINT RT_BIT(6) /**< ro */ +#define AC97_GS_PIINT RT_BIT(5) /**< ro */ +#define AC97_GS_RSRVD (RT_BIT(4) | RT_BIT(3)) +#define AC97_GS_MOINT RT_BIT(2) /**< ro */ +#define AC97_GS_MIINT RT_BIT(1) /**< ro */ +#define AC97_GS_GSCI RT_BIT(0) /**< rwc */ +#define AC97_GS_RO_MASK ( AC97_GS_B3S12 \ + | AC97_GS_B2S12 \ + | AC97_GS_B1S12 \ + | AC97_GS_S1CR \ + | AC97_GS_S0CR \ + | AC97_GS_MINT \ + | AC97_GS_POINT \ + | AC97_GS_PIINT \ + | AC97_GS_RSRVD \ + | AC97_GS_MOINT \ + | AC97_GS_MIINT) +#define AC97_GS_VALID_MASK (RT_BIT(18) - 1) +#define AC97_GS_WCLEAR_MASK (AC97_GS_RCS | AC97_GS_S1R1 | AC97_GS_S0R1 | AC97_GS_GSCI) + +/** @name Buffer Descriptor (BD). + * @{ */ +#define AC97_BD_IOC RT_BIT(31) /**< Interrupt on Completion. */ +#define AC97_BD_BUP RT_BIT(30) /**< Buffer Underrun Policy. */ + +#define AC97_BD_LEN_MASK 0xFFFF /**< Mask for the BDL buffer length. */ + +#define AC97_MAX_BDLE 32 /**< Maximum number of BDLEs. */ +/** @} */ + +/** @name Extended Audio ID Register (EAID). + * @{ */ +#define AC97_EAID_VRA RT_BIT(0) /**< Variable Rate Audio. */ +#define AC97_EAID_VRM RT_BIT(3) /**< Variable Rate Mic Audio. */ +#define AC97_EAID_REV0 RT_BIT(10) /**< AC'97 revision compliance. */ +#define AC97_EAID_REV1 RT_BIT(11) /**< AC'97 revision compliance. */ +/** @} */ + +/** @name Extended Audio Control and Status Register (EACS). + * @{ */ +#define AC97_EACS_VRA RT_BIT(0) /**< Variable Rate Audio (4.2.1.1). */ +#define AC97_EACS_VRM RT_BIT(3) /**< Variable Rate Mic Audio (4.2.1.1). */ +/** @} */ + +/** @name Baseline Audio Register Set (BARS). + * @{ */ +#define AC97_BARS_VOL_MASK 0x1f /**< Volume mask for the Baseline Audio Register Set (5.7.2). */ +#define AC97_BARS_GAIN_MASK 0x0f /**< Gain mask for the Baseline Audio Register Set. */ +#define AC97_BARS_VOL_MUTE_SHIFT 15 /**< Mute bit shift for the Baseline Audio Register Set (5.7.2). */ +/** @} */ + +/* AC'97 uses 1.5dB steps, we use 0.375dB steps: 1 AC'97 step equals 4 PDM steps. */ +#define AC97_DB_FACTOR 4 + +#define AC97_REC_MASK 7 +enum +{ + AC97_REC_MIC = 0, + AC97_REC_CD, + AC97_REC_VIDEO, + AC97_REC_AUX, + AC97_REC_LINE_IN, + AC97_REC_STEREO_MIX, + AC97_REC_MONO_MIX, + AC97_REC_PHONE +}; + +enum +{ + AC97_Reset = 0x00, + AC97_Master_Volume_Mute = 0x02, + AC97_Headphone_Volume_Mute = 0x04, /** Also known as AUX, see table 16, section 5.7. */ + AC97_Master_Volume_Mono_Mute = 0x06, + AC97_Master_Tone_RL = 0x08, + AC97_PC_BEEP_Volume_Mute = 0x0A, + AC97_Phone_Volume_Mute = 0x0C, + AC97_Mic_Volume_Mute = 0x0E, + AC97_Line_In_Volume_Mute = 0x10, + AC97_CD_Volume_Mute = 0x12, + AC97_Video_Volume_Mute = 0x14, + AC97_Aux_Volume_Mute = 0x16, + AC97_PCM_Out_Volume_Mute = 0x18, + AC97_Record_Select = 0x1A, + AC97_Record_Gain_Mute = 0x1C, + AC97_Record_Gain_Mic_Mute = 0x1E, + AC97_General_Purpose = 0x20, + AC97_3D_Control = 0x22, + AC97_AC_97_RESERVED = 0x24, + AC97_Powerdown_Ctrl_Stat = 0x26, + AC97_Extended_Audio_ID = 0x28, + AC97_Extended_Audio_Ctrl_Stat = 0x2A, + AC97_PCM_Front_DAC_Rate = 0x2C, + AC97_PCM_Surround_DAC_Rate = 0x2E, + AC97_PCM_LFE_DAC_Rate = 0x30, + AC97_PCM_LR_ADC_Rate = 0x32, + AC97_MIC_ADC_Rate = 0x34, + AC97_6Ch_Vol_C_LFE_Mute = 0x36, + AC97_6Ch_Vol_L_R_Surround_Mute = 0x38, + AC97_Vendor_Reserved = 0x58, + AC97_AD_Misc = 0x76, + AC97_Vendor_ID1 = 0x7c, + AC97_Vendor_ID2 = 0x7e +}; + +/* Codec models. */ +typedef enum +{ + AC97_CODEC_STAC9700 = 0, /**< SigmaTel STAC9700 */ + AC97_CODEC_AD1980, /**< Analog Devices AD1980 */ + AC97_CODEC_AD1981B /**< Analog Devices AD1981B */ +} AC97CODEC; + +/* Analog Devices miscellaneous regiter bits used in AD1980. */ +#define AC97_AD_MISC_LOSEL RT_BIT(5) /**< Surround (rear) goes to line out outputs. */ +#define AC97_AD_MISC_HPSEL RT_BIT(10) /**< PCM (front) goes to headphone outputs. */ + +#define ICHAC97STATE_2_DEVINS(a_pAC97) ((a_pAC97)->CTX_SUFF(pDevIns)) + +enum +{ + BUP_SET = RT_BIT(0), + BUP_LAST = RT_BIT(1) +}; + +/** Emits registers for a specific (Native Audio Bus Master BAR) NABMBAR. + * @todo This totally messes with grepping for identifiers and tagging. */ +#define AC97_NABMBAR_REGS(prefix, off) \ + enum { \ + prefix ## _BDBAR = off, /* Buffer Descriptor Base Address */ \ + prefix ## _CIV = off + 4, /* Current Index Value */ \ + prefix ## _LVI = off + 5, /* Last Valid Index */ \ + prefix ## _SR = off + 6, /* Status Register */ \ + prefix ## _PICB = off + 8, /* Position in Current Buffer */ \ + prefix ## _PIV = off + 10, /* Prefetched Index Value */ \ + prefix ## _CR = off + 11 /* Control Register */ \ + } + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE +/** + * Enumeration of AC'97 source indices. + * + * Note: The order of this indices is fixed (also applies for saved states) for the moment. + * So make sure you know what you're done when altering this. + */ +typedef enum +{ + AC97SOUNDSOURCE_PI_INDEX = 0, /**< PCM in */ + AC97SOUNDSOURCE_PO_INDEX, /**< PCM out */ + AC97SOUNDSOURCE_MC_INDEX, /**< Mic in */ + AC97SOUNDSOURCE_END_INDEX +} AC97SOUNDSOURCE; + +AC97_NABMBAR_REGS(PI, AC97SOUNDSOURCE_PI_INDEX * 16); +AC97_NABMBAR_REGS(PO, AC97SOUNDSOURCE_PO_INDEX * 16); +AC97_NABMBAR_REGS(MC, AC97SOUNDSOURCE_MC_INDEX * 16); +#endif + +enum +{ + /** NABMBAR: Global Control Register. */ + AC97_GLOB_CNT = 0x2c, + /** NABMBAR Global Status. */ + AC97_GLOB_STA = 0x30, + /** Codec Access Semaphore Register. */ + AC97_CAS = 0x34 +}; + +#define AC97_PORT2IDX(a_idx) ( ((a_idx) >> 4) & 3 ) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Buffer Descriptor List Entry (BDLE). + */ +typedef struct AC97BDLE +{ + /** Location of data buffer (bits 31:1). */ + uint32_t addr; + /** Flags (bits 31 + 30) and length (bits 15:0) of data buffer (in audio samples). */ + uint32_t ctl_len; +} AC97BDLE; +AssertCompileSize(AC97BDLE, 8); +/** Pointer to BDLE. */ +typedef AC97BDLE *PAC97BDLE; + +/** + * Bus master register set for an audio stream. + */ +typedef struct AC97BMREGS +{ + uint32_t bdbar; /** rw 0, Buffer Descriptor List: BAR (Base Address Register). */ + uint8_t civ; /** ro 0, Current index value. */ + uint8_t lvi; /** rw 0, Last valid index. */ + uint16_t sr; /** rw 1, Status register. */ + uint16_t picb; /** ro 0, Position in current buffer (in samples). */ + uint8_t piv; /** ro 0, Prefetched index value. */ + uint8_t cr; /** rw 0, Control register. */ + int32_t bd_valid; /** Whether current BDLE is initialized or not. */ + AC97BDLE bd; /** Current Buffer Descriptor List Entry (BDLE). */ +} AC97BMREGS; +AssertCompileSizeAlignment(AC97BMREGS, 8); +/** Pointer to the BM registers of an audio stream. */ +typedef AC97BMREGS *PAC97BMREGS; + +#ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO +/** + * Structure keeping the AC'97 stream's state for asynchronous I/O. + */ +typedef struct AC97STREAMSTATEAIO +{ + /** Thread handle for the actual I/O thread. */ + RTTHREAD Thread; + /** Event for letting the thread know there is some data to process. */ + RTSEMEVENT Event; + /** Critical section for synchronizing access. */ + RTCRITSECT CritSect; + /** Started indicator. */ + volatile bool fStarted; + /** Shutdown indicator. */ + volatile bool fShutdown; + /** Whether the thread should do any data processing or not. */ + volatile bool fEnabled; + uint32_t Padding1; +} AC97STREAMSTATEAIO, *PAC97STREAMSTATEAIO; +#endif + +/** The ICH AC'97 (Intel) controller. */ +typedef struct AC97STATE *PAC97STATE; + +/** + * Structure for keeping the internal state of an AC'97 stream. + */ +typedef struct AC97STREAMSTATE +{ + /** Criticial section for this stream. */ + RTCRITSECT CritSect; + /** Circular buffer (FIFO) for holding DMA'ed data. */ + R3PTRTYPE(PRTCIRCBUF) pCircBuf; +#if HC_ARCH_BITS == 32 + uint32_t Padding; +#endif + /** The stream's current configuration. */ + PDMAUDIOSTREAMCFG Cfg; //+104 + uint32_t Padding2; +#ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + /** Asynchronous I/O state members. */ + AC97STREAMSTATEAIO AIO; +#endif + /** Timestamp of the last DMA data transfer. */ + uint64_t tsTransferLast; + /** Timestamp of the next DMA data transfer. + * Next for determining the next scheduling window. + * Can be 0 if no next transfer is scheduled. */ + uint64_t tsTransferNext; + /** Transfer chunk size (in bytes) of a transfer period. */ + uint32_t cbTransferChunk; + /** The stream's timer Hz rate. + * This value can can be different from the device's default Hz rate, + * depending on the rate the stream expects (e.g. for 5.1 speaker setups). + * Set in R3StreamInit(). */ + uint16_t uTimerHz; + uint8_t Padding3[2]; + /** (Virtual) clock ticks per transfer. */ + uint64_t cTransferTicks; + /** Timestamp (in ns) of last stream update. */ + uint64_t tsLastUpdateNs; +} AC97STREAMSTATE; +AssertCompileSizeAlignment(AC97STREAMSTATE, 8); +/** Pointer to internal state of an AC'97 stream. */ +typedef AC97STREAMSTATE *PAC97STREAMSTATE; + +/** + * Structure containing AC'97 stream debug stuff, configurable at runtime. + */ +typedef struct AC97STREAMDBGINFORT +{ + /** Whether debugging is enabled or not. */ + bool fEnabled; + uint8_t Padding[7]; + /** File for dumping stream reads / writes. + * For input streams, this dumps data being written to the device FIFO, + * whereas for output streams this dumps data being read from the device FIFO. */ + R3PTRTYPE(PPDMAUDIOFILE) pFileStream; + /** File for dumping DMA reads / writes. + * For input streams, this dumps data being written to the device DMA, + * whereas for output streams this dumps data being read from the device DMA. */ + R3PTRTYPE(PPDMAUDIOFILE) pFileDMA; +} AC97STREAMDBGINFORT, *PAC97STREAMDBGINFORT; + +/** + * Structure containing AC'97 stream debug information. + */ +typedef struct AC97STREAMDBGINFO +{ + /** Runtime debug info. */ + AC97STREAMDBGINFORT Runtime; +} AC97STREAMDBGINFO ,*PAC97STREAMDBGINFO; + +/** + * Structure for an AC'97 stream. + */ +typedef struct AC97STREAM +{ + /** Stream number (SDn). */ + uint8_t u8SD; + uint8_t abPadding0[7]; + /** Bus master registers of this stream. */ + AC97BMREGS Regs; + /** Internal state of this stream. */ + AC97STREAMSTATE State; + /** Pointer to parent (AC'97 state). */ + R3PTRTYPE(PAC97STATE) pAC97State; +#if HC_ARCH_BITS == 32 + uint32_t Padding1; +#endif + /** Debug information. */ + AC97STREAMDBGINFO Dbg; +} AC97STREAM, *PAC97STREAM; +AssertCompileSizeAlignment(AC97STREAM, 8); +/** Pointer to an AC'97 stream (registers + state). */ +typedef AC97STREAM *PAC97STREAM; + +typedef struct AC97STATE *PAC97STATE; +#ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO +/** + * Structure for the async I/O thread context. + */ +typedef struct AC97STREAMTHREADCTX +{ + PAC97STATE pThis; + PAC97STREAM pStream; +} AC97STREAMTHREADCTX, *PAC97STREAMTHREADCTX; +#endif + +/** + * Structure defining a (host backend) driver stream. + * Each driver has its own instances of audio mixer streams, which then + * can go into the same (or even different) audio mixer sinks. + */ +typedef struct AC97DRIVERSTREAM +{ + /** Associated mixer stream handle. */ + R3PTRTYPE(PAUDMIXSTREAM) pMixStrm; +} AC97DRIVERSTREAM, *PAC97DRIVERSTREAM; + +/** + * Struct for maintaining a host backend driver. + */ +typedef struct AC97DRIVER +{ + /** Node for storing this driver in our device driver list of AC97STATE. */ + RTLISTNODER3 Node; + /** Pointer to AC97 controller (state). */ + R3PTRTYPE(PAC97STATE) pAC97State; + /** Driver flags. */ + PDMAUDIODRVFLAGS fFlags; + uint32_t PaddingFlags; + /** LUN # to which this driver has been assigned. */ + uint8_t uLUN; + /** Whether this driver is in an attached state or not. */ + bool fAttached; + uint8_t Padding[4]; + /** Pointer to attached driver base interface. */ + R3PTRTYPE(PPDMIBASE) pDrvBase; + /** Audio connector interface to the underlying host backend. */ + R3PTRTYPE(PPDMIAUDIOCONNECTOR) pConnector; + /** Driver stream for line input. */ + AC97DRIVERSTREAM LineIn; + /** Driver stream for mic input. */ + AC97DRIVERSTREAM MicIn; + /** Driver stream for output. */ + AC97DRIVERSTREAM Out; +} AC97DRIVER, *PAC97DRIVER; + +typedef struct AC97STATEDBGINFO +{ + /** Whether debugging is enabled or not. */ + bool fEnabled; + /** Path where to dump the debug output to. + * Defaults to VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH. */ + char szOutPath[RTPATH_MAX + 1]; +} AC97STATEDBGINFO, *PAC97STATEDBGINFO; + +/** + * Structure for maintaining an AC'97 device state. + */ +typedef struct AC97STATE +{ + /** The PCI device state. */ + PDMPCIDEV PciDev; + /** Critical section protecting the AC'97 state. */ + PDMCRITSECT CritSect; + /** R3 pointer to the device instance. */ + PPDMDEVINSR3 pDevInsR3; + /** R0 pointer to the device instance. */ + PPDMDEVINSR0 pDevInsR0; + /** RC pointer to the device instance. */ + PPDMDEVINSRC pDevInsRC; + /** Set if R0/RC is enabled. */ + bool fRZEnabled; + bool afPadding0[3]; + /** Global Control (Bus Master Control Register). */ + uint32_t glob_cnt; + /** Global Status (Bus Master Control Register). */ + uint32_t glob_sta; + /** Codec Access Semaphore Register (Bus Master Control Register). */ + uint32_t cas; + uint32_t last_samp; + uint8_t mixer_data[256]; + /** Array of AC'97 streams. */ + AC97STREAM aStreams[AC97_MAX_STREAMS]; + /** The device timer Hz rate. Defaults to AC97_TIMER_HZ_DEFAULT_DEFAULT. */ + uint16_t uTimerHz; + /** The timer for pumping data thru the attached LUN drivers - RCPtr. */ + PTMTIMERRC pTimerRC[AC97_MAX_STREAMS]; + /** The timer for pumping data thru the attached LUN drivers - R3Ptr. */ + PTMTIMERR3 pTimerR3[AC97_MAX_STREAMS]; + /** The timer for pumping data thru the attached LUN drivers - R0Ptr. */ + PTMTIMERR0 pTimerR0[AC97_MAX_STREAMS]; +#ifdef VBOX_WITH_STATISTICS + STAMPROFILE StatTimer; + STAMPROFILE StatIn; + STAMPROFILE StatOut; + STAMCOUNTER StatBytesRead; + STAMCOUNTER StatBytesWritten; +#endif + /** List of associated LUN drivers (AC97DRIVER). */ + RTLISTANCHORR3 lstDrv; + /** The device's software mixer. */ + R3PTRTYPE(PAUDIOMIXER) pMixer; + /** Audio sink for PCM output. */ + R3PTRTYPE(PAUDMIXSINK) pSinkOut; + /** Audio sink for line input. */ + R3PTRTYPE(PAUDMIXSINK) pSinkLineIn; + /** Audio sink for microphone input. */ + R3PTRTYPE(PAUDMIXSINK) pSinkMicIn; + uint8_t silence[128]; + int32_t bup_flag; + /** Base port of the I/O space region. */ + RTIOPORT IOPortBase[2]; + /** Codec model. */ + uint32_t uCodecModel; +#if HC_ARCH_BITS == 64 + uint32_t uPadding2; +#endif + /** The base interface for LUN\#0. */ + PDMIBASE IBase; + AC97STATEDBGINFO Dbg; +} AC97STATE; +AssertCompileMemberAlignment(AC97STATE, aStreams, 8); +/** Pointer to a AC'97 state. */ +typedef AC97STATE *PAC97STATE; + +/** + * Acquires the AC'97 lock. + */ +#define DEVAC97_LOCK(a_pThis) \ + do { \ + int rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, VERR_IGNORED); \ + AssertRC(rcLock); \ + } while (0) + +/** + * Acquires the AC'97 lock or returns. + */ +# define DEVAC97_LOCK_RETURN(a_pThis, a_rcBusy) \ + do { \ + int rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, a_rcBusy); \ + if (rcLock != VINF_SUCCESS) \ + { \ + AssertRC(rcLock); \ + return rcLock; \ + } \ + } while (0) + +/** + * Acquires the AC'97 lock or returns. + */ +# define DEVAC97_LOCK_RETURN_VOID(a_pThis) \ + do { \ + int rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, VERR_IGNORED); \ + if (rcLock != VINF_SUCCESS) \ + { \ + AssertRC(rcLock); \ + return; \ + } \ + } while (0) + +#ifdef IN_RC +/** Retrieves an attribute from a specific audio stream in RC. */ +# define DEVAC97_CTX_SUFF_SD(a_Var, a_SD) a_Var##RC[a_SD] +#elif defined(IN_RING0) +/** Retrieves an attribute from a specific audio stream in R0. */ +# define DEVAC97_CTX_SUFF_SD(a_Var, a_SD) a_Var##R0[a_SD] +#else +/** Retrieves an attribute from a specific audio stream in R3. */ +# define DEVAC97_CTX_SUFF_SD(a_Var, a_SD) a_Var##R3[a_SD] +#endif + +/** + * Releases the AC'97 lock. + */ +#define DEVAC97_UNLOCK(a_pThis) \ + do { PDMCritSectLeave(&(a_pThis)->CritSect); } while (0) + +/** + * Acquires the TM lock and AC'97 lock, returns on failure. + */ +#define DEVAC97_LOCK_BOTH_RETURN_VOID(a_pThis, a_SD) \ + do { \ + int rcLock = TMTimerLock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD), VERR_IGNORED); \ + if (rcLock != VINF_SUCCESS) \ + { \ + AssertRC(rcLock); \ + return; \ + } \ + rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, VERR_IGNORED); \ + if (rcLock != VINF_SUCCESS) \ + { \ + AssertRC(rcLock); \ + TMTimerUnlock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD)); \ + return; \ + } \ + } while (0) + +/** + * Acquires the TM lock and AC'97 lock, returns on failure. + */ +#define DEVAC97_LOCK_BOTH_RETURN(a_pThis, a_SD, a_rcBusy) \ + do { \ + int rcLock = TMTimerLock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD), (a_rcBusy)); \ + if (rcLock != VINF_SUCCESS) \ + return rcLock; \ + rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, (a_rcBusy)); \ + if (rcLock != VINF_SUCCESS) \ + { \ + AssertRC(rcLock); \ + TMTimerUnlock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD)); \ + return rcLock; \ + } \ + } while (0) + +/** + * Releases the AC'97 lock and TM lock. + */ +#define DEVAC97_UNLOCK_BOTH(a_pThis, a_SD) \ + do { \ + PDMCritSectLeave(&(a_pThis)->CritSect); \ + TMTimerUnlock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD)); \ + } while (0) + +#ifdef VBOX_WITH_STATISTICS +AssertCompileMemberAlignment(AC97STATE, StatTimer, 8); +AssertCompileMemberAlignment(AC97STATE, StatBytesRead, 8); +AssertCompileMemberAlignment(AC97STATE, StatBytesWritten, 8); +#endif + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifdef IN_RING3 +static int ichac97R3StreamCreate(PAC97STATE pThis, PAC97STREAM pStream, uint8_t u8Strm); +static void ichac97R3StreamDestroy(PAC97STATE pThis, PAC97STREAM pStream); +static int ichac97R3StreamOpen(PAC97STATE pThis, PAC97STREAM pStream); +static int ichac97R3StreamReOpen(PAC97STATE pThis, PAC97STREAM pStream); +static int ichac97R3StreamClose(PAC97STATE pThis, PAC97STREAM pStream); +static void ichac97R3StreamReset(PAC97STATE pThis, PAC97STREAM pStream); +static void ichac97R3StreamLock(PAC97STREAM pStream); +static void ichac97R3StreamUnlock(PAC97STREAM pStream); +static uint32_t ichac97R3StreamGetUsed(PAC97STREAM pStream); +static uint32_t ichac97R3StreamGetFree(PAC97STREAM pStream); +static int ichac97R3StreamTransfer(PAC97STATE pThis, PAC97STREAM pStream, uint32_t cbToProcessMax); +static void ichac97R3StreamUpdate(PAC97STATE pThis, PAC97STREAM pStream, bool fInTimer); + +static DECLCALLBACK(void) ichac97R3Reset(PPDMDEVINS pDevIns); + +static DECLCALLBACK(void) ichac97R3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser); + +static int ichac97R3MixerAddDrv(PAC97STATE pThis, PAC97DRIVER pDrv); +static int ichac97R3MixerAddDrvStream(PAC97STATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PAC97DRIVER pDrv); +static int ichac97R3MixerAddDrvStreams(PAC97STATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg); +static void ichac97R3MixerRemoveDrv(PAC97STATE pThis, PAC97DRIVER pDrv); +static void ichac97R3MixerRemoveDrvStream(PAC97STATE pThis, PAUDMIXSINK pMixSink, PDMAUDIODIR enmDir, PDMAUDIODESTSOURCE dstSrc, PAC97DRIVER pDrv); +static void ichac97R3MixerRemoveDrvStreams(PAC97STATE pThis, PAUDMIXSINK pMixSink, PDMAUDIODIR enmDir, PDMAUDIODESTSOURCE dstSrc); + +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO +static DECLCALLBACK(int) ichac97R3StreamAsyncIOThread(RTTHREAD hThreadSelf, void *pvUser); +static int ichac97R3StreamAsyncIOCreate(PAC97STATE pThis, PAC97STREAM pStream); +static int ichac97R3StreamAsyncIODestroy(PAC97STATE pThis, PAC97STREAM pStream); +static int ichac97R3StreamAsyncIONotify(PAC97STATE pThis, PAC97STREAM pStream); +static void ichac97R3StreamAsyncIOLock(PAC97STREAM pStream); +static void ichac97R3StreamAsyncIOUnlock(PAC97STREAM pStream); +/*static void ichac97R3StreamAsyncIOEnable(PAC97STREAM pStream, bool fEnable); Unused */ +# endif + +DECLINLINE(PDMAUDIODIR) ichac97GetDirFromSD(uint8_t uSD); + +# ifdef LOG_ENABLED +static void ichac97R3BDLEDumpAll(PAC97STATE pThis, uint64_t u64BDLBase, uint16_t cBDLE); +# endif +#endif /* IN_RING3 */ +bool ichac97TimerSet(PAC97STATE pThis, PAC97STREAM pStream, uint64_t tsExpire, bool fForce); + +static void ichac97WarmReset(PAC97STATE pThis) +{ + NOREF(pThis); +} + +static void ichac97ColdReset(PAC97STATE pThis) +{ + NOREF(pThis); +} + +#ifdef IN_RING3 + +/** + * Retrieves the audio mixer sink of a corresponding AC'97 stream index. + * + * @returns Pointer to audio mixer sink if found, or NULL if not found / invalid. + * @param pThis AC'97 state. + * @param uIndex Stream index to get audio mixer sink for. + */ +DECLINLINE(PAUDMIXSINK) ichac97R3IndexToSink(PAC97STATE pThis, uint8_t uIndex) +{ + AssertPtrReturn(pThis, NULL); + + switch (uIndex) + { + case AC97SOUNDSOURCE_PI_INDEX: return pThis->pSinkLineIn; + case AC97SOUNDSOURCE_PO_INDEX: return pThis->pSinkOut; + case AC97SOUNDSOURCE_MC_INDEX: return pThis->pSinkMicIn; + default: break; + } + + AssertMsgFailed(("Wrong index %RU8\n", uIndex)); + return NULL; +} + +/** + * Fetches the current BDLE (Buffer Descriptor List Entry) of an AC'97 audio stream. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pStream AC'97 stream to fetch BDLE for. + * + * @remark Uses CIV as BDLE index. + */ +static void ichac97R3StreamFetchBDLE(PAC97STATE pThis, PAC97STREAM pStream) +{ + PPDMDEVINS pDevIns = ICHAC97STATE_2_DEVINS(pThis); + PAC97BMREGS pRegs = &pStream->Regs; + + AC97BDLE BDLE; + PDMDevHlpPhysRead(pDevIns, pRegs->bdbar + pRegs->civ * sizeof(AC97BDLE), &BDLE, sizeof(AC97BDLE)); + pRegs->bd_valid = 1; +# ifndef RT_LITTLE_ENDIAN +# error "Please adapt the code (audio buffers are little endian)!" +# else + pRegs->bd.addr = RT_H2LE_U32(BDLE.addr & ~3); + pRegs->bd.ctl_len = RT_H2LE_U32(BDLE.ctl_len); +# endif + pRegs->picb = pRegs->bd.ctl_len & AC97_BD_LEN_MASK; + LogFlowFunc(("bd %2d addr=%#x ctl=%#06x len=%#x(%d bytes), bup=%RTbool, ioc=%RTbool\n", + pRegs->civ, pRegs->bd.addr, pRegs->bd.ctl_len >> 16, + pRegs->bd.ctl_len & AC97_BD_LEN_MASK, + (pRegs->bd.ctl_len & AC97_BD_LEN_MASK) << 1, /** @todo r=andy Assumes 16bit samples. */ + RT_BOOL(pRegs->bd.ctl_len & AC97_BD_BUP), + RT_BOOL(pRegs->bd.ctl_len & AC97_BD_IOC))); +} + +#endif /* IN_RING3 */ + +/** + * Updates the status register (SR) of an AC'97 audio stream. + * + * @param pThis AC'97 state. + * @param pStream AC'97 stream to update SR for. + * @param new_sr New value for status register (SR). + */ +static void ichac97StreamUpdateSR(PAC97STATE pThis, PAC97STREAM pStream, uint32_t new_sr) +{ + PPDMDEVINS pDevIns = ICHAC97STATE_2_DEVINS(pThis); + PAC97BMREGS pRegs = &pStream->Regs; + + bool fSignal = false; + int iIRQL = 0; + + uint32_t new_mask = new_sr & AC97_SR_INT_MASK; + uint32_t old_mask = pRegs->sr & AC97_SR_INT_MASK; + + if (new_mask ^ old_mask) + { + /** @todo Is IRQ deasserted when only one of status bits is cleared? */ + if (!new_mask) + { + fSignal = true; + iIRQL = 0; + } + else if ((new_mask & AC97_SR_LVBCI) && (pRegs->cr & AC97_CR_LVBIE)) + { + fSignal = true; + iIRQL = 1; + } + else if ((new_mask & AC97_SR_BCIS) && (pRegs->cr & AC97_CR_IOCE)) + { + fSignal = true; + iIRQL = 1; + } + } + + pRegs->sr = new_sr; + + LogFlowFunc(("IOC%d, LVB%d, sr=%#x, fSignal=%RTbool, IRQL=%d\n", + pRegs->sr & AC97_SR_BCIS, pRegs->sr & AC97_SR_LVBCI, pRegs->sr, fSignal, iIRQL)); + + if (fSignal) + { + static uint32_t const s_aMasks[] = { AC97_GS_PIINT, AC97_GS_POINT, AC97_GS_MINT }; + Assert(pStream->u8SD < AC97_MAX_STREAMS); + if (iIRQL) + pThis->glob_sta |= s_aMasks[pStream->u8SD]; + else + pThis->glob_sta &= ~s_aMasks[pStream->u8SD]; + + LogFlowFunc(("Setting IRQ level=%d\n", iIRQL)); + PDMDevHlpPCISetIrq(pDevIns, 0, iIRQL); + } +} + +/** + * Writes a new value to a stream's status register (SR). + * + * @param pThis AC'97 device state. + * @param pStream Stream to update SR for. + * @param u32Val New value to set the stream's SR to. + */ +static void ichac97StreamWriteSR(PAC97STATE pThis, PAC97STREAM pStream, uint32_t u32Val) +{ + PAC97BMREGS pRegs = &pStream->Regs; + + Log3Func(("[SD%RU8] SR <- %#x (sr %#x)\n", pStream->u8SD, u32Val, pRegs->sr)); + + pRegs->sr |= u32Val & ~(AC97_SR_RO_MASK | AC97_SR_WCLEAR_MASK); + ichac97StreamUpdateSR(pThis, pStream, pRegs->sr & ~(u32Val & AC97_SR_WCLEAR_MASK)); +} + +#ifdef IN_RING3 + +/** + * Returns whether an AC'97 stream is enabled or not. + * + * @returns IPRT status code. + * @param pThis AC'97 device state. + * @param pStream Stream to return status for. + */ +static bool ichac97R3StreamIsEnabled(PAC97STATE pThis, PAC97STREAM pStream) +{ + AssertPtrReturn(pThis, false); + AssertPtrReturn(pStream, false); + + PAUDMIXSINK pSink = ichac97R3IndexToSink(pThis, pStream->u8SD); + bool fIsEnabled = RT_BOOL(AudioMixerSinkGetStatus(pSink) & AUDMIXSINK_STS_RUNNING); + + LogFunc(("[SD%RU8] fIsEnabled=%RTbool\n", pStream->u8SD, fIsEnabled)); + return fIsEnabled; +} + +/** + * Enables or disables an AC'97 audio stream. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pStream AC'97 stream to enable or disable. + * @param fEnable Whether to enable or disable the stream. + * + */ +static int ichac97R3StreamEnable(PAC97STATE pThis, PAC97STREAM pStream, bool fEnable) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + ichac97R3StreamLock(pStream); + + int rc = VINF_SUCCESS; + +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + if (fEnable) + rc = ichac97R3StreamAsyncIOCreate(pThis, pStream); + if (RT_SUCCESS(rc)) + ichac97R3StreamAsyncIOLock(pStream); +# endif + + if (fEnable) + { + if (pStream->State.pCircBuf) + RTCircBufReset(pStream->State.pCircBuf); + + rc = ichac97R3StreamOpen(pThis, pStream); + + if (pStream->Dbg.Runtime.fEnabled) + { + if (!DrvAudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileStream)) + { + int rc2 = DrvAudioHlpFileOpen(pStream->Dbg.Runtime.pFileStream, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, + &pStream->State.Cfg.Props); + AssertRC(rc2); + } + + if (!DrvAudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileDMA)) + { + int rc2 = DrvAudioHlpFileOpen(pStream->Dbg.Runtime.pFileDMA, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, + &pStream->State.Cfg.Props); + AssertRC(rc2); + } + } + } + else + rc = ichac97R3StreamClose(pThis, pStream); + + if (RT_SUCCESS(rc)) + { + /* First, enable or disable the stream and the stream's sink, if any. */ + rc = AudioMixerSinkCtl(ichac97R3IndexToSink(pThis, pStream->u8SD), + fEnable ? AUDMIXSINKCMD_ENABLE : AUDMIXSINKCMD_DISABLE); + } + +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + ichac97R3StreamAsyncIOUnlock(pStream); +# endif + + /* Make sure to leave the lock before (eventually) starting the timer. */ + ichac97R3StreamUnlock(pStream); + + LogFunc(("[SD%RU8] fEnable=%RTbool, rc=%Rrc\n", pStream->u8SD, fEnable, rc)); + return rc; +} + +/** + * Resets an AC'97 stream. + * + * @param pThis AC'97 state. + * @param pStream AC'97 stream to reset. + * + */ +static void ichac97R3StreamReset(PAC97STATE pThis, PAC97STREAM pStream) +{ + AssertPtrReturnVoid(pThis); + AssertPtrReturnVoid(pStream); + + ichac97R3StreamLock(pStream); + + LogFunc(("[SD%RU8]\n", pStream->u8SD)); + + if (pStream->State.pCircBuf) + RTCircBufReset(pStream->State.pCircBuf); + + PAC97BMREGS pRegs = &pStream->Regs; + + pRegs->bdbar = 0; + pRegs->civ = 0; + pRegs->lvi = 0; + + pRegs->picb = 0; + pRegs->piv = 0; + pRegs->cr = pRegs->cr & AC97_CR_DONT_CLEAR_MASK; + pRegs->bd_valid = 0; + + RT_ZERO(pThis->silence); + + ichac97R3StreamUnlock(pStream); +} + +/** + * Creates an AC'97 audio stream. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pStream AC'97 stream to create. + * @param u8SD Stream descriptor number to assign. + */ +static int ichac97R3StreamCreate(PAC97STATE pThis, PAC97STREAM pStream, uint8_t u8SD) +{ + RT_NOREF(pThis); + AssertPtrReturn(pStream, VERR_INVALID_PARAMETER); + /** @todo Validate u8Strm. */ + + LogFunc(("[SD%RU8] pStream=%p\n", u8SD, pStream)); + + AssertReturn(u8SD < AC97_MAX_STREAMS, VERR_INVALID_PARAMETER); + pStream->u8SD = u8SD; + pStream->pAC97State = pThis; + + int rc = RTCritSectInit(&pStream->State.CritSect); + + pStream->Dbg.Runtime.fEnabled = pThis->Dbg.fEnabled; + + if (pStream->Dbg.Runtime.fEnabled) + { + char szFile[64]; + + if (ichac97GetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN) + RTStrPrintf(szFile, sizeof(szFile), "ac97StreamWriteSD%RU8", pStream->u8SD); + else + RTStrPrintf(szFile, sizeof(szFile), "ac97StreamReadSD%RU8", pStream->u8SD); + + char szPath[RTPATH_MAX + 1]; + int rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThis->Dbg.szOutPath, szFile, + 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE); + AssertRC(rc2); + rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAG_NONE, &pStream->Dbg.Runtime.pFileStream); + AssertRC(rc2); + + if (ichac97GetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN) + RTStrPrintf(szFile, sizeof(szFile), "ac97DMAWriteSD%RU8", pStream->u8SD); + else + RTStrPrintf(szFile, sizeof(szFile), "ac97DMAReadSD%RU8", pStream->u8SD); + + rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThis->Dbg.szOutPath, szFile, + 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE); + AssertRC(rc2); + + rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAG_NONE, &pStream->Dbg.Runtime.pFileDMA); + AssertRC(rc2); + + /* Delete stale debugging files from a former run. */ + DrvAudioHlpFileDelete(pStream->Dbg.Runtime.pFileStream); + DrvAudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMA); + } + + return rc; +} + +/** + * Destroys an AC'97 audio stream. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pStream AC'97 stream to destroy. + */ +static void ichac97R3StreamDestroy(PAC97STATE pThis, PAC97STREAM pStream) +{ + LogFlowFunc(("[SD%RU8]\n", pStream->u8SD)); + + ichac97R3StreamClose(pThis, pStream); + + int rc2 = RTCritSectDelete(&pStream->State.CritSect); + AssertRC(rc2); + +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + rc2 = ichac97R3StreamAsyncIODestroy(pThis, pStream); + AssertRC(rc2); +# else + RT_NOREF(pThis); +# endif + + if (pStream->Dbg.Runtime.fEnabled) + { + DrvAudioHlpFileDestroy(pStream->Dbg.Runtime.pFileStream); + pStream->Dbg.Runtime.pFileStream = NULL; + + DrvAudioHlpFileDestroy(pStream->Dbg.Runtime.pFileDMA); + pStream->Dbg.Runtime.pFileDMA = NULL; + } + + if (pStream->State.pCircBuf) + { + RTCircBufDestroy(pStream->State.pCircBuf); + pStream->State.pCircBuf = NULL; + } + + LogFlowFuncLeave(); +} + +/** + * Destroys all AC'97 audio streams of the device. + * + * @param pThis AC'97 state. + */ +static void ichac97R3StreamsDestroy(PAC97STATE pThis) +{ + LogFlowFuncEnter(); + + /* + * Destroy all AC'97 streams. + */ + for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) + ichac97R3StreamDestroy(pThis, &pThis->aStreams[i]); + + /* + * Destroy all sinks. + */ + + PDMAUDIODESTSOURCE dstSrc; + if (pThis->pSinkLineIn) + { + dstSrc.Source = PDMAUDIORECSOURCE_LINE; + ichac97R3MixerRemoveDrvStreams(pThis, pThis->pSinkLineIn, PDMAUDIODIR_IN, dstSrc); + + AudioMixerSinkDestroy(pThis->pSinkLineIn); + pThis->pSinkLineIn = NULL; + } + + if (pThis->pSinkMicIn) + { + dstSrc.Source = PDMAUDIORECSOURCE_MIC; + ichac97R3MixerRemoveDrvStreams(pThis, pThis->pSinkMicIn, PDMAUDIODIR_IN, dstSrc); + + AudioMixerSinkDestroy(pThis->pSinkMicIn); + pThis->pSinkMicIn = NULL; + } + + if (pThis->pSinkOut) + { + dstSrc.Dest = PDMAUDIOPLAYBACKDEST_FRONT; + ichac97R3MixerRemoveDrvStreams(pThis, pThis->pSinkOut, PDMAUDIODIR_OUT, dstSrc); + + AudioMixerSinkDestroy(pThis->pSinkOut); + pThis->pSinkOut = NULL; + } +} + +/** + * Writes audio data from a mixer sink into an AC'97 stream's DMA buffer. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pDstStream AC'97 stream to write to. + * @param pSrcMixSink Mixer sink to get audio data to write from. + * @param cbToWrite Number of bytes to write. + * @param pcbWritten Number of bytes written. Optional. + */ +static int ichac97R3StreamWrite(PAC97STATE pThis, PAC97STREAM pDstStream, PAUDMIXSINK pSrcMixSink, uint32_t cbToWrite, + uint32_t *pcbWritten) +{ + RT_NOREF(pThis); + AssertPtrReturn(pDstStream, VERR_INVALID_POINTER); + AssertPtrReturn(pSrcMixSink, VERR_INVALID_POINTER); + AssertReturn(cbToWrite, VERR_INVALID_PARAMETER); + /* pcbWritten is optional. */ + + PRTCIRCBUF pCircBuf = pDstStream->State.pCircBuf; + AssertPtr(pCircBuf); + + void *pvDst; + size_t cbDst; + + uint32_t cbRead = 0; + + RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvDst, &cbDst); + + if (cbDst) + { + int rc2 = AudioMixerSinkRead(pSrcMixSink, AUDMIXOP_COPY, pvDst, (uint32_t)cbDst, &cbRead); + AssertRC(rc2); + + if (pDstStream->Dbg.Runtime.fEnabled) + DrvAudioHlpFileWrite(pDstStream->Dbg.Runtime.pFileStream, pvDst, cbRead, 0 /* fFlags */); + } + + RTCircBufReleaseWriteBlock(pCircBuf, cbRead); + + if (pcbWritten) + *pcbWritten = cbRead; + + return VINF_SUCCESS; +} + +/** + * Reads audio data from an AC'97 stream's DMA buffer and writes into a specified mixer sink. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pSrcStream AC'97 stream to read audio data from. + * @param pDstMixSink Mixer sink to write audio data to. + * @param cbToRead Number of bytes to read. + * @param pcbRead Number of bytes read. Optional. + */ +static int ichac97R3StreamRead(PAC97STATE pThis, PAC97STREAM pSrcStream, PAUDMIXSINK pDstMixSink, uint32_t cbToRead, + uint32_t *pcbRead) +{ + RT_NOREF(pThis); + AssertPtrReturn(pSrcStream, VERR_INVALID_POINTER); + AssertPtrReturn(pDstMixSink, VERR_INVALID_POINTER); + AssertReturn(cbToRead, VERR_INVALID_PARAMETER); + /* pcbRead is optional. */ + + PRTCIRCBUF pCircBuf = pSrcStream->State.pCircBuf; + AssertPtr(pCircBuf); + + void *pvSrc; + size_t cbSrc; + + int rc = VINF_SUCCESS; + + uint32_t cbReadTotal = 0; + uint32_t cbLeft = RT_MIN(cbToRead, (uint32_t)RTCircBufUsed(pCircBuf)); + + while (cbLeft) + { + uint32_t cbWritten = 0; + + RTCircBufAcquireReadBlock(pCircBuf, cbLeft, &pvSrc, &cbSrc); + + if (cbSrc) + { + if (pSrcStream->Dbg.Runtime.fEnabled) + DrvAudioHlpFileWrite(pSrcStream->Dbg.Runtime.pFileStream, pvSrc, cbSrc, 0 /* fFlags */); + + rc = AudioMixerSinkWrite(pDstMixSink, AUDMIXOP_COPY, pvSrc, (uint32_t)cbSrc, &cbWritten); + AssertRC(rc); + + Assert(cbSrc >= cbWritten); + Log3Func(("[SD%RU8] %RU32/%zu bytes read\n", pSrcStream->u8SD, cbWritten, cbSrc)); + } + + RTCircBufReleaseReadBlock(pCircBuf, cbWritten); + + if (RT_FAILURE(rc)) + break; + + Assert(cbLeft >= cbWritten); + cbLeft -= cbWritten; + + cbReadTotal += cbWritten; + } + + if (pcbRead) + *pcbRead = cbReadTotal; + + return rc; +} + +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + +/** + * Asynchronous I/O thread for an AC'97 stream. + * This will do the heavy lifting work for us as soon as it's getting notified by another thread. + * + * @returns IPRT status code. + * @param hThreadSelf Thread handle. + * @param pvUser User argument. Must be of type PAC97STREAMTHREADCTX. + */ +static DECLCALLBACK(int) ichac97R3StreamAsyncIOThread(RTTHREAD hThreadSelf, void *pvUser) +{ + PAC97STREAMTHREADCTX pCtx = (PAC97STREAMTHREADCTX)pvUser; + AssertPtr(pCtx); + + PAC97STATE pThis = pCtx->pThis; + AssertPtr(pThis); + + PAC97STREAM pStream = pCtx->pStream; + AssertPtr(pStream); + + PAC97STREAMSTATEAIO pAIO = &pCtx->pStream->State.AIO; + + ASMAtomicXchgBool(&pAIO->fStarted, true); + + RTThreadUserSignal(hThreadSelf); + + LogFunc(("[SD%RU8] Started\n", pStream->u8SD)); + + for (;;) + { + Log2Func(("[SD%RU8] Waiting ...\n", pStream->u8SD)); + + int rc2 = RTSemEventWait(pAIO->Event, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc2)) + break; + + if (ASMAtomicReadBool(&pAIO->fShutdown)) + break; + + rc2 = RTCritSectEnter(&pAIO->CritSect); + if (RT_SUCCESS(rc2)) + { + if (!pAIO->fEnabled) + { + RTCritSectLeave(&pAIO->CritSect); + continue; + } + + ichac97R3StreamUpdate(pThis, pStream, false /* fInTimer */); + + int rc3 = RTCritSectLeave(&pAIO->CritSect); + AssertRC(rc3); + } + + AssertRC(rc2); + } + + LogFunc(("[SD%RU8] Ended\n", pStream->u8SD)); + + ASMAtomicXchgBool(&pAIO->fStarted, false); + + return VINF_SUCCESS; +} + +/** + * Creates the async I/O thread for a specific AC'97 audio stream. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pStream AC'97 audio stream to create the async I/O thread for. + */ +static int ichac97R3StreamAsyncIOCreate(PAC97STATE pThis, PAC97STREAM pStream) +{ + PAC97STREAMSTATEAIO pAIO = &pStream->State.AIO; + + int rc; + + if (!ASMAtomicReadBool(&pAIO->fStarted)) + { + pAIO->fShutdown = false; + pAIO->fEnabled = true; /* Enabled by default. */ + + rc = RTSemEventCreate(&pAIO->Event); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&pAIO->CritSect); + if (RT_SUCCESS(rc)) + { + AC97STREAMTHREADCTX Ctx = { pThis, pStream }; + + char szThreadName[64]; + RTStrPrintf2(szThreadName, sizeof(szThreadName), "ac97AIO%RU8", pStream->u8SD); + + rc = RTThreadCreate(&pAIO->Thread, ichac97R3StreamAsyncIOThread, &Ctx, + 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, szThreadName); + if (RT_SUCCESS(rc)) + rc = RTThreadUserWait(pAIO->Thread, 10 * 1000 /* 10s timeout */); + } + } + } + else + rc = VINF_SUCCESS; + + LogFunc(("[SD%RU8] Returning %Rrc\n", pStream->u8SD, rc)); + return rc; +} + +/** + * Destroys the async I/O thread of a specific AC'97 audio stream. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pStream AC'97 audio stream to destroy the async I/O thread for. + */ +static int ichac97R3StreamAsyncIODestroy(PAC97STATE pThis, PAC97STREAM pStream) +{ + PAC97STREAMSTATEAIO pAIO = &pStream->State.AIO; + + if (!ASMAtomicReadBool(&pAIO->fStarted)) + return VINF_SUCCESS; + + ASMAtomicWriteBool(&pAIO->fShutdown, true); + + int rc = ichac97R3StreamAsyncIONotify(pThis, pStream); + AssertRC(rc); + + int rcThread; + rc = RTThreadWait(pAIO->Thread, 30 * 1000 /* 30s timeout */, &rcThread); + LogFunc(("Async I/O thread ended with %Rrc (%Rrc)\n", rc, rcThread)); + + if (RT_SUCCESS(rc)) + { + rc = RTCritSectDelete(&pAIO->CritSect); + AssertRC(rc); + + rc = RTSemEventDestroy(pAIO->Event); + AssertRC(rc); + + pAIO->fStarted = false; + pAIO->fShutdown = false; + pAIO->fEnabled = false; + } + + LogFunc(("[SD%RU8] Returning %Rrc\n", pStream->u8SD, rc)); + return rc; +} + +/** + * Lets the stream's async I/O thread know that there is some data to process. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pStream AC'97 stream to notify async I/O thread for. + */ +static int ichac97R3StreamAsyncIONotify(PAC97STATE pThis, PAC97STREAM pStream) +{ + RT_NOREF(pThis); + + LogFunc(("[SD%RU8]\n", pStream->u8SD)); + return RTSemEventSignal(pStream->State.AIO.Event); +} + +/** + * Locks the async I/O thread of a specific AC'97 audio stream. + * + * @param pStream AC'97 stream to lock async I/O thread for. + */ +static void ichac97R3StreamAsyncIOLock(PAC97STREAM pStream) +{ + PAC97STREAMSTATEAIO pAIO = &pStream->State.AIO; + + if (!ASMAtomicReadBool(&pAIO->fStarted)) + return; + + int rc2 = RTCritSectEnter(&pAIO->CritSect); + AssertRC(rc2); +} + +/** + * Unlocks the async I/O thread of a specific AC'97 audio stream. + * + * @param pStream AC'97 stream to unlock async I/O thread for. + */ +static void ichac97R3StreamAsyncIOUnlock(PAC97STREAM pStream) +{ + PAC97STREAMSTATEAIO pAIO = &pStream->State.AIO; + + if (!ASMAtomicReadBool(&pAIO->fStarted)) + return; + + int rc2 = RTCritSectLeave(&pAIO->CritSect); + AssertRC(rc2); +} + +#if 0 /* Unused */ +/** + * Enables (resumes) or disables (pauses) the async I/O thread. + * + * @param pStream AC'97 stream to enable/disable async I/O thread for. + * @param fEnable Whether to enable or disable the I/O thread. + * + * @remarks Does not do locking. + */ +static void ichac97R3StreamAsyncIOEnable(PAC97STREAM pStream, bool fEnable) +{ + PAC97STREAMSTATEAIO pAIO = &pStream->State.AIO; + ASMAtomicXchgBool(&pAIO->fEnabled, fEnable); +} +#endif +# endif /* VBOX_WITH_AUDIO_AC97_ASYNC_IO */ + +# ifdef LOG_ENABLED +static void ichac97R3BDLEDumpAll(PAC97STATE pThis, uint64_t u64BDLBase, uint16_t cBDLE) +{ + LogFlowFunc(("BDLEs @ 0x%x (%RU16):\n", u64BDLBase, cBDLE)); + if (!u64BDLBase) + return; + + uint32_t cbBDLE = 0; + for (uint16_t i = 0; i < cBDLE; i++) + { + AC97BDLE BDLE; + PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), u64BDLBase + i * sizeof(AC97BDLE), &BDLE, sizeof(AC97BDLE)); + +# ifndef RT_LITTLE_ENDIAN +# error "Please adapt the code (audio buffers are little endian)!" +# else + BDLE.addr = RT_H2LE_U32(BDLE.addr & ~3); + BDLE.ctl_len = RT_H2LE_U32(BDLE.ctl_len); +#endif + LogFunc(("\t#%03d BDLE(adr:0x%llx, size:%RU32 [%RU32 bytes], bup:%RTbool, ioc:%RTbool)\n", + i, BDLE.addr, + BDLE.ctl_len & AC97_BD_LEN_MASK, + (BDLE.ctl_len & AC97_BD_LEN_MASK) << 1, /** @todo r=andy Assumes 16bit samples. */ + RT_BOOL(BDLE.ctl_len & AC97_BD_BUP), + RT_BOOL(BDLE.ctl_len & AC97_BD_IOC))); + + cbBDLE += (BDLE.ctl_len & AC97_BD_LEN_MASK) << 1; /** @todo r=andy Ditto. */ + } + + LogFlowFunc(("Total: %RU32 bytes\n", cbBDLE)); +} +# endif /* LOG_ENABLED */ + +/** + * Updates an AC'97 stream by doing its required data transfers. + * The host sink(s) set the overall pace. + * + * This routine is called by both, the synchronous and the asynchronous + * (VBOX_WITH_AUDIO_AC97_ASYNC_IO), implementations. + * + * When running synchronously, the device DMA transfers *and* the mixer sink + * processing is within the device timer. + * + * When running asynchronously, only the device DMA transfers are done in the + * device timer, whereas the mixer sink processing then is done in the stream's + * own async I/O thread. This thread also will call this function + * (with fInTimer set to @c false). + * + * @param pThis AC'97 state. + * @param pStream AC'97 stream to update. + * @param fInTimer Whether to this function was called from the timer + * context or an asynchronous I/O stream thread (if supported). + */ +static void ichac97R3StreamUpdate(PAC97STATE pThis, PAC97STREAM pStream, bool fInTimer) +{ + RT_NOREF(fInTimer); + + PAUDMIXSINK pSink = ichac97R3IndexToSink(pThis, pStream->u8SD); + AssertPtr(pSink); + + if (!AudioMixerSinkIsActive(pSink)) /* No sink available? Bail out. */ + return; + + int rc2; + + if (pStream->State.Cfg.enmDir == PDMAUDIODIR_OUT) /* Output (SDO). */ + { +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + if (fInTimer) +# endif + { + const uint32_t cbStreamFree = ichac97R3StreamGetFree(pStream); + if (cbStreamFree) + { + Log3Func(("[SD%RU8] PICB=%zu (%RU64ms), cbFree=%zu (%RU64ms), cbTransferChunk=%zu (%RU64ms)\n", + pStream->u8SD, + (pStream->Regs.picb << 1), DrvAudioHlpBytesToMilli((pStream->Regs.picb << 1), &pStream->State.Cfg.Props), + cbStreamFree, DrvAudioHlpBytesToMilli(cbStreamFree, &pStream->State.Cfg.Props), + pStream->State.cbTransferChunk, DrvAudioHlpBytesToMilli(pStream->State.cbTransferChunk, &pStream->State.Cfg.Props))); + + /* Do the DMA transfer. */ + rc2 = ichac97R3StreamTransfer(pThis, pStream, RT_MIN(pStream->State.cbTransferChunk, cbStreamFree)); + AssertRC(rc2); + + pStream->State.tsLastUpdateNs = RTTimeNanoTS(); + } + } + + Log3Func(("[SD%RU8] fInTimer=%RTbool\n", pStream->u8SD, fInTimer)); + +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + rc2 = ichac97R3StreamAsyncIONotify(pThis, pStream); + AssertRC(rc2); +# endif + +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + if (!fInTimer) /* In async I/O thread */ + { +# endif + const uint32_t cbSinkWritable = AudioMixerSinkGetWritable(pSink); + const uint32_t cbStreamReadable = ichac97R3StreamGetUsed(pStream); + const uint32_t cbToReadFromStream = RT_MIN(cbStreamReadable, cbSinkWritable); + + Log3Func(("[SD%RU8] cbSinkWritable=%RU32, cbStreamReadable=%RU32\n", pStream->u8SD, cbSinkWritable, cbStreamReadable)); + + if (cbToReadFromStream) + { + /* Read (guest output) data and write it to the stream's sink. */ + rc2 = ichac97R3StreamRead(pThis, pStream, pSink, cbToReadFromStream, NULL); + AssertRC(rc2); + } +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + } +#endif + /* When running synchronously, update the associated sink here. + * Otherwise this will be done in the async I/O thread. */ + rc2 = AudioMixerSinkUpdate(pSink); + AssertRC(rc2); + } + else /* Input (SDI). */ + { +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + if (!fInTimer) + { +# endif + rc2 = AudioMixerSinkUpdate(pSink); + AssertRC(rc2); + + /* Is the sink ready to be read (host input data) from? If so, by how much? */ + uint32_t cbSinkReadable = AudioMixerSinkGetReadable(pSink); + + /* How much (guest input) data is available for writing at the moment for the AC'97 stream? */ + uint32_t cbStreamFree = ichac97R3StreamGetFree(pStream); + + Log3Func(("[SD%RU8] cbSinkReadable=%RU32, cbStreamFree=%RU32\n", pStream->u8SD, cbSinkReadable, cbStreamFree)); + + /* Do not read more than the sink can provide at the moment. + * The host sets the overall pace. */ + if (cbSinkReadable > cbStreamFree) + cbSinkReadable = cbStreamFree; + + if (cbSinkReadable) + { + /* Write (guest input) data to the stream which was read from stream's sink before. */ + rc2 = ichac97R3StreamWrite(pThis, pStream, pSink, cbSinkReadable, NULL /* pcbWritten */); + AssertRC(rc2); + } +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + } + else /* fInTimer */ + { +# endif + +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + const uint64_t tsNowNs = RTTimeNanoTS(); + if (tsNowNs - pStream->State.tsLastUpdateNs >= pStream->State.Cfg.Device.uSchedulingHintMs * RT_NS_1MS) + { + rc2 = ichac97R3StreamAsyncIONotify(pThis, pStream); + AssertRC(rc2); + + pStream->State.tsLastUpdateNs = tsNowNs; + } +# endif + + const uint32_t cbStreamUsed = ichac97R3StreamGetUsed(pStream); + if (cbStreamUsed) + { + /* When running synchronously, do the DMA data transfers here. + * Otherwise this will be done in the stream's async I/O thread. */ + rc2 = ichac97R3StreamTransfer(pThis, pStream, cbStreamUsed); + AssertRC(rc2); + } +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + } +# endif + } +} + +#endif /* IN_RING3 */ + +/** + * Sets a AC'97 mixer control to a specific value. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param uMixerIdx Mixer control to set value for. + * @param uVal Value to set. + */ +static void ichac97MixerSet(PAC97STATE pThis, uint8_t uMixerIdx, uint16_t uVal) +{ + AssertMsgReturnVoid(uMixerIdx + 2U <= sizeof(pThis->mixer_data), + ("Index %RU8 out of bounds (%zu)\n", uMixerIdx, sizeof(pThis->mixer_data))); + pThis->mixer_data[uMixerIdx + 0] = RT_LO_U8(uVal); + pThis->mixer_data[uMixerIdx + 1] = RT_HI_U8(uVal); +} + +/** + * Gets a value from a specific AC'97 mixer control. + * + * @returns Retrieved mixer control value. + * @param pThis AC'97 state. + * @param uMixerIdx Mixer control to get value for. + */ +static uint16_t ichac97MixerGet(PAC97STATE pThis, uint32_t uMixerIdx) +{ + AssertMsgReturn(uMixerIdx + 2U <= sizeof(pThis->mixer_data), + ("Index %RU8 out of bounds (%zu)\n", uMixerIdx, sizeof(pThis->mixer_data)), + UINT16_MAX); + return RT_MAKE_U16(pThis->mixer_data[uMixerIdx + 0], pThis->mixer_data[uMixerIdx + 1]); +} + +#ifdef IN_RING3 + +/** + * Retrieves a specific driver stream of a AC'97 driver. + * + * @returns Pointer to driver stream if found, or NULL if not found. + * @param pThis AC'97 state. + * @param pDrv Driver to retrieve driver stream for. + * @param enmDir Stream direction to retrieve. + * @param dstSrc Stream destination / source to retrieve. + */ +static PAC97DRIVERSTREAM ichac97R3MixerGetDrvStream(PAC97STATE pThis, PAC97DRIVER pDrv, + PDMAUDIODIR enmDir, PDMAUDIODESTSOURCE dstSrc) +{ + RT_NOREF(pThis); + + PAC97DRIVERSTREAM pDrvStream = NULL; + + if (enmDir == PDMAUDIODIR_IN) + { + LogFunc(("enmRecSource=%d\n", dstSrc.Source)); + + switch (dstSrc.Source) + { + case PDMAUDIORECSOURCE_LINE: + pDrvStream = &pDrv->LineIn; + break; + case PDMAUDIORECSOURCE_MIC: + pDrvStream = &pDrv->MicIn; + break; + default: + AssertFailed(); + break; + } + } + else if (enmDir == PDMAUDIODIR_OUT) + { + LogFunc(("enmPlaybackDest=%d\n", dstSrc.Dest)); + + switch (dstSrc.Dest) + { + case PDMAUDIOPLAYBACKDEST_FRONT: + pDrvStream = &pDrv->Out; + break; + default: + AssertFailed(); + break; + } + } + else + AssertFailed(); + + return pDrvStream; +} + +/** + * Adds a driver stream to a specific mixer sink. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pMixSink Mixer sink to add driver stream to. + * @param pCfg Stream configuration to use. + * @param pDrv Driver stream to add. + */ +static int ichac97R3MixerAddDrvStream(PAC97STATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PAC97DRIVER pDrv) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pMixSink, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + PPDMAUDIOSTREAMCFG pStreamCfg = DrvAudioHlpStreamCfgDup(pCfg); + if (!pStreamCfg) + return VERR_NO_MEMORY; + + if (!RTStrPrintf(pStreamCfg->szName, sizeof(pStreamCfg->szName), "%s", pCfg->szName)) + { + DrvAudioHlpStreamCfgFree(pStreamCfg); + return VERR_BUFFER_OVERFLOW; + } + + LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pStreamCfg->szName)); + + int rc; + + PAC97DRIVERSTREAM pDrvStream = ichac97R3MixerGetDrvStream(pThis, pDrv, pStreamCfg->enmDir, pStreamCfg->DestSource); + if (pDrvStream) + { + AssertMsg(pDrvStream->pMixStrm == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN)); + + PAUDMIXSTREAM pMixStrm; + rc = AudioMixerSinkCreateStream(pMixSink, pDrv->pConnector, pStreamCfg, 0 /* fFlags */, &pMixStrm); + LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pStreamCfg->szName, rc)); + if (RT_SUCCESS(rc)) + { + rc = AudioMixerSinkAddStream(pMixSink, pMixStrm); + LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pStreamCfg->szName, rc)); + if (RT_SUCCESS(rc)) + { + /* If this is an input stream, always set the latest (added) stream + * as the recording source. + * @todo Make the recording source dynamic (CFGM?). */ + if (pStreamCfg->enmDir == PDMAUDIODIR_IN) + { + PDMAUDIOBACKENDCFG Cfg; + rc = pDrv->pConnector->pfnGetConfig(pDrv->pConnector, &Cfg); + if (RT_SUCCESS(rc)) + { + if (Cfg.cMaxStreamsIn) /* At least one input source available? */ + { + rc = AudioMixerSinkSetRecordingSource(pMixSink, pMixStrm); + LogFlowFunc(("LUN#%RU8: Recording source for '%s' -> '%s', rc=%Rrc\n", + pDrv->uLUN, pStreamCfg->szName, Cfg.szName, rc)); + + if (RT_SUCCESS(rc)) + LogRel2(("AC97: Set recording source for '%s' to '%s'\n", pStreamCfg->szName, Cfg.szName)); + } + else + LogRel(("AC97: Backend '%s' currently is not offering any recording source for '%s'\n", + Cfg.szName, pStreamCfg->szName)); + } + else if (RT_FAILURE(rc)) + LogFunc(("LUN#%RU8: Unable to retrieve backend configuratio for '%s', rc=%Rrc\n", + pDrv->uLUN, pStreamCfg->szName, rc)); + } + } + } + + if (RT_SUCCESS(rc)) + pDrvStream->pMixStrm = pMixStrm; + } + else + rc = VERR_INVALID_PARAMETER; + + DrvAudioHlpStreamCfgFree(pStreamCfg); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Adds all current driver streams to a specific mixer sink. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pMixSink Mixer sink to add stream to. + * @param pCfg Stream configuration to use. + */ +static int ichac97R3MixerAddDrvStreams(PAC97STATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pMixSink, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + if (!DrvAudioHlpStreamCfgIsValid(pCfg)) + return VERR_INVALID_PARAMETER; + + int rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props); + if (RT_FAILURE(rc)) + return rc; + + PAC97DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, AC97DRIVER, Node) + { + int rc2 = ichac97R3MixerAddDrvStream(pThis, pMixSink, pCfg, pDrv); + if (RT_FAILURE(rc2)) + LogFunc(("Attaching stream failed with %Rrc\n", rc2)); + + /* Do not pass failure to rc here, as there might be drivers which aren't + * configured / ready yet. */ + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Adds a specific AC'97 driver to the driver chain. + * + * @return IPRT status code. + * @param pThis AC'97 state. + * @param pDrv AC'97 driver to add. + */ +static int ichac97R3MixerAddDrv(PAC97STATE pThis, PAC97DRIVER pDrv) +{ + int rc = VINF_SUCCESS; + + if (DrvAudioHlpStreamCfgIsValid(&pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg)) + { + int rc2 = ichac97R3MixerAddDrvStream(pThis, pThis->pSinkLineIn, + &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (DrvAudioHlpStreamCfgIsValid(&pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg)) + { + int rc2 = ichac97R3MixerAddDrvStream(pThis, pThis->pSinkOut, + &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (DrvAudioHlpStreamCfgIsValid(&pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg)) + { + int rc2 = ichac97R3MixerAddDrvStream(pThis, pThis->pSinkMicIn, + &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg, pDrv); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + +/** + * Removes a specific AC'97 driver from the driver chain and destroys its + * associated streams. + * + * @param pThis AC'97 state. + * @param pDrv AC'97 driver to remove. + */ +static void ichac97R3MixerRemoveDrv(PAC97STATE pThis, PAC97DRIVER pDrv) +{ + AssertPtrReturnVoid(pThis); + AssertPtrReturnVoid(pDrv); + + if (pDrv->MicIn.pMixStrm) + { + if (AudioMixerSinkGetRecordingSource(pThis->pSinkMicIn) == pDrv->MicIn.pMixStrm) + AudioMixerSinkSetRecordingSource(pThis->pSinkMicIn, NULL); + + AudioMixerSinkRemoveStream(pThis->pSinkMicIn, pDrv->MicIn.pMixStrm); + AudioMixerStreamDestroy(pDrv->MicIn.pMixStrm); + pDrv->MicIn.pMixStrm = NULL; + } + + if (pDrv->LineIn.pMixStrm) + { + if (AudioMixerSinkGetRecordingSource(pThis->pSinkLineIn) == pDrv->LineIn.pMixStrm) + AudioMixerSinkSetRecordingSource(pThis->pSinkLineIn, NULL); + + AudioMixerSinkRemoveStream(pThis->pSinkLineIn, pDrv->LineIn.pMixStrm); + AudioMixerStreamDestroy(pDrv->LineIn.pMixStrm); + pDrv->LineIn.pMixStrm = NULL; + } + + if (pDrv->Out.pMixStrm) + { + AudioMixerSinkRemoveStream(pThis->pSinkOut, pDrv->Out.pMixStrm); + AudioMixerStreamDestroy(pDrv->Out.pMixStrm); + pDrv->Out.pMixStrm = NULL; + } + + RTListNodeRemove(&pDrv->Node); +} + +/** + * Removes a driver stream from a specific mixer sink. + * + * @param pThis AC'97 state. + * @param pMixSink Mixer sink to remove audio streams from. + * @param enmDir Stream direction to remove. + * @param dstSrc Stream destination / source to remove. + * @param pDrv Driver stream to remove. + */ +static void ichac97R3MixerRemoveDrvStream(PAC97STATE pThis, PAUDMIXSINK pMixSink, + PDMAUDIODIR enmDir, PDMAUDIODESTSOURCE dstSrc, PAC97DRIVER pDrv) +{ + AssertPtrReturnVoid(pThis); + AssertPtrReturnVoid(pMixSink); + + PAC97DRIVERSTREAM pDrvStream = ichac97R3MixerGetDrvStream(pThis, pDrv, enmDir, dstSrc); + if (pDrvStream) + { + if (pDrvStream->pMixStrm) + { + AudioMixerSinkRemoveStream(pMixSink, pDrvStream->pMixStrm); + + AudioMixerStreamDestroy(pDrvStream->pMixStrm); + pDrvStream->pMixStrm = NULL; + } + } +} + +/** + * Removes all driver streams from a specific mixer sink. + * + * @param pThis AC'97 state. + * @param pMixSink Mixer sink to remove audio streams from. + * @param enmDir Stream direction to remove. + * @param dstSrc Stream destination / source to remove. + */ +static void ichac97R3MixerRemoveDrvStreams(PAC97STATE pThis, PAUDMIXSINK pMixSink, + PDMAUDIODIR enmDir, PDMAUDIODESTSOURCE dstSrc) +{ + AssertPtrReturnVoid(pThis); + AssertPtrReturnVoid(pMixSink); + + PAC97DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, AC97DRIVER, Node) + { + ichac97R3MixerRemoveDrvStream(pThis, pMixSink, enmDir, dstSrc, pDrv); + } +} + +/** + * Calculates and returns the ticks for a specified amount of bytes. + * + * @returns Calculated ticks + * @param pThis AC'97 device state. + * @param pStream AC'97 stream to calculate ticks for. + * @param cbBytes Bytes to calculate ticks for. + */ +static uint64_t ichac97R3StreamTransferCalcNext(PAC97STATE pThis, PAC97STREAM pStream, uint32_t cbBytes) +{ + if (!cbBytes) + return 0; + + const uint64_t usBytes = DrvAudioHlpBytesToMicro(cbBytes, &pStream->State.Cfg.Props); + const uint64_t cTransferTicks = TMTimerFromMicro((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD), usBytes); + + Log3Func(("[SD%RU8] Timer %uHz, cbBytes=%RU32 -> usBytes=%RU64, cTransferTicks=%RU64\n", + pStream->u8SD, pStream->State.uTimerHz, cbBytes, usBytes, cTransferTicks)); + + return cTransferTicks; +} + +/** + * Updates the next transfer based on a specific amount of bytes. + * + * @param pThis AC'97 device state. + * @param pStream AC'97 stream to update. + * @param cbBytes Bytes to update next transfer for. + */ +static void ichac97R3StreamTransferUpdate(PAC97STATE pThis, PAC97STREAM pStream, uint32_t cbBytes) +{ + if (!cbBytes) + return; + + /* Calculate the bytes we need to transfer to / from the stream's DMA per iteration. + * This is bound to the device's Hz rate and thus to the (virtual) timing the device expects. */ + pStream->State.cbTransferChunk = cbBytes; + + /* Update the transfer ticks. */ + pStream->State.cTransferTicks = ichac97R3StreamTransferCalcNext(pThis, pStream, pStream->State.cbTransferChunk); + Assert(pStream->State.cTransferTicks); /* Paranoia. */ +} + +/** + * Opens an AC'97 stream with its current mixer settings. + * + * This will open an AC'97 stream with 2 (stereo) channels, 16-bit samples and + * the last set sample rate in the AC'97 mixer for this stream. + * + * @returns IPRT status code. + * @param pThis AC'97 device state. + * @param pStream AC'97 stream to open. + */ +static int ichac97R3StreamOpen(PAC97STATE pThis, PAC97STREAM pStream) +{ + int rc = VINF_SUCCESS; + + PDMAUDIOSTREAMCFG Cfg; + RT_ZERO(Cfg); + + PAUDMIXSINK pMixSink = NULL; + + Cfg.Props.cChannels = 2; + Cfg.Props.cBytes = 2 /* 16-bit */; + Cfg.Props.fSigned = true; + Cfg.Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(Cfg.Props.cBytes, Cfg.Props.cChannels); + + switch (pStream->u8SD) + { + case AC97SOUNDSOURCE_PI_INDEX: + { + Cfg.Props.uHz = ichac97MixerGet(pThis, AC97_PCM_LR_ADC_Rate); + Cfg.enmDir = PDMAUDIODIR_IN; + Cfg.DestSource.Source = PDMAUDIORECSOURCE_LINE; + Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Line-In"); + + pMixSink = pThis->pSinkLineIn; + break; + } + + case AC97SOUNDSOURCE_MC_INDEX: + { + Cfg.Props.uHz = ichac97MixerGet(pThis, AC97_MIC_ADC_Rate); + Cfg.enmDir = PDMAUDIODIR_IN; + Cfg.DestSource.Source = PDMAUDIORECSOURCE_MIC; + Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Mic-In"); + + pMixSink = pThis->pSinkMicIn; + break; + } + + case AC97SOUNDSOURCE_PO_INDEX: + { + Cfg.Props.uHz = ichac97MixerGet(pThis, AC97_PCM_Front_DAC_Rate); + Cfg.enmDir = PDMAUDIODIR_OUT; + Cfg.DestSource.Dest = PDMAUDIOPLAYBACKDEST_FRONT; + Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Output"); + + pMixSink = pThis->pSinkOut; + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + if (RT_SUCCESS(rc)) + { + /* Only (re-)create the stream (and driver chain) if we really have to. + * Otherwise avoid this and just reuse it, as this costs performance. */ + if (!DrvAudioHlpPCMPropsAreEqual(&Cfg.Props, &pStream->State.Cfg.Props)) + { + LogFlowFunc(("[SD%RU8] uHz=%RU32\n", pStream->u8SD, Cfg.Props.uHz)); + + if (Cfg.Props.uHz) + { + Assert(Cfg.enmDir != PDMAUDIODIR_UNKNOWN); + + /* + * Set the stream's timer Hz rate, based on the PCM properties Hz rate. + */ + if (pThis->uTimerHz == AC97_TIMER_HZ_DEFAULT) /* Make sure that we don't have any custom Hz rate set we want to enforce */ + { + if (Cfg.Props.uHz > 44100) /* E.g. 48000 Hz. */ + pStream->State.uTimerHz = 200; + else /* Just take the global Hz rate otherwise. */ + pStream->State.uTimerHz = pThis->uTimerHz; + } + else + pStream->State.uTimerHz = pThis->uTimerHz; + + /* Set scheduling hint (if available). */ + if (pStream->State.uTimerHz) + Cfg.Device.uSchedulingHintMs = 1000 /* ms */ / pStream->State.uTimerHz; + + if (pStream->State.pCircBuf) + { + RTCircBufDestroy(pStream->State.pCircBuf); + pStream->State.pCircBuf = NULL; + } + + rc = RTCircBufCreate(&pStream->State.pCircBuf, DrvAudioHlpMilliToBytes(100 /* ms */, &Cfg.Props)); /** @todo Make this configurable. */ + if (RT_SUCCESS(rc)) + { + ichac97R3MixerRemoveDrvStreams(pThis, pMixSink, Cfg.enmDir, Cfg.DestSource); + + rc = ichac97R3MixerAddDrvStreams(pThis, pMixSink, &Cfg); + if (RT_SUCCESS(rc)) + rc = DrvAudioHlpStreamCfgCopy(&pStream->State.Cfg, &Cfg); + } + } + } + else + LogFlowFunc(("[SD%RU8] Skipping (re-)creation\n", pStream->u8SD)); + } + + LogFlowFunc(("[SD%RU8] rc=%Rrc\n", pStream->u8SD, rc)); + return rc; +} + +/** + * Closes an AC'97 stream. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pStream AC'97 stream to close. + */ +static int ichac97R3StreamClose(PAC97STATE pThis, PAC97STREAM pStream) +{ + RT_NOREF(pThis, pStream); + + LogFlowFunc(("[SD%RU8]\n", pStream->u8SD)); + + return VINF_SUCCESS; +} + +/** + * Re-opens (that is, closes and opens again) an AC'97 stream on the backend + * side with the current AC'97 mixer settings for this stream. + * + * @returns IPRT status code. + * @param pThis AC'97 device state. + * @param pStream AC'97 stream to re-open. + */ +static int ichac97R3StreamReOpen(PAC97STATE pThis, PAC97STREAM pStream) +{ + LogFlowFunc(("[SD%RU8]\n", pStream->u8SD)); + + int rc = ichac97R3StreamClose(pThis, pStream); + if (RT_SUCCESS(rc)) + rc = ichac97R3StreamOpen(pThis, pStream); + + return rc; +} + +/** + * Locks an AC'97 stream for serialized access. + * + * @returns IPRT status code. + * @param pStream AC'97 stream to lock. + */ +static void ichac97R3StreamLock(PAC97STREAM pStream) +{ + AssertPtrReturnVoid(pStream); + int rc2 = RTCritSectEnter(&pStream->State.CritSect); + AssertRC(rc2); +} + +/** + * Unlocks a formerly locked AC'97 stream. + * + * @returns IPRT status code. + * @param pStream AC'97 stream to unlock. + */ +static void ichac97R3StreamUnlock(PAC97STREAM pStream) +{ + AssertPtrReturnVoid(pStream); + int rc2 = RTCritSectLeave(&pStream->State.CritSect); + AssertRC(rc2); +} + +/** + * Retrieves the available size of (buffered) audio data (in bytes) of a given AC'97 stream. + * + * @returns Available data (in bytes). + * @param pStream AC'97 stream to retrieve size for. + */ +static uint32_t ichac97R3StreamGetUsed(PAC97STREAM pStream) +{ + AssertPtrReturn(pStream, 0); + + if (!pStream->State.pCircBuf) + return 0; + + return (uint32_t)RTCircBufUsed(pStream->State.pCircBuf); +} + +/** + * Retrieves the free size of audio data (in bytes) of a given AC'97 stream. + * + * @returns Free data (in bytes). + * @param pStream AC'97 stream to retrieve size for. + */ +static uint32_t ichac97R3StreamGetFree(PAC97STREAM pStream) +{ + AssertPtrReturn(pStream, 0); + + if (!pStream->State.pCircBuf) + return 0; + + return (uint32_t)RTCircBufFree(pStream->State.pCircBuf); +} + +/** + * Sets the volume of a specific AC'97 mixer control. + * + * This currently only supports attenuation -- gain support is currently not implemented. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param index AC'97 mixer index to set volume for. + * @param enmMixerCtl Corresponding audio mixer sink. + * @param uVal Volume value to set. + */ +static int ichac97R3MixerSetVolume(PAC97STATE pThis, int index, PDMAUDIOMIXERCTL enmMixerCtl, uint32_t uVal) +{ + /* + * From AC'97 SoundMax Codec AD1981A/AD1981B: + * "Because AC '97 defines 6-bit volume registers, to maintain compatibility whenever the + * D5 or D13 bits are set to 1, their respective lower five volume bits are automatically + * set to 1 by the Codec logic. On readback, all lower 5 bits will read ones whenever + * these bits are set to 1." + * + * Linux ALSA depends on this behavior to detect that only 5 bits are used for volume + * control and the optional 6th bit is not used. Note that this logic only applies to the + * master volume controls. + */ + if ((index == AC97_Master_Volume_Mute) || (index == AC97_Headphone_Volume_Mute) || (index == AC97_Master_Volume_Mono_Mute)) + { + if (uVal & RT_BIT(5)) /* D5 bit set? */ + uVal |= RT_BIT(4) | RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0); + if (uVal & RT_BIT(13)) /* D13 bit set? */ + uVal |= RT_BIT(12) | RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8); + } + + const bool fCtlMuted = (uVal >> AC97_BARS_VOL_MUTE_SHIFT) & 1; + uint8_t uCtlAttLeft = (uVal >> 8) & AC97_BARS_VOL_MASK; + uint8_t uCtlAttRight = uVal & AC97_BARS_VOL_MASK; + + /* For the master and headphone volume, 0 corresponds to 0dB attenuation. For the other + * volume controls, 0 means 12dB gain and 8 means unity gain. + */ + if (index != AC97_Master_Volume_Mute && index != AC97_Headphone_Volume_Mute) + { +# ifndef VBOX_WITH_AC97_GAIN_SUPPORT + /* NB: Currently there is no gain support, only attenuation. */ + uCtlAttLeft = uCtlAttLeft < 8 ? 0 : uCtlAttLeft - 8; + uCtlAttRight = uCtlAttRight < 8 ? 0 : uCtlAttRight - 8; +# endif + } + Assert(uCtlAttLeft <= 255 / AC97_DB_FACTOR); + Assert(uCtlAttRight <= 255 / AC97_DB_FACTOR); + + LogFunc(("index=0x%x, uVal=%RU32, enmMixerCtl=%RU32\n", index, uVal, enmMixerCtl)); + LogFunc(("uCtlAttLeft=%RU8, uCtlAttRight=%RU8 ", uCtlAttLeft, uCtlAttRight)); + + /* + * For AC'97 volume controls, each additional step means -1.5dB attenuation with + * zero being maximum. In contrast, we're internally using 255 (PDMAUDIO_VOLUME_MAX) + * steps, each -0.375dB, where 0 corresponds to -96dB and 255 corresponds to 0dB. + */ + uint8_t lVol = PDMAUDIO_VOLUME_MAX - uCtlAttLeft * AC97_DB_FACTOR; + uint8_t rVol = PDMAUDIO_VOLUME_MAX - uCtlAttRight * AC97_DB_FACTOR; + + Log(("-> fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n", fCtlMuted, lVol, rVol)); + + int rc = VINF_SUCCESS; + + if (pThis->pMixer) /* Device can be in reset state, so no mixer available. */ + { + PDMAUDIOVOLUME Vol = { fCtlMuted, lVol, rVol }; + PAUDMIXSINK pSink = NULL; + + switch (enmMixerCtl) + { + case PDMAUDIOMIXERCTL_VOLUME_MASTER: + rc = AudioMixerSetMasterVolume(pThis->pMixer, &Vol); + break; + + case PDMAUDIOMIXERCTL_FRONT: + pSink = pThis->pSinkOut; + break; + + case PDMAUDIOMIXERCTL_MIC_IN: + case PDMAUDIOMIXERCTL_LINE_IN: + /* These are recognized but do nothing. */ + break; + + default: + AssertFailed(); + rc = VERR_NOT_SUPPORTED; + break; + } + + if (pSink) + rc = AudioMixerSinkSetVolume(pSink, &Vol); + } + + ichac97MixerSet(pThis, index, uVal); + + if (RT_FAILURE(rc)) + LogFlowFunc(("Failed with %Rrc\n", rc)); + + return rc; +} + +/** + * Sets the gain of a specific AC'97 recording control. + * + * NB: gain support is currently not implemented in PDM audio. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param index AC'97 mixer index to set volume for. + * @param enmMixerCtl Corresponding audio mixer sink. + * @param uVal Volume value to set. + */ +static int ichac97R3MixerSetGain(PAC97STATE pThis, int index, PDMAUDIOMIXERCTL enmMixerCtl, uint32_t uVal) +{ + /* + * For AC'97 recording controls, each additional step means +1.5dB gain with + * zero being 0dB gain and 15 being +22.5dB gain. + */ + const bool fCtlMuted = (uVal >> AC97_BARS_VOL_MUTE_SHIFT) & 1; + uint8_t uCtlGainLeft = (uVal >> 8) & AC97_BARS_GAIN_MASK; + uint8_t uCtlGainRight = uVal & AC97_BARS_GAIN_MASK; + + Assert(uCtlGainLeft <= 255 / AC97_DB_FACTOR); + Assert(uCtlGainRight <= 255 / AC97_DB_FACTOR); + + LogFunc(("index=0x%x, uVal=%RU32, enmMixerCtl=%RU32\n", index, uVal, enmMixerCtl)); + LogFunc(("uCtlGainLeft=%RU8, uCtlGainRight=%RU8 ", uCtlGainLeft, uCtlGainRight)); + + uint8_t lVol = PDMAUDIO_VOLUME_MAX + uCtlGainLeft * AC97_DB_FACTOR; + uint8_t rVol = PDMAUDIO_VOLUME_MAX + uCtlGainRight * AC97_DB_FACTOR; + + /* We do not currently support gain. Since AC'97 does not support attenuation + * for the recording input, the best we can do is set the maximum volume. + */ +# ifndef VBOX_WITH_AC97_GAIN_SUPPORT + /* NB: Currently there is no gain support, only attenuation. Since AC'97 does not + * support attenuation for the recording inputs, the best we can do is set the + * maximum volume. + */ + lVol = rVol = PDMAUDIO_VOLUME_MAX; +# endif + + Log(("-> fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n", fCtlMuted, lVol, rVol)); + + int rc = VINF_SUCCESS; + + if (pThis->pMixer) /* Device can be in reset state, so no mixer available. */ + { + PDMAUDIOVOLUME Vol = { fCtlMuted, lVol, rVol }; + PAUDMIXSINK pSink = NULL; + + switch (enmMixerCtl) + { + case PDMAUDIOMIXERCTL_MIC_IN: + pSink = pThis->pSinkMicIn; + break; + + case PDMAUDIOMIXERCTL_LINE_IN: + pSink = pThis->pSinkLineIn; + break; + + default: + AssertFailed(); + rc = VERR_NOT_SUPPORTED; + break; + } + + if (pSink) { + rc = AudioMixerSinkSetVolume(pSink, &Vol); + /* There is only one AC'97 recording gain control. If line in + * is changed, also update the microphone. If the optional dedicated + * microphone is changed, only change that. + * NB: The codecs we support do not have the dedicated microphone control. + */ + if ((pSink == pThis->pSinkLineIn) && pThis->pSinkMicIn) + rc = AudioMixerSinkSetVolume(pSink, &Vol); + } + } + + ichac97MixerSet(pThis, index, uVal); + + if (RT_FAILURE(rc)) + LogFlowFunc(("Failed with %Rrc\n", rc)); + + return rc; +} + +/** + * Converts an AC'97 recording source index to a PDM audio recording source. + * + * @returns PDM audio recording source. + * @param uIdx AC'97 index to convert. + */ +static PDMAUDIORECSOURCE ichac97R3IdxToRecSource(uint8_t uIdx) +{ + switch (uIdx) + { + case AC97_REC_MIC: return PDMAUDIORECSOURCE_MIC; + case AC97_REC_CD: return PDMAUDIORECSOURCE_CD; + case AC97_REC_VIDEO: return PDMAUDIORECSOURCE_VIDEO; + case AC97_REC_AUX: return PDMAUDIORECSOURCE_AUX; + case AC97_REC_LINE_IN: return PDMAUDIORECSOURCE_LINE; + case AC97_REC_PHONE: return PDMAUDIORECSOURCE_PHONE; + default: + break; + } + + LogFlowFunc(("Unknown record source %d, using MIC\n", uIdx)); + return PDMAUDIORECSOURCE_MIC; +} + +/** + * Converts a PDM audio recording source to an AC'97 recording source index. + * + * @returns AC'97 recording source index. + * @param enmRecSrc PDM audio recording source to convert. + */ +static uint8_t ichac97R3RecSourceToIdx(PDMAUDIORECSOURCE enmRecSrc) +{ + switch (enmRecSrc) + { + case PDMAUDIORECSOURCE_MIC: return AC97_REC_MIC; + case PDMAUDIORECSOURCE_CD: return AC97_REC_CD; + case PDMAUDIORECSOURCE_VIDEO: return AC97_REC_VIDEO; + case PDMAUDIORECSOURCE_AUX: return AC97_REC_AUX; + case PDMAUDIORECSOURCE_LINE: return AC97_REC_LINE_IN; + case PDMAUDIORECSOURCE_PHONE: return AC97_REC_PHONE; + default: + break; + } + + LogFlowFunc(("Unknown audio recording source %d using MIC\n", enmRecSrc)); + return AC97_REC_MIC; +} + +/** + * Returns the audio direction of a specified stream descriptor. + * + * @return Audio direction. + */ +DECLINLINE(PDMAUDIODIR) ichac97GetDirFromSD(uint8_t uSD) +{ + switch (uSD) + { + case AC97SOUNDSOURCE_PI_INDEX: return PDMAUDIODIR_IN; + case AC97SOUNDSOURCE_PO_INDEX: return PDMAUDIODIR_OUT; + case AC97SOUNDSOURCE_MC_INDEX: return PDMAUDIODIR_IN; + } + + AssertFailed(); + return PDMAUDIODIR_UNKNOWN; +} + +#endif /* IN_RING3 */ + +#ifdef IN_RING3 + +/** + * Performs an AC'97 mixer record select to switch to a different recording + * source. + * + * @param pThis AC'97 state. + * @param val AC'97 recording source index to set. + */ +static void ichac97R3MixerRecordSelect(PAC97STATE pThis, uint32_t val) +{ + uint8_t rs = val & AC97_REC_MASK; + uint8_t ls = (val >> 8) & AC97_REC_MASK; + PDMAUDIORECSOURCE ars = ichac97R3IdxToRecSource(rs); + PDMAUDIORECSOURCE als = ichac97R3IdxToRecSource(ls); + rs = ichac97R3RecSourceToIdx(ars); + ls = ichac97R3RecSourceToIdx(als); + ichac97MixerSet(pThis, AC97_Record_Select, rs | (ls << 8)); +} + +/** + * Resets the AC'97 mixer. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + */ +static int ichac97R3MixerReset(PAC97STATE pThis) +{ + AssertPtrReturn(pThis, VERR_INVALID_PARAMETER); + + LogFlowFuncEnter(); + + RT_ZERO(pThis->mixer_data); + + /* Note: Make sure to reset all registers first before bailing out on error. */ + + ichac97MixerSet(pThis, AC97_Reset , 0x0000); /* 6940 */ + ichac97MixerSet(pThis, AC97_Master_Volume_Mono_Mute , 0x8000); + ichac97MixerSet(pThis, AC97_PC_BEEP_Volume_Mute , 0x0000); + + ichac97MixerSet(pThis, AC97_Phone_Volume_Mute , 0x8008); + ichac97MixerSet(pThis, AC97_Mic_Volume_Mute , 0x8008); + ichac97MixerSet(pThis, AC97_CD_Volume_Mute , 0x8808); + ichac97MixerSet(pThis, AC97_Aux_Volume_Mute , 0x8808); + ichac97MixerSet(pThis, AC97_Record_Gain_Mic_Mute , 0x8000); + ichac97MixerSet(pThis, AC97_General_Purpose , 0x0000); + ichac97MixerSet(pThis, AC97_3D_Control , 0x0000); + ichac97MixerSet(pThis, AC97_Powerdown_Ctrl_Stat , 0x000f); + + /* Configure Extended Audio ID (EAID) + Control & Status (EACS) registers. */ + uint16_t fEAID = AC97_EAID_REV1; /* Our hardware is AC'97 rev2.3 compliant. */ + uint16_t fEACS = 0; +#ifdef VBOX_WITH_AC97_VRA + fEAID |= AC97_EAID_VRA; /* Variable Rate PCM Audio capable. */ + fEACS |= AC97_EACS_VRA; /* Ditto. */ +#endif +#ifdef VBOX_WITH_AC97_VRM + fEAID |= AC97_EAID_VRM; /* Variable Rate Mic-In Audio capable. */ + fEACS |= AC97_EACS_VRM; /* Ditto. */ +#endif + + ichac97MixerSet(pThis, AC97_Extended_Audio_ID, fEAID); + ichac97MixerSet(pThis, AC97_Extended_Audio_Ctrl_Stat, fEACS); + ichac97MixerSet(pThis, AC97_PCM_Front_DAC_Rate , 0xbb80); + ichac97MixerSet(pThis, AC97_PCM_Surround_DAC_Rate , 0xbb80); + ichac97MixerSet(pThis, AC97_PCM_LFE_DAC_Rate , 0xbb80); + ichac97MixerSet(pThis, AC97_PCM_LR_ADC_Rate , 0xbb80); + ichac97MixerSet(pThis, AC97_MIC_ADC_Rate , 0xbb80); + + if (pThis->uCodecModel == AC97_CODEC_AD1980) + { + /* Analog Devices 1980 (AD1980) */ + ichac97MixerSet(pThis, AC97_Reset , 0x0010); /* Headphones. */ + ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x4144); + ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x5370); + ichac97MixerSet(pThis, AC97_Headphone_Volume_Mute , 0x8000); + } + else if (pThis->uCodecModel == AC97_CODEC_AD1981B) + { + /* Analog Devices 1981B (AD1981B) */ + ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x4144); + ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x5374); + } + else + { + /* Sigmatel 9700 (STAC9700) */ + ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x8384); + ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x7600); /* 7608 */ + } + ichac97R3MixerRecordSelect(pThis, 0); + + /* The default value is 8000h, which corresponds to 0 dB attenuation with mute on. */ + ichac97R3MixerSetVolume(pThis, AC97_Master_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER, 0x8000); + + /* The default value for stereo registers is 8808h, which corresponds to 0 dB gain with mute on.*/ + ichac97R3MixerSetVolume(pThis, AC97_PCM_Out_Volume_Mute, PDMAUDIOMIXERCTL_FRONT, 0x8808); + ichac97R3MixerSetVolume(pThis, AC97_Line_In_Volume_Mute, PDMAUDIOMIXERCTL_LINE_IN, 0x8808); + ichac97R3MixerSetVolume(pThis, AC97_Mic_Volume_Mute, PDMAUDIOMIXERCTL_MIC_IN, 0x8008); + + /* The default for record controls is 0 dB gain with mute on. */ + ichac97R3MixerSetGain(pThis, AC97_Record_Gain_Mute, PDMAUDIOMIXERCTL_LINE_IN, 0x8000); + ichac97R3MixerSetGain(pThis, AC97_Record_Gain_Mic_Mute, PDMAUDIOMIXERCTL_MIC_IN, 0x8000); + + return VINF_SUCCESS; +} + +# if 0 /* Unused */ +static void ichac97R3WriteBUP(PAC97STATE pThis, uint32_t cbElapsed) +{ + LogFlowFunc(("cbElapsed=%RU32\n", cbElapsed)); + + if (!(pThis->bup_flag & BUP_SET)) + { + if (pThis->bup_flag & BUP_LAST) + { + unsigned int i; + uint32_t *p = (uint32_t*)pThis->silence; + for (i = 0; i < sizeof(pThis->silence) / 4; i++) /** @todo r=andy Assumes 16-bit samples, stereo. */ + *p++ = pThis->last_samp; + } + else + RT_ZERO(pThis->silence); + + pThis->bup_flag |= BUP_SET; + } + + while (cbElapsed) + { + uint32_t cbToWrite = RT_MIN(cbElapsed, (uint32_t)sizeof(pThis->silence)); + uint32_t cbWrittenToStream; + + int rc2 = AudioMixerSinkWrite(pThis->pSinkOut, AUDMIXOP_COPY, + pThis->silence, cbToWrite, &cbWrittenToStream); + if (RT_SUCCESS(rc2)) + { + if (cbWrittenToStream < cbToWrite) /* Lagging behind? */ + LogFlowFunc(("Warning: Only written %RU32 / %RU32 bytes, expect lags\n", cbWrittenToStream, cbToWrite)); + } + + /* Always report all data as being written; + * backends who were not able to catch up have to deal with it themselves. */ + Assert(cbElapsed >= cbToWrite); + cbElapsed -= cbToWrite; + } +} +# endif /* Unused */ + +/** + * Timer callback which handles the audio data transfers on a periodic basis. + * + * @param pDevIns Device instance. + * @param pTimer Timer which was used when calling this. + * @param pvUser User argument as PAC97STATE. + */ +static DECLCALLBACK(void) ichac97R3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) +{ + RT_NOREF(pDevIns, pTimer); + + PAC97STREAM pStream = (PAC97STREAM)pvUser; + AssertPtr(pStream); + + PAC97STATE pThis = pStream->pAC97State; + AssertPtr(pThis); + + STAM_PROFILE_START(&pThis->StatTimer, a); + + DEVAC97_LOCK_BOTH_RETURN_VOID(pThis, pStream->u8SD); + + ichac97R3StreamUpdate(pThis, pStream, true /* fInTimer */); + + PAUDMIXSINK pSink = ichac97R3IndexToSink(pThis, pStream->u8SD); + + bool fSinkActive = false; + if (pSink) + fSinkActive = AudioMixerSinkIsActive(pSink); + + if (fSinkActive) + { + ichac97R3StreamTransferUpdate(pThis, pStream, pStream->Regs.picb << 1); /** @todo r=andy Assumes 16-bit samples. */ + + ichac97TimerSet(pThis,pStream, + TMTimerGet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD)) + pStream->State.cTransferTicks, + false /* fForce */); + } + + DEVAC97_UNLOCK_BOTH(pThis, pStream->u8SD); + + STAM_PROFILE_STOP(&pThis->StatTimer, a); +} +#endif /* IN_RING3 */ + +/** + * Sets the virtual device timer to a new expiration time. + * + * @returns Whether the new expiration time was set or not. + * @param pThis AC'97 state. + * @param pStream AC'97 stream to set timer for. + * @param tsExpire New (virtual) expiration time to set. + * @param fForce Whether to force setting the expiration time or not. + * + * @remark This function takes all active AC'97 streams and their + * current timing into account. This is needed to make sure + * that all streams can match their needed timing. + * + * To achieve this, the earliest (lowest) timestamp of all + * active streams found will be used for the next scheduling slot. + * + * Forcing a new expiration time will override the above mechanism. + */ +bool ichac97TimerSet(PAC97STATE pThis, PAC97STREAM pStream, uint64_t tsExpire, bool fForce) +{ + AssertPtrReturn(pThis, false); + AssertPtrReturn(pStream, false); + + RT_NOREF(fForce); + + uint64_t tsExpireMin = tsExpire; + + AssertPtr((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD)); + + const uint64_t tsNow = TMTimerGet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD)); + + /* Make sure to not go backwards in time, as this will assert in TMTimerSet(). */ + if (tsExpireMin < tsNow) + tsExpireMin = tsNow; + + int rc = TMTimerSet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD), tsExpireMin); + AssertRC(rc); + + return RT_SUCCESS(rc); +} + +#ifdef IN_RING3 + +/** + * Transfers data of an AC'97 stream according to its usage (input / output). + * + * For an SDO (output) stream this means reading DMA data from the device to + * the AC'97 stream's internal FIFO buffer. + * + * For an SDI (input) stream this is reading audio data from the AC'97 stream's + * internal FIFO buffer and writing it as DMA data to the device. + * + * @returns IPRT status code. + * @param pThis AC'97 state. + * @param pStream AC'97 stream to update. + * @param cbToProcessMax Maximum of data (in bytes) to process. + */ +static int ichac97R3StreamTransfer(PAC97STATE pThis, PAC97STREAM pStream, uint32_t cbToProcessMax) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + if (!cbToProcessMax) + return VINF_SUCCESS; + +#ifdef VBOX_STRICT + const unsigned cbFrame = DrvAudioHlpPCMPropsBytesPerFrame(&pStream->State.Cfg.Props); +#endif + + /* Make sure to only process an integer number of audio frames. */ + Assert(cbToProcessMax % cbFrame == 0); + + ichac97R3StreamLock(pStream); + + PAC97BMREGS pRegs = &pStream->Regs; + + if (pRegs->sr & AC97_SR_DCH) /* Controller halted? */ + { + if (pRegs->cr & AC97_CR_RPBM) /* Bus master operation starts. */ + { + switch (pStream->u8SD) + { + case AC97SOUNDSOURCE_PO_INDEX: + /*ichac97R3WriteBUP(pThis, cbToProcess);*/ + break; + + default: + break; + } + } + + ichac97R3StreamUnlock(pStream); + return VINF_SUCCESS; + } + + /* BCIS flag still set? Skip iteration. */ + if (pRegs->sr & AC97_SR_BCIS) + { + Log3Func(("[SD%RU8] BCIS set\n", pStream->u8SD)); + + ichac97R3StreamUnlock(pStream); + return VINF_SUCCESS; + } + + uint32_t cbLeft = RT_MIN((uint32_t)(pRegs->picb << 1), cbToProcessMax); /** @todo r=andy Assumes 16bit samples. */ + uint32_t cbProcessedTotal = 0; + + PRTCIRCBUF pCircBuf = pStream->State.pCircBuf; + AssertPtr(pCircBuf); + + int rc = VINF_SUCCESS; + + Log3Func(("[SD%RU8] cbToProcessMax=%RU32, cbLeft=%RU32\n", pStream->u8SD, cbToProcessMax, cbLeft)); + + while (cbLeft) + { + if (!pRegs->picb) /* Got a new buffer descriptor, that is, the position is 0? */ + { + Log3Func(("Fresh buffer descriptor %RU8 is empty, addr=%#x, len=%#x, skipping\n", + pRegs->civ, pRegs->bd.addr, pRegs->bd.ctl_len)); + if (pRegs->civ == pRegs->lvi) + { + pRegs->sr |= AC97_SR_DCH; /** @todo r=andy Also set CELV? */ + pThis->bup_flag = 0; + + rc = VINF_EOF; + break; + } + + pRegs->sr &= ~AC97_SR_CELV; + pRegs->civ = pRegs->piv; + pRegs->piv = (pRegs->piv + 1) % AC97_MAX_BDLE; + + ichac97R3StreamFetchBDLE(pThis, pStream); + continue; + } + + uint32_t cbChunk = cbLeft; + + switch (pStream->u8SD) + { + case AC97SOUNDSOURCE_PO_INDEX: /* Output */ + { + void *pvDst; + size_t cbDst; + + RTCircBufAcquireWriteBlock(pCircBuf, cbChunk, &pvDst, &cbDst); + + if (cbDst) + { + int rc2 = PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), pRegs->bd.addr, (uint8_t *)pvDst, cbDst); + AssertRC(rc2); + + if (pStream->Dbg.Runtime.fEnabled) + DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMA, pvDst, cbDst, 0 /* fFlags */); + } + + RTCircBufReleaseWriteBlock(pCircBuf, cbDst); + + cbChunk = (uint32_t)cbDst; /* Update the current chunk size to what really has been written. */ + break; + } + + case AC97SOUNDSOURCE_PI_INDEX: /* Input */ + case AC97SOUNDSOURCE_MC_INDEX: /* Input */ + { + void *pvSrc; + size_t cbSrc; + + RTCircBufAcquireReadBlock(pCircBuf, cbChunk, &pvSrc, &cbSrc); + + if (cbSrc) + { +/** @todo r=bird: Just curious, DevHDA uses PDMDevHlpPCIPhysWrite here. So, + * is AC97 not subject to PCI busmaster enable/disable? */ + int rc2 = PDMDevHlpPhysWrite(pThis->CTX_SUFF(pDevIns), pRegs->bd.addr, (uint8_t *)pvSrc, cbSrc); + AssertRC(rc2); + + if (pStream->Dbg.Runtime.fEnabled) + DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMA, pvSrc, cbSrc, 0 /* fFlags */); + } + + RTCircBufReleaseReadBlock(pCircBuf, cbSrc); + + cbChunk = (uint32_t)cbSrc; /* Update the current chunk size to what really has been read. */ + break; + } + + default: + AssertMsgFailed(("Stream #%RU8 not supported\n", pStream->u8SD)); + rc = VERR_NOT_SUPPORTED; + break; + } + + if (RT_FAILURE(rc)) + break; + + if (cbChunk) + { + cbProcessedTotal += cbChunk; + Assert(cbProcessedTotal <= cbToProcessMax); + Assert(cbLeft >= cbChunk); + cbLeft -= cbChunk; + Assert((cbChunk & 1) == 0); /* Else the following shift won't work */ + + pRegs->picb -= (cbChunk >> 1); /** @todo r=andy Assumes 16bit samples. */ + pRegs->bd.addr += cbChunk; + } + + LogFlowFunc(("[SD%RU8] cbChunk=%RU32, cbLeft=%RU32, cbTotal=%RU32, rc=%Rrc\n", + pStream->u8SD, cbChunk, cbLeft, cbProcessedTotal, rc)); + + if (!pRegs->picb) + { + uint32_t new_sr = pRegs->sr & ~AC97_SR_CELV; + + if (pRegs->bd.ctl_len & AC97_BD_IOC) + { + new_sr |= AC97_SR_BCIS; + } + + if (pRegs->civ == pRegs->lvi) + { + /* Did we run out of data? */ + LogFunc(("Underrun CIV (%RU8) == LVI (%RU8)\n", pRegs->civ, pRegs->lvi)); + + new_sr |= AC97_SR_LVBCI | AC97_SR_DCH | AC97_SR_CELV; + pThis->bup_flag = (pRegs->bd.ctl_len & AC97_BD_BUP) ? BUP_LAST : 0; + + rc = VINF_EOF; + } + else + { + pRegs->civ = pRegs->piv; + pRegs->piv = (pRegs->piv + 1) % AC97_MAX_BDLE; + ichac97R3StreamFetchBDLE(pThis, pStream); + } + + ichac97StreamUpdateSR(pThis, pStream, new_sr); + } + + if (/* All data processed? */ + rc == VINF_EOF + /* ... or an error occurred? */ + || RT_FAILURE(rc)) + { + break; + } + } + + ichac97R3StreamUnlock(pStream); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#endif /* IN_RING3 */ + + +/** + * Port I/O Handler for IN operations. + * + * @returns VINF_SUCCESS or VINF_EM_*. + * @returns VERR_IOM_IOPORT_UNUSED if the port is really unused and a ~0 value should be returned. + * + * @param pDevIns The device instance. + * @param pvUser User argument. + * @param uPort Port number used for the IN operation. + * @param pu32Val Where to store the result. This is always a 32-bit + * variable regardless of what @a cbVal might say. + * @param cbVal Number of bytes read. + */ +PDMBOTHCBDECL(int) ichac97IOPortNABMRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t *pu32Val, unsigned cbVal) +{ + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + RT_NOREF(pvUser); + + DEVAC97_LOCK_RETURN(pThis, VINF_IOM_R3_IOPORT_READ); + + /* Get the index of the NABMBAR port. */ + const uint32_t uPortIdx = uPort - pThis->IOPortBase[1]; + + PAC97STREAM pStream = NULL; + PAC97BMREGS pRegs = NULL; + + if (AC97_PORT2IDX(uPortIdx) < AC97_MAX_STREAMS) + { + pStream = &pThis->aStreams[AC97_PORT2IDX(uPortIdx)]; + AssertPtr(pStream); + pRegs = &pStream->Regs; + } + + int rc = VINF_SUCCESS; + + switch (cbVal) + { + case 1: + { + switch (uPortIdx) + { + case AC97_CAS: + /* Codec Access Semaphore Register */ + Log3Func(("CAS %d\n", pThis->cas)); + *pu32Val = pThis->cas; + pThis->cas = 1; + break; + case PI_CIV: + case PO_CIV: + case MC_CIV: + /* Current Index Value Register */ + *pu32Val = pRegs->civ; + Log3Func(("CIV[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val)); + break; + case PI_LVI: + case PO_LVI: + case MC_LVI: + /* Last Valid Index Register */ + *pu32Val = pRegs->lvi; + Log3Func(("LVI[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val)); + break; + case PI_PIV: + case PO_PIV: + case MC_PIV: + /* Prefetched Index Value Register */ + *pu32Val = pRegs->piv; + Log3Func(("PIV[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val)); + break; + case PI_CR: + case PO_CR: + case MC_CR: + /* Control Register */ + *pu32Val = pRegs->cr; + Log3Func(("CR[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val)); + break; + case PI_SR: + case PO_SR: + case MC_SR: + /* Status Register (lower part) */ + *pu32Val = RT_LO_U8(pRegs->sr); + Log3Func(("SRb[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val)); + break; + default: + *pu32Val = UINT32_MAX; + LogFunc(("U nabm readb %#x -> %#x\n", uPort, *pu32Val)); + break; + } + break; + } + + case 2: + { + switch (uPortIdx) + { + case PI_SR: + case PO_SR: + case MC_SR: + /* Status Register */ + *pu32Val = pRegs->sr; + Log3Func(("SR[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val)); + break; + case PI_PICB: + case PO_PICB: + case MC_PICB: + /* Position in Current Buffer */ + *pu32Val = pRegs->picb; + Log3Func(("PICB[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val)); + break; + default: + *pu32Val = UINT32_MAX; + LogFunc(("U nabm readw %#x -> %#x\n", uPort, *pu32Val)); + break; + } + break; + } + + case 4: + { + switch (uPortIdx) + { + case PI_BDBAR: + case PO_BDBAR: + case MC_BDBAR: + /* Buffer Descriptor Base Address Register */ + *pu32Val = pRegs->bdbar; + Log3Func(("BMADDR[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val)); + break; + case PI_CIV: + case PO_CIV: + case MC_CIV: + /* 32-bit access: Current Index Value Register + + * Last Valid Index Register + + * Status Register */ + *pu32Val = pRegs->civ | (pRegs->lvi << 8) | (pRegs->sr << 16); /** @todo r=andy Use RT_MAKE_U32_FROM_U8. */ + Log3Func(("CIV LVI SR[%d] -> %#x, %#x, %#x\n", + AC97_PORT2IDX(uPortIdx), pRegs->civ, pRegs->lvi, pRegs->sr)); + break; + case PI_PICB: + case PO_PICB: + case MC_PICB: + /* 32-bit access: Position in Current Buffer Register + + * Prefetched Index Value Register + + * Control Register */ + *pu32Val = pRegs->picb | (pRegs->piv << 16) | (pRegs->cr << 24); /** @todo r=andy Use RT_MAKE_U32_FROM_U8. */ + Log3Func(("PICB PIV CR[%d] -> %#x %#x %#x %#x\n", + AC97_PORT2IDX(uPortIdx), *pu32Val, pRegs->picb, pRegs->piv, pRegs->cr)); + break; + case AC97_GLOB_CNT: + /* Global Control */ + *pu32Val = pThis->glob_cnt; + Log3Func(("glob_cnt -> %#x\n", *pu32Val)); + break; + case AC97_GLOB_STA: + /* Global Status */ + *pu32Val = pThis->glob_sta | AC97_GS_S0CR; + Log3Func(("glob_sta -> %#x\n", *pu32Val)); + break; + default: + *pu32Val = UINT32_MAX; + LogFunc(("U nabm readl %#x -> %#x\n", uPort, *pu32Val)); + break; + } + break; + } + + default: + { + AssertFailed(); + rc = VERR_IOM_IOPORT_UNUSED; + } + } + + DEVAC97_UNLOCK(pThis); + + return rc; +} + +/** + * Port I/O Handler for OUT operations. + * + * @returns VINF_SUCCESS or VINF_EM_*. + * + * @param pDevIns The device instance. + * @param pvUser User argument. + * @param uPort Port number used for the OUT operation. + * @param u32Val The value to output. + * @param cbVal The value size in bytes. + */ +PDMBOTHCBDECL(int) ichac97IOPortNABMWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t u32Val, unsigned cbVal) +{ + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + RT_NOREF(pvUser); + + /* Get the index of the NABMBAR register. */ + const uint32_t uPortIdx = uPort - pThis->IOPortBase[1]; + + PAC97STREAM pStream = NULL; + PAC97BMREGS pRegs = NULL; + + if (AC97_PORT2IDX(uPortIdx) < AC97_MAX_STREAMS) + { + pStream = &pThis->aStreams[AC97_PORT2IDX(uPortIdx)]; + AssertPtr(pStream); + pRegs = &pStream->Regs; + + DEVAC97_LOCK_BOTH_RETURN(pThis, pStream->u8SD, VINF_IOM_R3_IOPORT_WRITE); + } + + int rc = VINF_SUCCESS; + switch (cbVal) + { + case 1: + { + switch (uPortIdx) + { + /* + * Last Valid Index. + */ + case PI_LVI: + case PO_LVI: + case MC_LVI: + { + AssertPtr(pStream); + AssertPtr(pRegs); + if ( (pRegs->cr & AC97_CR_RPBM) + && (pRegs->sr & AC97_SR_DCH)) + { +#ifdef IN_RING3 + pRegs->sr &= ~(AC97_SR_DCH | AC97_SR_CELV); + pRegs->civ = pRegs->piv; + pRegs->piv = (pRegs->piv + 1) % AC97_MAX_BDLE; +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + } + pRegs->lvi = u32Val % AC97_MAX_BDLE; + Log3Func(("[SD%RU8] LVI <- %#x\n", pStream->u8SD, u32Val)); + break; + } + + /* + * Control Registers. + */ + case PI_CR: + case PO_CR: + case MC_CR: + { + AssertPtr(pStream); + AssertPtr(pRegs); +#ifdef IN_RING3 + Log3Func(("[SD%RU8] CR <- %#x (cr %#x)\n", pStream->u8SD, u32Val, pRegs->cr)); + if (u32Val & AC97_CR_RR) /* Busmaster reset. */ + { + Log3Func(("[SD%RU8] Reset\n", pStream->u8SD)); + + /* Make sure that Run/Pause Bus Master bit (RPBM) is cleared (0). */ + Assert((pRegs->cr & AC97_CR_RPBM) == 0); + + ichac97R3StreamEnable(pThis, pStream, false /* fEnable */); + ichac97R3StreamReset(pThis, pStream); + + ichac97StreamUpdateSR(pThis, pStream, AC97_SR_DCH); /** @todo Do we need to do that? */ + } + else + { + pRegs->cr = u32Val & AC97_CR_VALID_MASK; + + if (!(pRegs->cr & AC97_CR_RPBM)) + { + Log3Func(("[SD%RU8] Disable\n", pStream->u8SD)); + + ichac97R3StreamEnable(pThis, pStream, false /* fEnable */); + + pRegs->sr |= AC97_SR_DCH; + } + else + { + Log3Func(("[SD%RU8] Enable\n", pStream->u8SD)); + + pRegs->civ = pRegs->piv; + pRegs->piv = (pRegs->piv + 1) % AC97_MAX_BDLE; + + pRegs->sr &= ~AC97_SR_DCH; + + /* Fetch the initial BDLE descriptor. */ + ichac97R3StreamFetchBDLE(pThis, pStream); +# ifdef LOG_ENABLED + ichac97R3BDLEDumpAll(pThis, pStream->Regs.bdbar, pStream->Regs.lvi + 1); +# endif + ichac97R3StreamEnable(pThis, pStream, true /* fEnable */); + + /* Arm the timer for this stream. */ + int rc2 = ichac97TimerSet(pThis, pStream, + TMTimerGet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD)) + pStream->State.cTransferTicks, + false /* fForce */); + AssertRC(rc2); + } + } +#else /* !IN_RING3 */ + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + break; + } + + /* + * Status Registers. + */ + case PI_SR: + case PO_SR: + case MC_SR: + { + ichac97StreamWriteSR(pThis, pStream, u32Val); + break; + } + + default: + LogRel2(("AC97: Warning: Unimplemented NABMWrite (%u byte) portIdx=%#x <- %#x\n", cbVal, uPortIdx, u32Val)); + break; + } + break; + } + + case 2: + { + switch (uPortIdx) + { + case PI_SR: + case PO_SR: + case MC_SR: + ichac97StreamWriteSR(pThis, pStream, u32Val); + break; + default: + LogRel2(("AC97: Warning: Unimplemented NABMWrite (%u byte) portIdx=%#x <- %#x\n", cbVal, uPortIdx, u32Val)); + break; + } + break; + } + + case 4: + { + switch (uPortIdx) + { + case PI_BDBAR: + case PO_BDBAR: + case MC_BDBAR: + AssertPtr(pStream); + AssertPtr(pRegs); + /* Buffer Descriptor list Base Address Register */ + pRegs->bdbar = u32Val & ~3; + Log3Func(("[SD%RU8] BDBAR <- %#x (bdbar %#x)\n", AC97_PORT2IDX(uPortIdx), u32Val, pRegs->bdbar)); + break; + case AC97_GLOB_CNT: + /* Global Control */ + if (u32Val & AC97_GC_WR) + ichac97WarmReset(pThis); + if (u32Val & AC97_GC_CR) + ichac97ColdReset(pThis); + if (!(u32Val & (AC97_GC_WR | AC97_GC_CR))) + pThis->glob_cnt = u32Val & AC97_GC_VALID_MASK; + Log3Func(("glob_cnt <- %#x (glob_cnt %#x)\n", u32Val, pThis->glob_cnt)); + break; + case AC97_GLOB_STA: + /* Global Status */ + pThis->glob_sta &= ~(u32Val & AC97_GS_WCLEAR_MASK); + pThis->glob_sta |= (u32Val & ~(AC97_GS_WCLEAR_MASK | AC97_GS_RO_MASK)) & AC97_GS_VALID_MASK; + Log3Func(("glob_sta <- %#x (glob_sta %#x)\n", u32Val, pThis->glob_sta)); + break; + default: + LogRel2(("AC97: Warning: Unimplemented NABMWrite (%u byte) portIdx=%#x <- %#x\n", cbVal, uPortIdx, u32Val)); + break; + } + break; + } + + default: + LogRel2(("AC97: Warning: Unimplemented NABMWrite (%u byte) portIdx=%#x <- %#x\n", cbVal, uPortIdx, u32Val)); + break; + } + + if (pStream) + DEVAC97_UNLOCK_BOTH(pThis, pStream->u8SD); + + return rc; +} + +/** + * Port I/O Handler for IN operations. + * + * @returns VINF_SUCCESS or VINF_EM_*. + * @returns VERR_IOM_IOPORT_UNUSED if the port is really unused and a ~0 value should be returned. + * + * @param pDevIns The device instance. + * @param pvUser User argument. + * @param uPort Port number used for the IN operation. + * @param pu32Val Where to store the result. This is always a 32-bit + * variable regardless of what @a cbVal might say. + * @param cbVal Number of bytes read. + */ +PDMBOTHCBDECL(int) ichac97IOPortNAMRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t *pu32Val, unsigned cbVal) +{ + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + RT_NOREF(pvUser); + + DEVAC97_LOCK_RETURN(pThis, VINF_IOM_R3_IOPORT_READ); + + int rc = VINF_SUCCESS; + + uint32_t index = uPort - pThis->IOPortBase[0]; + Assert(index < 256); + + switch (cbVal) + { + case 1: + { + LogRel2(("AC97: Warning: Unimplemented read (%u byte) port=%#x, idx=%RU32\n", cbVal, uPort, index)); + pThis->cas = 0; + *pu32Val = UINT32_MAX; + break; + } + + case 2: + { + pThis->cas = 0; + *pu32Val = ichac97MixerGet(pThis, index); + break; + } + + case 4: + { + LogRel2(("AC97: Warning: Unimplemented read (%u byte) port=%#x, idx=%RU32\n", cbVal, uPort, index)); + pThis->cas = 0; + *pu32Val = UINT32_MAX; + break; + } + + default: + { + AssertFailed(); + rc = VERR_IOM_IOPORT_UNUSED; + } + } + + DEVAC97_UNLOCK(pThis); + + return rc; +} + +/** + * Port I/O Handler for OUT operations. + * + * @returns VINF_SUCCESS or VINF_EM_*. + * + * @param pDevIns The device instance. + * @param pvUser User argument. + * @param uPort Port number used for the OUT operation. + * @param u32Val The value to output. + * @param cbVal The value size in bytes. + * @remarks Caller enters the device critical section. + */ +PDMBOTHCBDECL(int) ichac97IOPortNAMWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t u32Val, unsigned cbVal) +{ + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + RT_NOREF(pvUser); + + DEVAC97_LOCK_RETURN(pThis, VINF_IOM_R3_IOPORT_WRITE); + + uint32_t uPortIdx = uPort - pThis->IOPortBase[0]; + + int rc = VINF_SUCCESS; + switch (cbVal) + { + case 1: + { + LogRel2(("AC97: Warning: Unimplemented NAMWrite (%u byte) port=%#x, idx=0x%x <- %#x\n", cbVal, uPort, uPortIdx, u32Val)); + pThis->cas = 0; + break; + } + + case 2: + { + pThis->cas = 0; + switch (uPortIdx) + { + case AC97_Reset: +#ifdef IN_RING3 + ichac97R3Reset(pThis->CTX_SUFF(pDevIns)); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + break; + case AC97_Powerdown_Ctrl_Stat: + u32Val &= ~0xf; + u32Val |= ichac97MixerGet(pThis, uPortIdx) & 0xf; + ichac97MixerSet(pThis, uPortIdx, u32Val); + break; + case AC97_Master_Volume_Mute: + if (pThis->uCodecModel == AC97_CODEC_AD1980) + { + if (ichac97MixerGet(pThis, AC97_AD_Misc) & AC97_AD_MISC_LOSEL) + break; /* Register controls surround (rear), do nothing. */ + } +#ifdef IN_RING3 + ichac97R3MixerSetVolume(pThis, uPortIdx, PDMAUDIOMIXERCTL_VOLUME_MASTER, u32Val); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + break; + case AC97_Headphone_Volume_Mute: + if (pThis->uCodecModel == AC97_CODEC_AD1980) + { + if (ichac97MixerGet(pThis, AC97_AD_Misc) & AC97_AD_MISC_HPSEL) + { + /* Register controls PCM (front) outputs. */ +#ifdef IN_RING3 + ichac97R3MixerSetVolume(pThis, uPortIdx, PDMAUDIOMIXERCTL_VOLUME_MASTER, u32Val); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + } + } + break; + case AC97_PCM_Out_Volume_Mute: +#ifdef IN_RING3 + ichac97R3MixerSetVolume(pThis, uPortIdx, PDMAUDIOMIXERCTL_FRONT, u32Val); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + break; + case AC97_Line_In_Volume_Mute: +#ifdef IN_RING3 + ichac97R3MixerSetVolume(pThis, uPortIdx, PDMAUDIOMIXERCTL_LINE_IN, u32Val); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + break; + case AC97_Record_Select: +#ifdef IN_RING3 + ichac97R3MixerRecordSelect(pThis, u32Val); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + break; + case AC97_Record_Gain_Mute: +#ifdef IN_RING3 + /* Newer Ubuntu guests rely on that when controlling gain and muting + * the recording (capturing) levels. */ + ichac97R3MixerSetGain(pThis, uPortIdx, PDMAUDIOMIXERCTL_LINE_IN, u32Val); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + break; + case AC97_Record_Gain_Mic_Mute: +#ifdef IN_RING3 + /* Ditto; see note above. */ + ichac97R3MixerSetGain(pThis, uPortIdx, PDMAUDIOMIXERCTL_MIC_IN, u32Val); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + break; + case AC97_Vendor_ID1: + case AC97_Vendor_ID2: + LogFunc(("Attempt to write vendor ID to %#x\n", u32Val)); + break; + case AC97_Extended_Audio_ID: + LogFunc(("Attempt to write extended audio ID to %#x\n", u32Val)); + break; + case AC97_Extended_Audio_Ctrl_Stat: +#ifdef IN_RING3 + if (!(u32Val & AC97_EACS_VRA)) + { + ichac97MixerSet(pThis, AC97_PCM_Front_DAC_Rate, 48000); + ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX]); + + ichac97MixerSet(pThis, AC97_PCM_LR_ADC_Rate, 48000); + ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX]); + } + else + LogRel2(("AC97: Variable rate audio (VRA) is not supported\n")); + + if (!(u32Val & AC97_EACS_VRM)) + { + ichac97MixerSet(pThis, AC97_MIC_ADC_Rate, 48000); + ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX]); + } + else + LogRel2(("AC97: Variable rate microphone audio (VRM) is not supported\n")); + + LogFunc(("Setting extended audio control to %#x\n", u32Val)); + ichac97MixerSet(pThis, AC97_Extended_Audio_Ctrl_Stat, u32Val); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + break; + case AC97_PCM_Front_DAC_Rate: + if (ichac97MixerGet(pThis, AC97_Extended_Audio_Ctrl_Stat) & AC97_EACS_VRA) + { +#ifdef IN_RING3 + ichac97MixerSet(pThis, uPortIdx, u32Val); + LogFunc(("Set front DAC rate to %RU32\n", u32Val)); + ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX]); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + } + else + LogRel2(("AC97: Setting Front DAC rate when VRA is not set is forbidden, ignoring\n")); + break; + case AC97_MIC_ADC_Rate: + if (ichac97MixerGet(pThis, AC97_Extended_Audio_Ctrl_Stat) & AC97_EACS_VRM) + { +#ifdef IN_RING3 + ichac97MixerSet(pThis, uPortIdx, u32Val); + LogFunc(("Set MIC ADC rate to %RU32\n", u32Val)); + ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX]); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + } + else + LogRel2(("AC97: Setting MIC ADC rate when VRM is not set is forbidden, ignoring\n")); + break; + case AC97_PCM_LR_ADC_Rate: + if (ichac97MixerGet(pThis, AC97_Extended_Audio_Ctrl_Stat) & AC97_EACS_VRA) + { +#ifdef IN_RING3 + ichac97MixerSet(pThis, uPortIdx, u32Val); + LogFunc(("Set front LR ADC rate to %RU32\n", u32Val)); + ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX]); +#else + rc = VINF_IOM_R3_IOPORT_WRITE; +#endif + } + else + LogRel2(("AC97: Setting LR ADC rate when VRA is not set is forbidden, ignoring\n")); + break; + default: + LogRel2(("AC97: Warning: Unimplemented NAMWrite (%u byte) port=%#x, idx=0x%x <- %#x\n", cbVal, uPort, uPortIdx, u32Val)); + ichac97MixerSet(pThis, uPortIdx, u32Val); + break; + } + break; + } + + case 4: + { + LogRel2(("AC97: Warning: Unimplemented NAMWrite (%u byte) port=%#x, idx=0x%x <- %#x\n", cbVal, uPort, uPortIdx, u32Val)); + pThis->cas = 0; + break; + } + + default: + AssertMsgFailed(("Unhandled NAMWrite port=%#x, cbVal=%u u32Val=%#x\n", uPort, cbVal, u32Val)); + break; + } + + DEVAC97_UNLOCK(pThis); + + return rc; +} + +#ifdef IN_RING3 + +/** + * @callback_method_impl{FNPCIIOREGIONMAP} + */ +static DECLCALLBACK(int) ichac97R3IOPortMap(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion, + RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType) +{ + RT_NOREF(cb, enmType); + + Assert(enmType == PCI_ADDRESS_SPACE_IO); + Assert(cb >= 0x20); + + if (iRegion > 1) /* We support 2 regions max. at the moment. */ + return VERR_INVALID_PARAMETER; + + PAC97STATE pThis = RT_FROM_MEMBER(pPciDev, AC97STATE, PciDev); + RTIOPORT Port = (RTIOPORT)GCPhysAddress; + + int rc; + if (iRegion == 0) + { + rc = PDMDevHlpIOPortRegister(pDevIns, Port, 256, NULL, ichac97IOPortNAMWrite, ichac97IOPortNAMRead, + NULL, NULL, "ICHAC97 NAM"); + AssertRCReturn(rc, rc); + if (pThis->fRZEnabled) + { + rc = PDMDevHlpIOPortRegisterR0(pDevIns, Port, 256, NIL_RTR0PTR, "ichac97IOPortNAMWrite", "ichac97IOPortNAMRead", + NULL, NULL, "ICHAC97 NAM"); + AssertRCReturn(rc, rc); + rc = PDMDevHlpIOPortRegisterRC(pDevIns, Port, 256, NIL_RTRCPTR, "ichac97IOPortNAMWrite", "ichac97IOPortNAMRead", + NULL, NULL, "ICHAC97 NAM"); + AssertRCReturn(rc, rc); + } + } + else + { + rc = PDMDevHlpIOPortRegister(pDevIns, Port, 64, NULL, ichac97IOPortNABMWrite, ichac97IOPortNABMRead, + NULL, NULL, "ICHAC97 NABM"); + AssertRCReturn(rc, rc); + if (pThis->fRZEnabled) + { + rc = PDMDevHlpIOPortRegisterR0(pDevIns, Port, 64, NIL_RTR0PTR, "ichac97IOPortNABMWrite", "ichac97IOPortNABMRead", + NULL, NULL, "ICHAC97 NABM"); + AssertRCReturn(rc, rc); + rc = PDMDevHlpIOPortRegisterRC(pDevIns, Port, 64, NIL_RTRCPTR, "ichac97IOPortNABMWrite", "ichac97IOPortNABMRead", + NULL, NULL, "ICHAC97 NABM"); + AssertRCReturn(rc, rc); + + } + } + + pThis->IOPortBase[iRegion] = Port; + return VINF_SUCCESS; +} + + +/** + * Saves (serializes) an AC'97 stream using SSM. + * + * @returns IPRT status code. + * @param pDevIns Device instance. + * @param pSSM Saved state manager (SSM) handle to use. + * @param pStream AC'97 stream to save. + */ +static int ichac97R3SaveStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PAC97STREAM pStream) +{ + RT_NOREF(pDevIns); + PAC97BMREGS pRegs = &pStream->Regs; + + SSMR3PutU32(pSSM, pRegs->bdbar); + SSMR3PutU8( pSSM, pRegs->civ); + SSMR3PutU8( pSSM, pRegs->lvi); + SSMR3PutU16(pSSM, pRegs->sr); + SSMR3PutU16(pSSM, pRegs->picb); + SSMR3PutU8( pSSM, pRegs->piv); + SSMR3PutU8( pSSM, pRegs->cr); + SSMR3PutS32(pSSM, pRegs->bd_valid); + SSMR3PutU32(pSSM, pRegs->bd.addr); + SSMR3PutU32(pSSM, pRegs->bd.ctl_len); + + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) ichac97R3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + + LogFlowFuncEnter(); + + SSMR3PutU32(pSSM, pThis->glob_cnt); + SSMR3PutU32(pSSM, pThis->glob_sta); + SSMR3PutU32(pSSM, pThis->cas); + + /** @todo r=andy For the next saved state version, add unique stream identifiers and a stream count. */ + /* Note: The order the streams are loaded here is critical, so don't touch. */ + for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) + { + int rc2 = ichac97R3SaveStream(pDevIns, pSSM, &pThis->aStreams[i]); + AssertRC(rc2); + } + + SSMR3PutMem(pSSM, pThis->mixer_data, sizeof(pThis->mixer_data)); + + uint8_t active[AC97SOUNDSOURCE_END_INDEX]; + + active[AC97SOUNDSOURCE_PI_INDEX] = ichac97R3StreamIsEnabled(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX]) ? 1 : 0; + active[AC97SOUNDSOURCE_PO_INDEX] = ichac97R3StreamIsEnabled(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX]) ? 1 : 0; + active[AC97SOUNDSOURCE_MC_INDEX] = ichac97R3StreamIsEnabled(pThis, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX]) ? 1 : 0; + + SSMR3PutMem(pSSM, active, sizeof(active)); + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + +/** + * Loads an AC'97 stream from SSM. + * + * @returns IPRT status code. + * @param pSSM Saved state manager (SSM) handle to use. + * @param pStream AC'97 stream to load. + */ +static int ichac97R3LoadStream(PSSMHANDLE pSSM, PAC97STREAM pStream) +{ + PAC97BMREGS pRegs = &pStream->Regs; + + SSMR3GetU32(pSSM, &pRegs->bdbar); + SSMR3GetU8( pSSM, &pRegs->civ); + SSMR3GetU8( pSSM, &pRegs->lvi); + SSMR3GetU16(pSSM, &pRegs->sr); + SSMR3GetU16(pSSM, &pRegs->picb); + SSMR3GetU8( pSSM, &pRegs->piv); + SSMR3GetU8( pSSM, &pRegs->cr); + SSMR3GetS32(pSSM, &pRegs->bd_valid); + SSMR3GetU32(pSSM, &pRegs->bd.addr); + return SSMR3GetU32(pSSM, &pRegs->bd.ctl_len); +} + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) ichac97R3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + + LogRel2(("ichac97LoadExec: uVersion=%RU32, uPass=0x%x\n", uVersion, uPass)); + + AssertMsgReturn (uVersion == AC97_SSM_VERSION, ("%RU32\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION); + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + + SSMR3GetU32(pSSM, &pThis->glob_cnt); + SSMR3GetU32(pSSM, &pThis->glob_sta); + SSMR3GetU32(pSSM, &pThis->cas); + + /** @todo r=andy For the next saved state version, add unique stream identifiers and a stream count. */ + /* Note: The order the streams are loaded here is critical, so don't touch. */ + for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) + { + int rc2 = ichac97R3LoadStream(pSSM, &pThis->aStreams[i]); + AssertRCReturn(rc2, rc2); + } + + SSMR3GetMem(pSSM, pThis->mixer_data, sizeof(pThis->mixer_data)); + + /** @todo r=andy Stream IDs are hardcoded to certain streams. */ + uint8_t uaStrmsActive[AC97SOUNDSOURCE_END_INDEX]; + int rc2 = SSMR3GetMem(pSSM, uaStrmsActive, sizeof(uaStrmsActive)); + AssertRCReturn(rc2, rc2); + + ichac97R3MixerRecordSelect(pThis, ichac97MixerGet(pThis, AC97_Record_Select)); + ichac97R3MixerSetVolume(pThis, AC97_Master_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER, ichac97MixerGet(pThis, AC97_Master_Volume_Mute)); + ichac97R3MixerSetVolume(pThis, AC97_PCM_Out_Volume_Mute, PDMAUDIOMIXERCTL_FRONT, ichac97MixerGet(pThis, AC97_PCM_Out_Volume_Mute)); + ichac97R3MixerSetVolume(pThis, AC97_Line_In_Volume_Mute, PDMAUDIOMIXERCTL_LINE_IN, ichac97MixerGet(pThis, AC97_Line_In_Volume_Mute)); + ichac97R3MixerSetVolume(pThis, AC97_Mic_Volume_Mute, PDMAUDIOMIXERCTL_MIC_IN, ichac97MixerGet(pThis, AC97_Mic_Volume_Mute)); + ichac97R3MixerSetGain(pThis, AC97_Record_Gain_Mic_Mute, PDMAUDIOMIXERCTL_MIC_IN, ichac97MixerGet(pThis, AC97_Record_Gain_Mic_Mute)); + ichac97R3MixerSetGain(pThis, AC97_Record_Gain_Mute, PDMAUDIOMIXERCTL_LINE_IN, ichac97MixerGet(pThis, AC97_Record_Gain_Mute)); + if (pThis->uCodecModel == AC97_CODEC_AD1980) + if (ichac97MixerGet(pThis, AC97_AD_Misc) & AC97_AD_MISC_HPSEL) + ichac97R3MixerSetVolume(pThis, AC97_Headphone_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER, + ichac97MixerGet(pThis, AC97_Headphone_Volume_Mute)); + + /** @todo r=andy Stream IDs are hardcoded to certain streams. */ + for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) + { + const bool fEnable = RT_BOOL(uaStrmsActive[i]); + const PAC97STREAM pStream = &pThis->aStreams[i]; + + rc2 = ichac97R3StreamEnable(pThis, pStream, fEnable); + if ( fEnable + && RT_SUCCESS(rc2)) + { + /* Re-arm the timer for this stream. */ + rc2 = ichac97TimerSet(pThis, pStream, + TMTimerGet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD)) + pStream->State.cTransferTicks, + false /* fForce */); + } + + AssertRC(rc2); + /* Keep going. */ + } + + pThis->bup_flag = 0; + pThis->last_samp = 0; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) ichac97R3QueryInterface(struct PDMIBASE *pInterface, const char *pszIID) +{ + PAC97STATE pThis = RT_FROM_MEMBER(pInterface, AC97STATE, IBase); + Assert(&pThis->IBase == pInterface); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase); + return NULL; +} + + +/** + * Powers off the device. + * + * @param pDevIns Device instance to power off. + */ +static DECLCALLBACK(void) ichac97R3PowerOff(PPDMDEVINS pDevIns) +{ + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + + LogRel2(("AC97: Powering off ...\n")); + + /* Note: Involves mixer stream / sink destruction, so also do this here + * instead of in ichac97R3Destruct(). */ + ichac97R3StreamsDestroy(pThis); + + /** + * Note: Destroy the mixer while powering off and *not* in ichac97R3Destruct, + * giving the mixer the chance to release any references held to + * PDM audio streams it maintains. + */ + if (pThis->pMixer) + { + AudioMixerDestroy(pThis->pMixer); + pThis->pMixer = NULL; + } +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + * + * @remarks The original sources didn't install a reset handler, but it seems to + * make sense to me so we'll do it. + */ +static DECLCALLBACK(void) ichac97R3Reset(PPDMDEVINS pDevIns) +{ + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + + LogRel(("AC97: Reset\n")); + + /* + * Reset the mixer too. The Windows XP driver seems to rely on + * this. At least it wants to read the vendor id before it resets + * the codec manually. + */ + ichac97R3MixerReset(pThis); + + /* + * Reset all streams. + */ + for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) + { + ichac97R3StreamEnable(pThis, &pThis->aStreams[i], false /* fEnable */); + ichac97R3StreamReset(pThis, &pThis->aStreams[i]); + } + + /* + * Reset mixer sinks. + * + * Do the reset here instead of in ichac97R3StreamReset(); + * the mixer sink(s) might still have data to be processed when an audio stream gets reset. + */ + AudioMixerSinkReset(pThis->pSinkLineIn); + AudioMixerSinkReset(pThis->pSinkMicIn); + AudioMixerSinkReset(pThis->pSinkOut); +} + + +/** + * Attach command, internal version. + * + * This is called to let the device attach to a driver for a specified LUN + * during runtime. This is not called during VM construction, the device + * constructor has to attach to all the available drivers. + * + * @returns VBox status code. + * @param pThis AC'97 state. + * @param uLUN The logical unit which is being attached. + * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines. + * @param ppDrv Attached driver instance on success. Optional. + */ +static int ichac97R3AttachInternal(PAC97STATE pThis, unsigned uLUN, uint32_t fFlags, PAC97DRIVER *ppDrv) +{ + RT_NOREF(fFlags); + + /* + * Attach driver. + */ + char *pszDesc; + if (RTStrAPrintf(&pszDesc, "Audio driver port (AC'97) for LUN #%u", uLUN) <= 0) + AssertLogRelFailedReturn(VERR_NO_MEMORY); + + PPDMIBASE pDrvBase; + int rc = PDMDevHlpDriverAttach(pThis->pDevInsR3, uLUN, + &pThis->IBase, &pDrvBase, pszDesc); + if (RT_SUCCESS(rc)) + { + PAC97DRIVER pDrv = (PAC97DRIVER)RTMemAllocZ(sizeof(AC97DRIVER)); + if (pDrv) + { + pDrv->pDrvBase = pDrvBase; + pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR); + AssertMsg(pDrv->pConnector != NULL, ("Configuration error: LUN #%u has no host audio interface, rc=%Rrc\n", uLUN, rc)); + pDrv->pAC97State = pThis; + pDrv->uLUN = uLUN; + + /* + * For now we always set the driver at LUN 0 as our primary + * host backend. This might change in the future. + */ + if (pDrv->uLUN == 0) + pDrv->fFlags |= PDMAUDIODRVFLAGS_PRIMARY; + + LogFunc(("LUN#%RU8: pCon=%p, drvFlags=0x%x\n", uLUN, pDrv->pConnector, pDrv->fFlags)); + + /* Attach to driver list if not attached yet. */ + if (!pDrv->fAttached) + { + RTListAppend(&pThis->lstDrv, &pDrv->Node); + pDrv->fAttached = true; + } + + if (ppDrv) + *ppDrv = pDrv; + } + else + rc = VERR_NO_MEMORY; + } + else if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + LogFunc(("No attached driver for LUN #%u\n", uLUN)); + + if (RT_FAILURE(rc)) + { + /* Only free this string on failure; + * must remain valid for the live of the driver instance. */ + RTStrFree(pszDesc); + } + + LogFunc(("uLUN=%u, fFlags=0x%x, rc=%Rrc\n", uLUN, fFlags, rc)); + return rc; +} + +/** + * Detach command, internal version. + * + * This is called to let the device detach from a driver for a specified LUN + * during runtime. + * + * @returns VBox status code. + * @param pThis AC'97 state. + * @param pDrv Driver to detach from device. + * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines. + */ +static int ichac97R3DetachInternal(PAC97STATE pThis, PAC97DRIVER pDrv, uint32_t fFlags) +{ + RT_NOREF(fFlags); + + /* First, remove the driver from our list and destory it's associated streams. + * This also will un-set the driver as a recording source (if associated). */ + ichac97R3MixerRemoveDrv(pThis, pDrv); + + /* Next, search backwards for a capable (attached) driver which now will be the + * new recording source. */ + PDMAUDIODESTSOURCE dstSrc; + PAC97DRIVER pDrvCur; + RTListForEachReverse(&pThis->lstDrv, pDrvCur, AC97DRIVER, Node) + { + if (!pDrvCur->pConnector) + continue; + + PDMAUDIOBACKENDCFG Cfg; + int rc2 = pDrvCur->pConnector->pfnGetConfig(pDrvCur->pConnector, &Cfg); + if (RT_FAILURE(rc2)) + continue; + + dstSrc.Source = PDMAUDIORECSOURCE_MIC; + PAC97DRIVERSTREAM pDrvStrm = ichac97R3MixerGetDrvStream(pThis, pDrvCur, PDMAUDIODIR_IN, dstSrc); + if ( pDrvStrm + && pDrvStrm->pMixStrm) + { + rc2 = AudioMixerSinkSetRecordingSource(pThis->pSinkMicIn, pDrvStrm->pMixStrm); + if (RT_SUCCESS(rc2)) + LogRel2(("AC97: Set new recording source for 'Mic In' to '%s'\n", Cfg.szName)); + } + + dstSrc.Source = PDMAUDIORECSOURCE_LINE; + pDrvStrm = ichac97R3MixerGetDrvStream(pThis, pDrvCur, PDMAUDIODIR_IN, dstSrc); + if ( pDrvStrm + && pDrvStrm->pMixStrm) + { + rc2 = AudioMixerSinkSetRecordingSource(pThis->pSinkLineIn, pDrvStrm->pMixStrm); + if (RT_SUCCESS(rc2)) + LogRel2(("AC97: Set new recording source for 'Line In' to '%s'\n", Cfg.szName)); + } + } + + LogFunc(("uLUN=%u, fFlags=0x%x\n", pDrv->uLUN, fFlags)); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnAttach} + */ +static DECLCALLBACK(int) ichac97R3Attach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags) +{ + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + + LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags)); + + DEVAC97_LOCK(pThis); + + PAC97DRIVER pDrv; + int rc2 = ichac97R3AttachInternal(pThis, uLUN, fFlags, &pDrv); + if (RT_SUCCESS(rc2)) + rc2 = ichac97R3MixerAddDrv(pThis, pDrv); + + if (RT_FAILURE(rc2)) + LogFunc(("Failed with %Rrc\n", rc2)); + + DEVAC97_UNLOCK(pThis); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnDetach} + */ +static DECLCALLBACK(void) ichac97R3Detach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags) +{ + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + + LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags)); + + DEVAC97_LOCK(pThis); + + PAC97DRIVER pDrv, pDrvNext; + RTListForEachSafe(&pThis->lstDrv, pDrv, pDrvNext, AC97DRIVER, Node) + { + if (pDrv->uLUN == uLUN) + { + int rc2 = ichac97R3DetachInternal(pThis, pDrv, fFlags); + if (RT_SUCCESS(rc2)) + { + RTMemFree(pDrv); + pDrv = NULL; + } + + break; + } + } + + DEVAC97_UNLOCK(pThis); +} + +/** + * Re-attaches (replaces) a driver with a new driver. + * + * @returns VBox status code. + * @param pThis Device instance. + * @param pDrv Driver instance used for attaching to. + * If NULL is specified, a new driver will be created and appended + * to the driver list. + * @param uLUN The logical unit which is being re-detached. + * @param pszDriver New driver name to attach. + */ +static int ichac97R3ReattachInternal(PAC97STATE pThis, PAC97DRIVER pDrv, uint8_t uLUN, const char *pszDriver) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pszDriver, VERR_INVALID_POINTER); + + int rc; + + if (pDrv) + { + rc = ichac97R3DetachInternal(pThis, pDrv, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + rc = PDMDevHlpDriverDetach(pThis->pDevInsR3, PDMIBASE_2_PDMDRV(pDrv->pDrvBase), 0 /* fFlags */); + + if (RT_FAILURE(rc)) + return rc; + + pDrv = NULL; + } + + PVM pVM = PDMDevHlpGetVM(pThis->pDevInsR3); + PCFGMNODE pRoot = CFGMR3GetRoot(pVM); + PCFGMNODE pDev0 = CFGMR3GetChild(pRoot, "Devices/ichac97/0/"); + + /* Remove LUN branch. */ + CFGMR3RemoveNode(CFGMR3GetChildF(pDev0, "LUN#%u/", uLUN)); + +# define RC_CHECK() if (RT_FAILURE(rc)) { AssertReleaseRC(rc); break; } + + do + { + PCFGMNODE pLunL0; + rc = CFGMR3InsertNodeF(pDev0, &pLunL0, "LUN#%u/", uLUN); RC_CHECK(); + rc = CFGMR3InsertString(pLunL0, "Driver", "AUDIO"); RC_CHECK(); + rc = CFGMR3InsertNode(pLunL0, "Config/", NULL); RC_CHECK(); + + PCFGMNODE pLunL1, pLunL2; + rc = CFGMR3InsertNode (pLunL0, "AttachedDriver/", &pLunL1); RC_CHECK(); + rc = CFGMR3InsertNode (pLunL1, "Config/", &pLunL2); RC_CHECK(); + rc = CFGMR3InsertString(pLunL1, "Driver", pszDriver); RC_CHECK(); + + rc = CFGMR3InsertString(pLunL2, "AudioDriver", pszDriver); RC_CHECK(); + + } while (0); + + if (RT_SUCCESS(rc)) + rc = ichac97R3AttachInternal(pThis, uLUN, 0 /* fFlags */, NULL /* ppDrv */); + + LogFunc(("pThis=%p, uLUN=%u, pszDriver=%s, rc=%Rrc\n", pThis, uLUN, pszDriver, rc)); + +# undef RC_CHECK + + return rc; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnRelocate} + */ +static DECLCALLBACK(void) ichac97R3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta) +{ + NOREF(offDelta); + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns); + + for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) + pThis->pTimerRC[i] = TMTimerRCPtr(pThis->pTimerR3[i]); +} + +/** + * @interface_method_impl{PDMDEVREG,pfnDestruct} + */ +static DECLCALLBACK(int) ichac97R3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */ + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + + LogFlowFuncEnter(); + + PAC97DRIVER pDrv, pDrvNext; + RTListForEachSafe(&pThis->lstDrv, pDrv, pDrvNext, AC97DRIVER, Node) + { + RTListNodeRemove(&pDrv->Node); + RTMemFree(pDrv); + } + + /* Sanity. */ + Assert(RTListIsEmpty(&pThis->lstDrv)); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct} + */ +static DECLCALLBACK(int) ichac97R3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */ + PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE); + Assert(iInstance == 0); RT_NOREF(iInstance); + + /* + * Initialize data so we can run the destructor without scewing up. + */ + pThis->pDevInsR3 = pDevIns; + pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns); + pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns); + pThis->IBase.pfnQueryInterface = ichac97R3QueryInterface; + RTListInit(&pThis->lstDrv); + + /* + * Validations. + */ + if (!CFGMR3AreValuesValid(pCfg, "RZEnabled\0" + "Codec\0" + "TimerHz\0" + "DebugEnabled\0" + "DebugPathOut\0")) + return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, + N_("Invalid configuration for the AC'97 device")); + + /* + * Read config data. + */ + int rc = CFGMR3QueryBoolDef(pCfg, "RZEnabled", &pThis->fRZEnabled, true); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("AC'97 configuration error: failed to read RCEnabled as boolean")); + + char szCodec[20]; + rc = CFGMR3QueryStringDef(pCfg, "Codec", &szCodec[0], sizeof(szCodec), "STAC9700"); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, + N_("AC'97 configuration error: Querying \"Codec\" as string failed")); + + rc = CFGMR3QueryU16Def(pCfg, "TimerHz", &pThis->uTimerHz, AC97_TIMER_HZ_DEFAULT /* Default value, if not set. */); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("AC'97 configuration error: failed to read Hertz (Hz) rate as unsigned integer")); + + if (pThis->uTimerHz != AC97_TIMER_HZ_DEFAULT) + LogRel(("AC97: Using custom device timer rate (%RU16Hz)\n", pThis->uTimerHz)); + + rc = CFGMR3QueryBoolDef(pCfg, "DebugEnabled", &pThis->Dbg.fEnabled, false); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("AC97 configuration error: failed to read debugging enabled flag as boolean")); + + rc = CFGMR3QueryStringDef(pCfg, "DebugPathOut", pThis->Dbg.szOutPath, sizeof(pThis->Dbg.szOutPath), + VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("AC97 configuration error: failed to read debugging output path flag as string")); + + if (!strlen(pThis->Dbg.szOutPath)) + RTStrPrintf(pThis->Dbg.szOutPath, sizeof(pThis->Dbg.szOutPath), VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH); + + if (pThis->Dbg.fEnabled) + LogRel2(("AC97: Debug output will be saved to '%s'\n", pThis->Dbg.szOutPath)); + + /* + * The AD1980 codec (with corresponding PCI subsystem vendor ID) is whitelisted + * in the Linux kernel; Linux makes no attempt to measure the data rate and assumes + * 48 kHz rate, which is exactly what we need. Same goes for AD1981B. + */ + if (!strcmp(szCodec, "STAC9700")) + pThis->uCodecModel = AC97_CODEC_STAC9700; + else if (!strcmp(szCodec, "AD1980")) + pThis->uCodecModel = AC97_CODEC_AD1980; + else if (!strcmp(szCodec, "AD1981B")) + pThis->uCodecModel = AC97_CODEC_AD1981B; + else + return PDMDevHlpVMSetError(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, RT_SRC_POS, + N_("AC'97 configuration error: The \"Codec\" value \"%s\" is unsupported"), szCodec); + + LogRel(("AC97: Using codec '%s'\n", szCodec)); + + /* + * Use an own critical section for the device instead of the default + * one provided by PDM. This allows fine-grained locking in combination + * with TM when timer-specific stuff is being called in e.g. the MMIO handlers. + */ + rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CritSect, RT_SRC_POS, "AC'97"); + AssertRCReturn(rc, rc); + + rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns)); + AssertRCReturn(rc, rc); + + /* + * Initialize data (most of it anyway). + */ + /* PCI Device */ + PCIDevSetVendorId (&pThis->PciDev, 0x8086); /* 00 ro - intel. */ Assert(pThis->PciDev.abConfig[0x00] == 0x86); Assert(pThis->PciDev.abConfig[0x01] == 0x80); + PCIDevSetDeviceId (&pThis->PciDev, 0x2415); /* 02 ro - 82801 / 82801aa(?). */ Assert(pThis->PciDev.abConfig[0x02] == 0x15); Assert(pThis->PciDev.abConfig[0x03] == 0x24); + PCIDevSetCommand (&pThis->PciDev, 0x0000); /* 04 rw,ro - pcicmd. */ Assert(pThis->PciDev.abConfig[0x04] == 0x00); Assert(pThis->PciDev.abConfig[0x05] == 0x00); + PCIDevSetStatus (&pThis->PciDev, VBOX_PCI_STATUS_DEVSEL_MEDIUM | VBOX_PCI_STATUS_FAST_BACK); /* 06 rwc?,ro? - pcists. */ Assert(pThis->PciDev.abConfig[0x06] == 0x80); Assert(pThis->PciDev.abConfig[0x07] == 0x02); + PCIDevSetRevisionId (&pThis->PciDev, 0x01); /* 08 ro - rid. */ Assert(pThis->PciDev.abConfig[0x08] == 0x01); + PCIDevSetClassProg (&pThis->PciDev, 0x00); /* 09 ro - pi. */ Assert(pThis->PciDev.abConfig[0x09] == 0x00); + PCIDevSetClassSub (&pThis->PciDev, 0x01); /* 0a ro - scc; 01 == Audio. */ Assert(pThis->PciDev.abConfig[0x0a] == 0x01); + PCIDevSetClassBase (&pThis->PciDev, 0x04); /* 0b ro - bcc; 04 == multimedia.*/Assert(pThis->PciDev.abConfig[0x0b] == 0x04); + PCIDevSetHeaderType (&pThis->PciDev, 0x00); /* 0e ro - headtyp. */ Assert(pThis->PciDev.abConfig[0x0e] == 0x00); + PCIDevSetBaseAddress (&pThis->PciDev, 0, /* 10 rw - nambar - native audio mixer base. */ + true /* fIoSpace */, false /* fPrefetchable */, false /* f64Bit */, 0x00000000); Assert(pThis->PciDev.abConfig[0x10] == 0x01); Assert(pThis->PciDev.abConfig[0x11] == 0x00); Assert(pThis->PciDev.abConfig[0x12] == 0x00); Assert(pThis->PciDev.abConfig[0x13] == 0x00); + PCIDevSetBaseAddress (&pThis->PciDev, 1, /* 14 rw - nabmbar - native audio bus mastering. */ + true /* fIoSpace */, false /* fPrefetchable */, false /* f64Bit */, 0x00000000); Assert(pThis->PciDev.abConfig[0x14] == 0x01); Assert(pThis->PciDev.abConfig[0x15] == 0x00); Assert(pThis->PciDev.abConfig[0x16] == 0x00); Assert(pThis->PciDev.abConfig[0x17] == 0x00); + PCIDevSetInterruptLine(&pThis->PciDev, 0x00); /* 3c rw. */ Assert(pThis->PciDev.abConfig[0x3c] == 0x00); + PCIDevSetInterruptPin (&pThis->PciDev, 0x01); /* 3d ro - INTA#. */ Assert(pThis->PciDev.abConfig[0x3d] == 0x01); + + if (pThis->uCodecModel == AC97_CODEC_AD1980) + { + PCIDevSetSubSystemVendorId(&pThis->PciDev, 0x1028); /* 2c ro - Dell.) */ + PCIDevSetSubSystemId (&pThis->PciDev, 0x0177); /* 2e ro. */ + } + else if (pThis->uCodecModel == AC97_CODEC_AD1981B) + { + PCIDevSetSubSystemVendorId(&pThis->PciDev, 0x1028); /* 2c ro - Dell.) */ + PCIDevSetSubSystemId (&pThis->PciDev, 0x01ad); /* 2e ro. */ + } + else + { + PCIDevSetSubSystemVendorId(&pThis->PciDev, 0x8086); /* 2c ro - Intel.) */ + PCIDevSetSubSystemId (&pThis->PciDev, 0x0000); /* 2e ro. */ + } + + /* + * Register the PCI device, it's I/O regions, the timer and the + * saved state item. + */ + rc = PDMDevHlpPCIRegister(pDevIns, &pThis->PciDev); + if (RT_FAILURE(rc)) + return rc; + + rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, 256, PCI_ADDRESS_SPACE_IO, ichac97R3IOPortMap); + if (RT_FAILURE(rc)) + return rc; + + rc = PDMDevHlpPCIIORegionRegister(pDevIns, 1, 64, PCI_ADDRESS_SPACE_IO, ichac97R3IOPortMap); + if (RT_FAILURE(rc)) + return rc; + + rc = PDMDevHlpSSMRegister(pDevIns, AC97_SSM_VERSION, sizeof(*pThis), ichac97R3SaveExec, ichac97R3LoadExec); + if (RT_FAILURE(rc)) + return rc; + +# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO + LogRel(("AC97: Asynchronous I/O enabled\n")); +# endif + + /* + * Attach driver. + */ + uint8_t uLUN; + for (uLUN = 0; uLUN < UINT8_MAX; ++uLUN) + { + LogFunc(("Trying to attach driver for LUN #%RU8 ...\n", uLUN)); + rc = ichac97R3AttachInternal(pThis, uLUN, 0 /* fFlags */, NULL /* ppDrv */); + if (RT_FAILURE(rc)) + { + if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + rc = VINF_SUCCESS; + else if (rc == VERR_AUDIO_BACKEND_INIT_FAILED) + { + ichac97R3ReattachInternal(pThis, NULL /* pDrv */, uLUN, "NullAudio"); + PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", + N_("Host audio backend initialization has failed. Selecting the NULL audio backend " + "with the consequence that no sound is audible")); + /* Attaching to the NULL audio backend will never fail. */ + rc = VINF_SUCCESS; + } + break; + } + } + + LogFunc(("cLUNs=%RU8, rc=%Rrc\n", uLUN, rc)); + + if (RT_SUCCESS(rc)) + { + rc = AudioMixerCreate("AC'97 Mixer", 0 /* uFlags */, &pThis->pMixer); + if (RT_SUCCESS(rc)) + { + rc = AudioMixerCreateSink(pThis->pMixer, "[Recording] Line In", AUDMIXSINKDIR_INPUT, &pThis->pSinkLineIn); + AssertRC(rc); + rc = AudioMixerCreateSink(pThis->pMixer, "[Recording] Microphone In", AUDMIXSINKDIR_INPUT, &pThis->pSinkMicIn); + AssertRC(rc); + rc = AudioMixerCreateSink(pThis->pMixer, "[Playback] PCM Output", AUDMIXSINKDIR_OUTPUT, &pThis->pSinkOut); + AssertRC(rc); + } + } + + if (RT_SUCCESS(rc)) + { + /* + * Create all hardware streams. + */ + for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) + { + int rc2 = ichac97R3StreamCreate(pThis, &pThis->aStreams[i], i /* SD# */); + AssertRC(rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +# ifdef VBOX_WITH_AUDIO_AC97_ONETIME_INIT + PAC97DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, AC97DRIVER, Node) + { + /* + * Only primary drivers are critical for the VM to run. Everything else + * might not worth showing an own error message box in the GUI. + */ + if (!(pDrv->fFlags & PDMAUDIODRVFLAGS_PRIMARY)) + continue; + + PPDMIAUDIOCONNECTOR pCon = pDrv->pConnector; + AssertPtr(pCon); + + bool fValidLineIn = AudioMixerStreamIsValid(pDrv->LineIn.pMixStrm); + bool fValidMicIn = AudioMixerStreamIsValid(pDrv->MicIn.pMixStrm); + bool fValidOut = AudioMixerStreamIsValid(pDrv->Out.pMixStrm); + + if ( !fValidLineIn + && !fValidMicIn + && !fValidOut) + { + LogRel(("AC97: Falling back to NULL backend (no sound audible)\n")); + + ichac97R3Reset(pDevIns); + ichac97R3ReattachInternal(pThis, pDrv, pDrv->uLUN, "NullAudio"); + + PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", + N_("No audio devices could be opened. Selecting the NULL audio backend " + "with the consequence that no sound is audible")); + } + else + { + bool fWarn = false; + + PDMAUDIOBACKENDCFG backendCfg; + int rc2 = pCon->pfnGetConfig(pCon, &backendCfg); + if (RT_SUCCESS(rc2)) + { + if (backendCfg.cMaxStreamsIn) + { + /* If the audio backend supports two or more input streams at once, + * warn if one of our two inputs (microphone-in and line-in) failed to initialize. */ + if (backendCfg.cMaxStreamsIn >= 2) + fWarn = !fValidLineIn || !fValidMicIn; + /* If the audio backend only supports one input stream at once (e.g. pure ALSA, and + * *not* ALSA via PulseAudio plugin!), only warn if both of our inputs failed to initialize. + * One of the two simply is not in use then. */ + else if (backendCfg.cMaxStreamsIn == 1) + fWarn = !fValidLineIn && !fValidMicIn; + /* Don't warn if our backend is not able of supporting any input streams at all. */ + } + + if ( !fWarn + && backendCfg.cMaxStreamsOut) + { + fWarn = !fValidOut; + } + } + else + { + LogRel(("AC97: Unable to retrieve audio backend configuration for LUN #%RU8, rc=%Rrc\n", pDrv->uLUN, rc2)); + fWarn = true; + } + + if (fWarn) + { + char szMissingStreams[255] = ""; + size_t len = 0; + if (!fValidLineIn) + { + LogRel(("AC97: WARNING: Unable to open PCM line input for LUN #%RU8!\n", pDrv->uLUN)); + len = RTStrPrintf(szMissingStreams, sizeof(szMissingStreams), "PCM Input"); + } + if (!fValidMicIn) + { + LogRel(("AC97: WARNING: Unable to open PCM microphone input for LUN #%RU8!\n", pDrv->uLUN)); + len += RTStrPrintf(szMissingStreams + len, + sizeof(szMissingStreams) - len, len ? ", PCM Microphone" : "PCM Microphone"); + } + if (!fValidOut) + { + LogRel(("AC97: WARNING: Unable to open PCM output for LUN #%RU8!\n", pDrv->uLUN)); + len += RTStrPrintf(szMissingStreams + len, + sizeof(szMissingStreams) - len, len ? ", PCM Output" : "PCM Output"); + } + + PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", + N_("Some AC'97 audio streams (%s) could not be opened. Guest applications generating audio " + "output or depending on audio input may hang. Make sure your host audio device " + "is working properly. Check the logfile for error messages of the audio " + "subsystem"), szMissingStreams); + } + } + } +# endif /* VBOX_WITH_AUDIO_AC97_ONETIME_INIT */ + } + + if (RT_SUCCESS(rc)) + ichac97R3Reset(pDevIns); + + if (RT_SUCCESS(rc)) + { + for (unsigned i = 0; i < AC97_MAX_STREAMS; i++) + { + /* Create the emulation timer (per stream). + * + * Note: Use TMCLOCK_VIRTUAL_SYNC here, as the guest's AC'97 driver + * relies on exact (virtual) DMA timing and uses DMA Position Buffers + * instead of the LPIB registers. + */ + char szTimer[16]; + RTStrPrintf2(szTimer, sizeof(szTimer), "AC97SD%i", i); + + rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, ichac97R3Timer, &pThis->aStreams[i], + TMTIMER_FLAGS_NO_CRIT_SECT, szTimer, &pThis->pTimerR3[i]); + AssertRCReturn(rc, rc); + pThis->pTimerR0[i] = TMTimerR0Ptr(pThis->pTimerR3[i]); + pThis->pTimerRC[i] = TMTimerRCPtr(pThis->pTimerR3[i]); + + /* Use our own critcal section for the device timer. + * That way we can control more fine-grained when to lock what. */ + rc = TMR3TimerSetCritSect(pThis->pTimerR3[i], &pThis->CritSect); + AssertRCReturn(rc, rc); + } + } + +# ifdef VBOX_WITH_STATISTICS + if (RT_SUCCESS(rc)) + { + /* + * Register statistics. + */ + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTimer, STAMTYPE_PROFILE, "/Devices/AC97/Timer", STAMUNIT_TICKS_PER_CALL, "Profiling ichac97Timer."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIn, STAMTYPE_PROFILE, "/Devices/AC97/Input", STAMUNIT_TICKS_PER_CALL, "Profiling input."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatOut, STAMTYPE_PROFILE, "/Devices/AC97/Output", STAMUNIT_TICKS_PER_CALL, "Profiling output."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "/Devices/AC97/BytesRead" , STAMUNIT_BYTES, "Bytes read from AC97 emulation."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, "/Devices/AC97/BytesWritten", STAMUNIT_BYTES, "Bytes written to AC97 emulation."); + } +# endif + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * The device registration structure. + */ +const PDMDEVREG g_DeviceICHAC97 = +{ + /* u32Version */ + PDM_DEVREG_VERSION, + /* szName */ + "ichac97", + /* szRCMod */ + "VBoxDDRC.rc", + /* szR0Mod */ + "VBoxDDR0.r0", + /* pszDescription */ + "ICH AC'97 Audio Controller", + /* fFlags */ + PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0, + /* fClass */ + PDM_DEVREG_CLASS_AUDIO, + /* cMaxInstances */ + 1, + /* cbInstance */ + sizeof(AC97STATE), + /* pfnConstruct */ + ichac97R3Construct, + /* pfnDestruct */ + ichac97R3Destruct, + /* pfnRelocate */ + ichac97R3Relocate, + /* pfnMemSetup */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + ichac97R3Reset, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + ichac97R3Attach, + /* pfnDetach */ + ichac97R3Detach, + /* pfnQueryInterface. */ + NULL, + /* pfnInitComplete */ + NULL, + /* pfnPowerOff */ + ichac97R3PowerOff, + /* pfnSoftReset */ + NULL, + /* u32VersionEnd */ + PDM_DEVREG_VERSION +}; + +#endif /* !IN_RING3 */ +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ + diff --git a/src/VBox/Devices/Audio/DevSB16.cpp b/src/VBox/Devices/Audio/DevSB16.cpp new file mode 100644 index 00000000..b2e997b5 --- /dev/null +++ b/src/VBox/Devices/Audio/DevSB16.cpp @@ -0,0 +1,2648 @@ +/* $Id: DevSB16.cpp $ */ +/** @file + * DevSB16 - VBox SB16 Audio Controller. + */ + +/* + * Copyright (C) 2015-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * -------------------------------------------------------------------- + * + * This code is based on: sb16.c from QEMU AUDIO subsystem (r3917). + * QEMU Soundblaster 16 emulation + * + * Copyright (c) 2003-2005 Vassili Karpov (malc) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_SB16 +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/file.h> +#ifdef IN_RING3 +# include <iprt/mem.h> +# include <iprt/string.h> +# include <iprt/uuid.h> +#endif + +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include "VBoxDD.h" + +#include "AudioMixBuffer.h" +#include "AudioMixer.h" +#include "DrvAudio.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Current saved state version. */ +#define SB16_SAVE_STATE_VERSION 2 +/** The version used in VirtualBox version 3.0 and earlier. This didn't include the config dump. */ +#define SB16_SAVE_STATE_VERSION_VBOX_30 1 + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const char e3[] = "COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992."; + + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Structure defining a (host backend) driver stream. + * Each driver has its own instances of audio mixer streams, which then + * can go into the same (or even different) audio mixer sinks. + */ +typedef struct SB16DRIVERSTREAM +{ + /** Associated PDM audio stream. */ + R3PTRTYPE(PPDMAUDIOSTREAM) pStream; + /** The stream's current configuration. */ +} SB16DRIVERSTREAM, *PSB16DRIVERSTREAM; + +/** + * Struct for maintaining a host backend driver. + */ +typedef struct SB16STATE *PSB16STATE; +typedef struct SB16DRIVER +{ + /** Node for storing this driver in our device driver list of SB16STATE. */ + RTLISTNODER3 Node; + /** Pointer to SB16 controller (state). */ + R3PTRTYPE(PSB16STATE) pSB16State; + /** Driver flags. */ + PDMAUDIODRVFLAGS fFlags; + uint32_t PaddingFlags; + /** LUN # to which this driver has been assigned. */ + uint8_t uLUN; + /** Whether this driver is in an attached state or not. */ + bool fAttached; + uint8_t Padding[4]; + /** Pointer to attached driver base interface. */ + R3PTRTYPE(PPDMIBASE) pDrvBase; + /** Audio connector interface to the underlying host backend. */ + R3PTRTYPE(PPDMIAUDIOCONNECTOR) pConnector; + /** Stream for output. */ + SB16DRIVERSTREAM Out; +} SB16DRIVER, *PSB16DRIVER; + +/** + * Structure for a SB16 stream. + */ +typedef struct SB16STREAM +{ + /** The stream's current configuration. */ + PDMAUDIOSTREAMCFG Cfg; +} SB16STREAM, *PSB16STREAM; + +typedef struct SB16STATE +{ +#ifdef VBOX + /** Pointer to the device instance. */ + PPDMDEVINSR3 pDevInsR3; + /** Pointer to the connector of the attached audio driver. */ + PPDMIAUDIOCONNECTOR pDrv; + int irqCfg; + int dmaCfg; + int hdmaCfg; + int portCfg; + int verCfg; +#endif + int irq; + int dma; + int hdma; + int port; + int ver; + + int in_index; + int out_data_len; + int fmt_stereo; + int fmt_signed; + int fmt_bits; + PDMAUDIOFMT fmt; + int dma_auto; + int block_size; + int fifo; + int freq; + int time_const; + int speaker; + int needed_bytes; + int cmd; + int use_hdma; + int highspeed; + int can_write; /** @todo Value never gets 0? */ + + int v2x6; + + uint8_t csp_param; + uint8_t csp_value; + uint8_t csp_mode; + uint8_t csp_regs[256]; + uint8_t csp_index; + uint8_t csp_reg83[4]; + int csp_reg83r; + int csp_reg83w; + + uint8_t in2_data[10]; + uint8_t out_data[50]; + uint8_t test_reg; + uint8_t last_read_byte; + int nzero; + + int left_till_irq; /** Note: Can be < 0. */ + + int dma_running; + int bytes_per_second; + int align; + + RTLISTANCHOR lstDrv; + /** Number of active (running) SDn streams. */ + uint8_t cStreamsActive; + /** The timer for pumping data thru the attached LUN drivers. */ + PTMTIMERR3 pTimerIO; + /** Flag indicating whether the timer is active or not. */ + bool fTimerActive; + uint8_t u8Padding1[7]; + /** The timer interval for pumping data thru the LUN drivers in timer ticks. */ + uint64_t cTimerTicksIO; + /** Timestamp of the last timer callback (sb16TimerIO). + * Used to calculate the time actually elapsed between two timer callbacks. */ + uint64_t uTimerTSIO; + PTMTIMER pTimerIRQ; + /** The base interface for LUN\#0. */ + PDMIBASE IBase; + /** Output stream. */ + SB16STREAM Out; + + /* mixer state */ + uint8_t mixer_nreg; + uint8_t mixer_regs[256]; +} SB16STATE, *PSB16STATE; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int sb16CheckAndReOpenOut(PSB16STATE pThis); +static int sb16OpenOut(PSB16STATE pThis, PPDMAUDIOSTREAMCFG pCfg); +static void sb16CloseOut(PSB16STATE pThis); +static void sb16TimerMaybeStart(PSB16STATE pThis); +static void sb16TimerMaybeStop(PSB16STATE pThis); + + + +static int magic_of_irq(int irq) +{ + switch (irq) + { + case 5: + return 2; + case 7: + return 4; + case 9: + return 1; + case 10: + return 8; + default: + break; + } + + LogFlowFunc(("bad irq %d\n", irq)); + return 2; +} + +static int irq_of_magic(int magic) +{ + switch (magic) + { + case 1: + return 9; + case 2: + return 5; + case 4: + return 7; + case 8: + return 10; + default: + break; + } + + LogFlowFunc(("bad irq magic %d\n", magic)); + return -1; +} + +#if 0 // unused // def DEBUG +DECLINLINE(void) log_dsp(PSB16STATE pThis) +{ + LogFlowFunc(("%s:%s:%d:%s:dmasize=%d:freq=%d:const=%d:speaker=%d\n", + pThis->fmt_stereo ? "Stereo" : "Mono", + pThis->fmt_signed ? "Signed" : "Unsigned", + pThis->fmt_bits, + pThis->dma_auto ? "Auto" : "Single", + pThis->block_size, + pThis->freq, + pThis->time_const, + pThis->speaker)); +} +#endif + +static void sb16SpeakerControl(PSB16STATE pThis, int on) +{ + pThis->speaker = on; + /* AUD_enable (pThis->voice, on); */ +} + +static void sb16Control(PSB16STATE pThis, int hold) +{ + int dma = pThis->use_hdma ? pThis->hdma : pThis->dma; + pThis->dma_running = hold; + + LogFlowFunc(("hold %d high %d dma %d\n", hold, pThis->use_hdma, dma)); + + PDMDevHlpDMASetDREQ(pThis->pDevInsR3, dma, hold); + + PSB16DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) + { + if (!pDrv->Out.pStream) + continue; + + int rc2 = pDrv->pConnector->pfnStreamControl(pDrv->pConnector, pDrv->Out.pStream, + hold == 1 ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE); + LogFlowFunc(("%s: rc=%Rrc\n", pDrv->Out.pStream->szName, rc2)); NOREF(rc2); + } + + if (hold) + { + pThis->cStreamsActive++; + sb16TimerMaybeStart(pThis); + PDMDevHlpDMASchedule(pThis->pDevInsR3); + } + else + { + if (pThis->cStreamsActive) + pThis->cStreamsActive--; + sb16TimerMaybeStop(pThis); + } +} + +/** + * @callback_method_impl{PFNTMTIMERDEV} + */ +static DECLCALLBACK(void) sb16TimerIRQ(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvThis) +{ + RT_NOREF(pDevIns, pTimer); + PSB16STATE pThis = (PSB16STATE)pvThis; + pThis->can_write = 1; + PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 1); +} + +#define DMA8_AUTO 1 +#define DMA8_HIGH 2 + +static void continue_dma8(PSB16STATE pThis) +{ + sb16CheckAndReOpenOut(pThis); + sb16Control(pThis, 1); +} + +static void dma_cmd8(PSB16STATE pThis, int mask, int dma_len) +{ + pThis->fmt = PDMAUDIOFMT_U8; + pThis->use_hdma = 0; + pThis->fmt_bits = 8; + pThis->fmt_signed = 0; + pThis->fmt_stereo = (pThis->mixer_regs[0x0e] & 2) != 0; + + if (-1 == pThis->time_const) + { + if (pThis->freq <= 0) + pThis->freq = 11025; + } + else + { + int tmp = (256 - pThis->time_const); + pThis->freq = (1000000 + (tmp / 2)) / tmp; + } + + if (dma_len != -1) + { + pThis->block_size = dma_len << pThis->fmt_stereo; + } + else + { + /* This is apparently the only way to make both Act1/PL + and SecondReality/FC work + + r=andy Wow, actually someone who remembers Future Crew :-) + + Act1 sets block size via command 0x48 and it's an odd number + SR does the same with even number + Both use stereo, and Creatives own documentation states that + 0x48 sets block size in bytes less one.. go figure */ + pThis->block_size &= ~pThis->fmt_stereo; + } + + pThis->freq >>= pThis->fmt_stereo; + pThis->left_till_irq = pThis->block_size; + pThis->bytes_per_second = (pThis->freq << pThis->fmt_stereo); + /* pThis->highspeed = (mask & DMA8_HIGH) != 0; */ + pThis->dma_auto = (mask & DMA8_AUTO) != 0; + pThis->align = (1 << pThis->fmt_stereo) - 1; + + if (pThis->block_size & pThis->align) + LogFlowFunc(("warning: misaligned block size %d, alignment %d\n", + pThis->block_size, pThis->align + 1)); + + LogFlowFunc(("freq %d, stereo %d, sign %d, bits %d, dma %d, auto %d, fifo %d, high %d\n", + pThis->freq, pThis->fmt_stereo, pThis->fmt_signed, pThis->fmt_bits, + pThis->block_size, pThis->dma_auto, pThis->fifo, pThis->highspeed)); + + continue_dma8(pThis); + sb16SpeakerControl(pThis, 1); +} + +static void dma_cmd(PSB16STATE pThis, uint8_t cmd, uint8_t d0, int dma_len) +{ + pThis->use_hdma = cmd < 0xc0; + pThis->fifo = (cmd >> 1) & 1; + pThis->dma_auto = (cmd >> 2) & 1; + pThis->fmt_signed = (d0 >> 4) & 1; + pThis->fmt_stereo = (d0 >> 5) & 1; + + switch (cmd >> 4) + { + case 11: + pThis->fmt_bits = 16; + break; + + case 12: + pThis->fmt_bits = 8; + break; + } + + if (-1 != pThis->time_const) + { +#if 1 + int tmp = 256 - pThis->time_const; + pThis->freq = (1000000 + (tmp / 2)) / tmp; +#else + /* pThis->freq = 1000000 / ((255 - pThis->time_const) << pThis->fmt_stereo); */ + pThis->freq = 1000000 / ((255 - pThis->time_const)); +#endif + pThis->time_const = -1; + } + + pThis->block_size = dma_len + 1; + pThis->block_size <<= ((pThis->fmt_bits == 16) ? 1 : 0); + if (!pThis->dma_auto) + { + /* + * It is clear that for DOOM and auto-init this value + * shouldn't take stereo into account, while Miles Sound Systems + * setsound.exe with single transfer mode wouldn't work without it + * wonders of SB16 yet again. + */ + pThis->block_size <<= pThis->fmt_stereo; + } + + LogFlowFunc(("freq %d, stereo %d, sign %d, bits %d, dma %d, auto %d, fifo %d, high %d\n", + pThis->freq, pThis->fmt_stereo, pThis->fmt_signed, pThis->fmt_bits, + pThis->block_size, pThis->dma_auto, pThis->fifo, pThis->highspeed)); + + if (16 == pThis->fmt_bits) + pThis->fmt = pThis->fmt_signed ? PDMAUDIOFMT_S16 : PDMAUDIOFMT_U16; + else + pThis->fmt = pThis->fmt_signed ? PDMAUDIOFMT_S8 : PDMAUDIOFMT_U8; + + pThis->left_till_irq = pThis->block_size; + + pThis->bytes_per_second = (pThis->freq << pThis->fmt_stereo) << ((pThis->fmt_bits == 16) ? 1 : 0); + pThis->highspeed = 0; + pThis->align = (1 << (pThis->fmt_stereo + (pThis->fmt_bits == 16))) - 1; + if (pThis->block_size & pThis->align) + { + LogFlowFunc(("warning: misaligned block size %d, alignment %d\n", + pThis->block_size, pThis->align + 1)); + } + + sb16CheckAndReOpenOut(pThis); + sb16Control(pThis, 1); + sb16SpeakerControl(pThis, 1); +} + +static inline void dsp_out_data (PSB16STATE pThis, uint8_t val) +{ + LogFlowFunc(("outdata %#x\n", val)); + if ((size_t) pThis->out_data_len < sizeof (pThis->out_data)) { + pThis->out_data[pThis->out_data_len++] = val; + } +} + +static inline uint8_t dsp_get_data (PSB16STATE pThis) +{ + if (pThis->in_index) { + return pThis->in2_data[--pThis->in_index]; + } + else { + LogFlowFunc(("buffer underflow\n")); + return 0; + } +} + +static void sb16HandleCommand(PSB16STATE pThis, uint8_t cmd) +{ + LogFlowFunc(("command %#x\n", cmd)); + + if (cmd > 0xaf && cmd < 0xd0) + { + if (cmd & 8) /** @todo Handle recording. */ + LogFlowFunc(("ADC not yet supported (command %#x)\n", cmd)); + + switch (cmd >> 4) + { + case 11: + case 12: + break; + default: + LogFlowFunc(("%#x wrong bits\n", cmd)); + } + + pThis->needed_bytes = 3; + } + else + { + pThis->needed_bytes = 0; + + switch (cmd) + { + case 0x03: + dsp_out_data(pThis, 0x10); /* pThis->csp_param); */ + goto warn; + + case 0x04: + pThis->needed_bytes = 1; + goto warn; + + case 0x05: + pThis->needed_bytes = 2; + goto warn; + + case 0x08: + /* __asm__ ("int3"); */ + goto warn; + + case 0x0e: + pThis->needed_bytes = 2; + goto warn; + + case 0x09: + dsp_out_data(pThis, 0xf8); + goto warn; + + case 0x0f: + pThis->needed_bytes = 1; + goto warn; + + case 0x10: + pThis->needed_bytes = 1; + goto warn; + + case 0x14: + pThis->needed_bytes = 2; + pThis->block_size = 0; + break; + + case 0x1c: /* Auto-Initialize DMA DAC, 8-bit */ + dma_cmd8(pThis, DMA8_AUTO, -1); + break; + + case 0x20: /* Direct ADC, Juice/PL */ + dsp_out_data(pThis, 0xff); + goto warn; + + case 0x35: + LogFlowFunc(("0x35 - MIDI command not implemented\n")); + break; + + case 0x40: + pThis->freq = -1; + pThis->time_const = -1; + pThis->needed_bytes = 1; + break; + + case 0x41: + pThis->freq = -1; + pThis->time_const = -1; + pThis->needed_bytes = 2; + break; + + case 0x42: + pThis->freq = -1; + pThis->time_const = -1; + pThis->needed_bytes = 2; + goto warn; + + case 0x45: + dsp_out_data(pThis, 0xaa); + goto warn; + + case 0x47: /* Continue Auto-Initialize DMA 16bit */ + break; + + case 0x48: + pThis->needed_bytes = 2; + break; + + case 0x74: + pThis->needed_bytes = 2; /* DMA DAC, 4-bit ADPCM */ + LogFlowFunc(("0x75 - DMA DAC, 4-bit ADPCM not implemented\n")); + break; + + case 0x75: /* DMA DAC, 4-bit ADPCM Reference */ + pThis->needed_bytes = 2; + LogFlowFunc(("0x74 - DMA DAC, 4-bit ADPCM Reference not implemented\n")); + break; + + case 0x76: /* DMA DAC, 2.6-bit ADPCM */ + pThis->needed_bytes = 2; + LogFlowFunc(("0x74 - DMA DAC, 2.6-bit ADPCM not implemented\n")); + break; + + case 0x77: /* DMA DAC, 2.6-bit ADPCM Reference */ + pThis->needed_bytes = 2; + LogFlowFunc(("0x74 - DMA DAC, 2.6-bit ADPCM Reference not implemented\n")); + break; + + case 0x7d: + LogFlowFunc(("0x7d - Autio-Initialize DMA DAC, 4-bit ADPCM Reference\n")); + LogFlowFunc(("not implemented\n")); + break; + + case 0x7f: + LogFlowFunc(("0x7d - Autio-Initialize DMA DAC, 2.6-bit ADPCM Reference\n")); + LogFlowFunc(("not implemented\n")); + break; + + case 0x80: + pThis->needed_bytes = 2; + break; + + case 0x90: + case 0x91: + dma_cmd8(pThis, (((cmd & 1) == 0) ? 1 : 0) | DMA8_HIGH, -1); + break; + + case 0xd0: /* halt DMA operation. 8bit */ + sb16Control(pThis, 0); + break; + + case 0xd1: /* speaker on */ + sb16SpeakerControl(pThis, 1); + break; + + case 0xd3: /* speaker off */ + sb16SpeakerControl(pThis, 0); + break; + + case 0xd4: /* continue DMA operation. 8bit */ + /* KQ6 (or maybe Sierras audblst.drv in general) resets + the frequency between halt/continue */ + continue_dma8(pThis); + break; + + case 0xd5: /* halt DMA operation. 16bit */ + sb16Control(pThis, 0); + break; + + case 0xd6: /* continue DMA operation. 16bit */ + sb16Control(pThis, 1); + break; + + case 0xd9: /* exit auto-init DMA after this block. 16bit */ + pThis->dma_auto = 0; + break; + + case 0xda: /* exit auto-init DMA after this block. 8bit */ + pThis->dma_auto = 0; + break; + + case 0xe0: /* DSP identification */ + pThis->needed_bytes = 1; + break; + + case 0xe1: + dsp_out_data(pThis, pThis->ver & 0xff); + dsp_out_data(pThis, pThis->ver >> 8); + break; + + case 0xe2: + pThis->needed_bytes = 1; + goto warn; + + case 0xe3: + { + for (int i = sizeof (e3) - 1; i >= 0; --i) + dsp_out_data(pThis, e3[i]); + + break; + } + + case 0xe4: /* write test reg */ + pThis->needed_bytes = 1; + break; + + case 0xe7: + LogFlowFunc(("Attempt to probe for ESS (0xe7)?\n")); + break; + + case 0xe8: /* read test reg */ + dsp_out_data(pThis, pThis->test_reg); + break; + + case 0xf2: + case 0xf3: + dsp_out_data(pThis, 0xaa); + pThis->mixer_regs[0x82] |= (cmd == 0xf2) ? 1 : 2; + PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 1); + break; + + case 0xf8: + /* Undocumented, used by old Creative diagnostic programs. */ + dsp_out_data (pThis, 0); + goto warn; + + case 0xf9: + pThis->needed_bytes = 1; + goto warn; + + case 0xfa: + dsp_out_data (pThis, 0); + goto warn; + + case 0xfc: /* FIXME */ + dsp_out_data (pThis, 0); + goto warn; + + default: + LogFlowFunc(("Unrecognized command %#x\n", cmd)); + break; + } + } + + if (!pThis->needed_bytes) + LogFlow(("\n")); + +exit: + + if (!pThis->needed_bytes) + pThis->cmd = -1; + else + pThis->cmd = cmd; + + return; + +warn: + LogFlowFunc(("warning: command %#x,%d is not truly understood yet\n", + cmd, pThis->needed_bytes)); + goto exit; +} + +static uint16_t dsp_get_lohi (PSB16STATE pThis) +{ + uint8_t hi = dsp_get_data (pThis); + uint8_t lo = dsp_get_data (pThis); + return (hi << 8) | lo; +} + +static uint16_t dsp_get_hilo (PSB16STATE pThis) +{ + uint8_t lo = dsp_get_data (pThis); + uint8_t hi = dsp_get_data (pThis); + return (hi << 8) | lo; +} + +static void complete(PSB16STATE pThis) +{ + int d0, d1, d2; + LogFlowFunc(("complete command %#x, in_index %d, needed_bytes %d\n", + pThis->cmd, pThis->in_index, pThis->needed_bytes)); + + if (pThis->cmd > 0xaf && pThis->cmd < 0xd0) + { + d2 = dsp_get_data (pThis); + d1 = dsp_get_data (pThis); + d0 = dsp_get_data (pThis); + + if (pThis->cmd & 8) + LogFlowFunc(("ADC params cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", pThis->cmd, d0, d1, d2)); + else + { + LogFlowFunc(("cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", pThis->cmd, d0, d1, d2)); + dma_cmd(pThis, pThis->cmd, d0, d1 + (d2 << 8)); + } + } + else + { + switch (pThis->cmd) + { + case 0x04: + pThis->csp_mode = dsp_get_data (pThis); + pThis->csp_reg83r = 0; + pThis->csp_reg83w = 0; + LogFlowFunc(("CSP command 0x04: mode=%#x\n", pThis->csp_mode)); + break; + + case 0x05: + pThis->csp_param = dsp_get_data (pThis); + pThis->csp_value = dsp_get_data (pThis); + LogFlowFunc(("CSP command 0x05: param=%#x value=%#x\n", + pThis->csp_param, + pThis->csp_value)); + break; + + case 0x0e: + { + d0 = dsp_get_data(pThis); + d1 = dsp_get_data(pThis); + LogFlowFunc(("write CSP register %d <- %#x\n", d1, d0)); + if (d1 == 0x83) + { + LogFlowFunc(("0x83[%d] <- %#x\n", pThis->csp_reg83r, d0)); + pThis->csp_reg83[pThis->csp_reg83r % 4] = d0; + pThis->csp_reg83r += 1; + } + else + pThis->csp_regs[d1] = d0; + break; + } + + case 0x0f: + d0 = dsp_get_data(pThis); + LogFlowFunc(("read CSP register %#x -> %#x, mode=%#x\n", d0, pThis->csp_regs[d0], pThis->csp_mode)); + if (d0 == 0x83) + { + LogFlowFunc(("0x83[%d] -> %#x\n", + pThis->csp_reg83w, + pThis->csp_reg83[pThis->csp_reg83w % 4])); + dsp_out_data (pThis, pThis->csp_reg83[pThis->csp_reg83w % 4]); + pThis->csp_reg83w += 1; + } + else + dsp_out_data(pThis, pThis->csp_regs[d0]); + break; + + case 0x10: + d0 = dsp_get_data(pThis); + LogFlowFunc(("cmd 0x10 d0=%#x\n", d0)); + break; + + case 0x14: + dma_cmd8(pThis, 0, dsp_get_lohi (pThis) + 1); + break; + + case 0x40: + pThis->time_const = dsp_get_data(pThis); + LogFlowFunc(("set time const %d\n", pThis->time_const)); + break; + + case 0x42: /* FT2 sets output freq with this, go figure */ +#if 0 + LogFlowFunc(("cmd 0x42 might not do what it think it should\n")); +#endif + case 0x41: + pThis->freq = dsp_get_hilo(pThis); + LogFlowFunc(("set freq %d\n", pThis->freq)); + break; + + case 0x48: + pThis->block_size = dsp_get_lohi(pThis) + 1; + LogFlowFunc(("set dma block len %d\n", pThis->block_size)); + break; + + case 0x74: + case 0x75: + case 0x76: + case 0x77: + /* ADPCM stuff, ignore */ + break; + + case 0x80: + { + int freq, samples, bytes; + uint64_t ticks; + + freq = pThis->freq > 0 ? pThis->freq : 11025; + samples = dsp_get_lohi (pThis) + 1; + bytes = samples << pThis->fmt_stereo << ((pThis->fmt_bits == 16) ? 1 : 0); + ticks = (bytes * TMTimerGetFreq(pThis->pTimerIRQ)) / freq; + if (ticks < TMTimerGetFreq(pThis->pTimerIRQ) / 1024) + PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 1); + else + TMTimerSet(pThis->pTimerIRQ, TMTimerGet(pThis->pTimerIRQ) + ticks); + LogFlowFunc(("mix silence: %d samples, %d bytes, %RU64 ticks\n", samples, bytes, ticks)); + break; + } + + case 0xe0: + d0 = dsp_get_data(pThis); + pThis->out_data_len = 0; + LogFlowFunc(("E0 data = %#x\n", d0)); + dsp_out_data(pThis, ~d0); + break; + + case 0xe2: + d0 = dsp_get_data(pThis); + LogFlow(("SB16:E2 = %#x\n", d0)); + break; + + case 0xe4: + pThis->test_reg = dsp_get_data(pThis); + break; + + case 0xf9: + d0 = dsp_get_data(pThis); + LogFlowFunc(("command 0xf9 with %#x\n", d0)); + switch (d0) { + case 0x0e: + dsp_out_data(pThis, 0xff); + break; + + case 0x0f: + dsp_out_data(pThis, 0x07); + break; + + case 0x37: + dsp_out_data(pThis, 0x38); + break; + + default: + dsp_out_data(pThis, 0x00); + break; + } + break; + + default: + LogFlowFunc(("complete: unrecognized command %#x\n", pThis->cmd)); + return; + } + } + + LogFlow(("\n")); + pThis->cmd = -1; + return; +} + +static uint8_t sb16MixRegToVol(PSB16STATE pThis, int reg) +{ + /* The SB16 mixer has a 0 to -62dB range in 32 levels (2dB each step). + * We use a 0 to -96dB range in 256 levels (0.375dB each step). + * Only the top 5 bits of a mixer register are used. + */ + uint8_t steps = 31 - (pThis->mixer_regs[reg] >> 3); + uint8_t vol = 255 - steps * 16 / 3; /* (2dB*8) / (0.375dB*8) */ + return vol; +} + +/** + * Returns the device's current master volume. + * + * @param pThis SB16 state. + * @param pVol Where to store the master volume information. + */ +static void sb16GetMasterVolume(PSB16STATE pThis, PPDMAUDIOVOLUME pVol) +{ + /* There's no mute switch, only volume controls. */ + uint8_t lvol = sb16MixRegToVol(pThis, 0x30); + uint8_t rvol = sb16MixRegToVol(pThis, 0x31); + + pVol->fMuted = false; + pVol->uLeft = lvol; + pVol->uRight = rvol; +} + +/** + * Returns the device's current output stream volume. + * + * @param pThis SB16 state. + * @param pVol Where to store the output stream volume information. + */ +static void sb16GetPcmOutVolume(PSB16STATE pThis, PPDMAUDIOVOLUME pVol) +{ + /* There's no mute switch, only volume controls. */ + uint8_t lvol = sb16MixRegToVol(pThis, 0x32); + uint8_t rvol = sb16MixRegToVol(pThis, 0x33); + + pVol->fMuted = false; + pVol->uLeft = lvol; + pVol->uRight = rvol; +} + +static void sb16UpdateVolume(PSB16STATE pThis) +{ + PDMAUDIOVOLUME VolMaster; + sb16GetMasterVolume(pThis, &VolMaster); + + PDMAUDIOVOLUME VolOut; + sb16GetPcmOutVolume(pThis, &VolOut); + + /* Combine the master + output stream volume. */ + PDMAUDIOVOLUME VolCombined; + RT_ZERO(VolCombined); + + VolCombined.fMuted = VolMaster.fMuted || VolOut.fMuted; + if (!VolCombined.fMuted) + { + VolCombined.uLeft = ( (VolOut.uLeft ? VolOut.uLeft : 1) + * (VolMaster.uLeft ? VolMaster.uLeft : 1)) / PDMAUDIO_VOLUME_MAX; + + VolCombined.uRight = ( (VolOut.uRight ? VolOut.uRight : 1) + * (VolMaster.uRight ? VolMaster.uRight : 1)) / PDMAUDIO_VOLUME_MAX; + } + + PSB16DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) + { + if (!pDrv->Out.pStream) + continue; + + int rc2 = pDrv->pConnector->pfnStreamSetVolume(pDrv->pConnector, pDrv->Out.pStream, &VolCombined); + AssertRC(rc2); + } +} + +static void sb16CmdResetLegacy(PSB16STATE pThis) +{ + LogFlowFuncEnter(); + + pThis->freq = 11025; + pThis->fmt_signed = 0; + pThis->fmt_bits = 8; + pThis->fmt_stereo = 0; + + /* At the moment we only have one stream, the output stream. */ + PPDMAUDIOSTREAMCFG pCfg = &pThis->Out.Cfg; + + pCfg->enmDir = PDMAUDIODIR_OUT; + pCfg->DestSource.Dest = PDMAUDIOPLAYBACKDEST_FRONT; + pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + + pCfg->Props.uHz = pThis->freq; + pCfg->Props.cChannels = 1; /* Mono */ + pCfg->Props.cBytes = 1 /* 8-bit */; + pCfg->Props.fSigned = false; + pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cBytes, pCfg->Props.cChannels); + + AssertCompile(sizeof(pCfg->szName) > sizeof("Output")); + strcpy(pCfg->szName, "Output"); + + sb16CloseOut(pThis); +} + +static void sb16CmdReset(PSB16STATE pThis) +{ + PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0); + if (pThis->dma_auto) + { + PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 1); + PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0); + } + + pThis->mixer_regs[0x82] = 0; + pThis->dma_auto = 0; + pThis->in_index = 0; + pThis->out_data_len = 0; + pThis->left_till_irq = 0; + pThis->needed_bytes = 0; + pThis->block_size = -1; + pThis->nzero = 0; + pThis->highspeed = 0; + pThis->v2x6 = 0; + pThis->cmd = -1; + + dsp_out_data(pThis, 0xaa); + sb16SpeakerControl(pThis, 0); + + sb16Control(pThis, 0); + sb16CmdResetLegacy(pThis); +} + +/** + * @callback_method_impl{PFNIOMIOPORTOUT} + */ +static DECLCALLBACK(int) dsp_write(PPDMDEVINS pDevIns, void *opaque, RTIOPORT nport, uint32_t val, unsigned cb) +{ + RT_NOREF(pDevIns, cb); + PSB16STATE pThis = (PSB16STATE)opaque; + int iport = nport - pThis->port; + + LogFlowFunc(("write %#x <- %#x\n", nport, val)); + switch (iport) + { + case 0x06: + switch (val) + { + case 0x00: + { + if (pThis->v2x6 == 1) + { + if (0 && pThis->highspeed) + { + pThis->highspeed = 0; + PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0); + sb16Control(pThis, 0); + } + else + sb16CmdReset(pThis); + } + pThis->v2x6 = 0; + break; + } + + case 0x01: + case 0x03: /* FreeBSD kludge */ + pThis->v2x6 = 1; + break; + + case 0xc6: + pThis->v2x6 = 0; /* Prince of Persia, csp.sys, diagnose.exe */ + break; + + case 0xb8: /* Panic */ + sb16CmdReset(pThis); + break; + + case 0x39: + dsp_out_data(pThis, 0x38); + sb16CmdReset(pThis); + pThis->v2x6 = 0x39; + break; + + default: + pThis->v2x6 = val; + break; + } + break; + + case 0x0c: /* Write data or command | write status */ +#if 0 + if (pThis->highspeed) + break; +#endif + if (0 == pThis->needed_bytes) + { + sb16HandleCommand(pThis, val); +#if 0 + if (0 == pThis->needed_bytes) { + log_dsp (pThis); + } +#endif + } + else + { + if (pThis->in_index == sizeof (pThis->in2_data)) + { + LogFlowFunc(("in data overrun\n")); + } + else + { + pThis->in2_data[pThis->in_index++] = val; + if (pThis->in_index == pThis->needed_bytes) + { + pThis->needed_bytes = 0; + complete (pThis); +#if 0 + log_dsp (pThis); +#endif + } + } + } + break; + + default: + LogFlowFunc(("nport=%#x, val=%#x)\n", nport, val)); + break; + } + + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{PFNIOMIOPORTIN} + */ +static DECLCALLBACK(int) dsp_read(PPDMDEVINS pDevIns, void *opaque, RTIOPORT nport, uint32_t *pu32, unsigned cb) +{ + RT_NOREF(pDevIns, cb); + PSB16STATE pThis = (PSB16STATE)opaque; + int iport, retval, ack = 0; + + iport = nport - pThis->port; + + /** @todo reject non-byte access? + * The spec does not mention a non-byte access so we should check how real hardware behaves. */ + + switch (iport) + { + case 0x06: /* reset */ + retval = 0xff; + break; + + case 0x0a: /* read data */ + if (pThis->out_data_len) + { + retval = pThis->out_data[--pThis->out_data_len]; + pThis->last_read_byte = retval; + } + else + { + if (pThis->cmd != -1) + LogFlowFunc(("empty output buffer for command %#x\n", pThis->cmd)); + retval = pThis->last_read_byte; + /* goto error; */ + } + break; + + case 0x0c: /* 0 can write */ + retval = pThis->can_write ? 0 : 0x80; + break; + + case 0x0d: /* timer interrupt clear */ + /* LogFlowFunc(("timer interrupt clear\n")); */ + retval = 0; + break; + + case 0x0e: /* data available status | irq 8 ack */ + retval = (!pThis->out_data_len || pThis->highspeed) ? 0 : 0x80; + if (pThis->mixer_regs[0x82] & 1) + { + ack = 1; + pThis->mixer_regs[0x82] &= ~1; + PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0); + } + break; + + case 0x0f: /* irq 16 ack */ + retval = 0xff; + if (pThis->mixer_regs[0x82] & 2) + { + ack = 1; + pThis->mixer_regs[0x82] &= ~2; + PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0); + } + break; + + default: + goto error; + } + + if (!ack) + LogFlowFunc(("read %#x -> %#x\n", nport, retval)); + + *pu32 = retval; + return VINF_SUCCESS; + + error: + LogFlowFunc(("warning: dsp_read %#x error\n", nport)); + return VERR_IOM_IOPORT_UNUSED; +} + +static void sb16MixerReset(PSB16STATE pThis) +{ + memset(pThis->mixer_regs, 0xff, 0x7f); + memset(pThis->mixer_regs + 0x83, 0xff, sizeof (pThis->mixer_regs) - 0x83); + + pThis->mixer_regs[0x02] = 4; /* master volume 3bits */ + pThis->mixer_regs[0x06] = 4; /* MIDI volume 3bits */ + pThis->mixer_regs[0x08] = 0; /* CD volume 3bits */ + pThis->mixer_regs[0x0a] = 0; /* voice volume 2bits */ + + /* d5=input filt, d3=lowpass filt, d1,d2=input source */ + pThis->mixer_regs[0x0c] = 0; + + /* d5=output filt, d1=stereo switch */ + pThis->mixer_regs[0x0e] = 0; + + /* voice volume L d5,d7, R d1,d3 */ + pThis->mixer_regs[0x04] = (12 << 4) | 12; + /* master ... */ + pThis->mixer_regs[0x22] = (12 << 4) | 12; + /* MIDI ... */ + pThis->mixer_regs[0x26] = (12 << 4) | 12; + + /* master/voice/MIDI L/R volume */ + for (int i = 0x30; i < 0x36; i++) + pThis->mixer_regs[i] = 24 << 3; /* -14 dB */ + + /* treble/bass */ + for (int i = 0x44; i < 0x48; i++) + pThis->mixer_regs[i] = 0x80; + + /* Update the master (mixer) and PCM out volumes. */ + sb16UpdateVolume(pThis); +} + +static int mixer_write_indexb(PSB16STATE pThis, uint8_t val) +{ + pThis->mixer_nreg = val; + return VINF_SUCCESS; +} + +uint32_t popcount(uint32_t u) /** @todo r=andy WTF? */ +{ + u = ((u&0x55555555) + ((u>>1)&0x55555555)); + u = ((u&0x33333333) + ((u>>2)&0x33333333)); + u = ((u&0x0f0f0f0f) + ((u>>4)&0x0f0f0f0f)); + u = ((u&0x00ff00ff) + ((u>>8)&0x00ff00ff)); + u = ( u&0x0000ffff) + (u>>16); + return u; +} + +uint32_t lsbindex(uint32_t u) +{ + return popcount((u & -(int32_t)u) - 1); +} + +/* Convert SB16 to SB Pro mixer volume (left). */ +static inline void sb16ConvVolumeL(PSB16STATE pThis, unsigned reg, uint8_t val) +{ + /* High nibble in SBP mixer. */ + pThis->mixer_regs[reg] = (pThis->mixer_regs[reg] & 0x0f) | (val & 0xf0); +} + +/* Convert SB16 to SB Pro mixer volume (right). */ +static inline void sb16ConvVolumeR(PSB16STATE pThis, unsigned reg, uint8_t val) +{ + /* Low nibble in SBP mixer. */ + pThis->mixer_regs[reg] = (pThis->mixer_regs[reg] & 0xf0) | (val >> 4); +} + +/* Convert SB Pro to SB16 mixer volume (left + right). */ +static inline void sb16ConvVolumeOldToNew(PSB16STATE pThis, unsigned reg, uint8_t val) +{ + /* Left channel. */ + pThis->mixer_regs[reg + 0] = (val & 0xf0) | RT_BIT(3); + /* Right channel (the register immediately following). */ + pThis->mixer_regs[reg + 1] = (val << 4) | RT_BIT(3); +} + + +static int mixer_write_datab(PSB16STATE pThis, uint8_t val) +{ + bool fUpdateMaster = false; + bool fUpdateStream = false; + + LogFlowFunc(("mixer_write [%#x] <- %#x\n", pThis->mixer_nreg, val)); + + switch (pThis->mixer_nreg) + { + case 0x00: + sb16MixerReset(pThis); + /* And update the actual volume, too. */ + fUpdateMaster = true; + fUpdateStream = true; + break; + + case 0x04: /* Translate from old style voice volume (L/R). */ + sb16ConvVolumeOldToNew(pThis, 0x32, val); + fUpdateStream = true; + break; + + case 0x22: /* Translate from old style master volume (L/R). */ + sb16ConvVolumeOldToNew(pThis, 0x30, val); + fUpdateMaster = true; + break; + + case 0x26: /* Translate from old style MIDI volume (L/R). */ + sb16ConvVolumeOldToNew(pThis, 0x34, val); + break; + + case 0x28: /* Translate from old style CD volume (L/R). */ + sb16ConvVolumeOldToNew(pThis, 0x36, val); + break; + + case 0x2E: /* Translate from old style line volume (L/R). */ + sb16ConvVolumeOldToNew(pThis, 0x38, val); + break; + + case 0x30: /* Translate to old style master volume (L). */ + sb16ConvVolumeL(pThis, 0x22, val); + fUpdateMaster = true; + break; + + case 0x31: /* Translate to old style master volume (R). */ + sb16ConvVolumeR(pThis, 0x22, val); + fUpdateMaster = true; + break; + + case 0x32: /* Translate to old style voice volume (L). */ + sb16ConvVolumeL(pThis, 0x04, val); + fUpdateStream = true; + break; + + case 0x33: /* Translate to old style voice volume (R). */ + sb16ConvVolumeR(pThis, 0x04, val); + fUpdateStream = true; + break; + + case 0x34: /* Translate to old style MIDI volume (L). */ + sb16ConvVolumeL(pThis, 0x26, val); + break; + + case 0x35: /* Translate to old style MIDI volume (R). */ + sb16ConvVolumeR(pThis, 0x26, val); + break; + + case 0x36: /* Translate to old style CD volume (L). */ + sb16ConvVolumeL(pThis, 0x28, val); + break; + + case 0x37: /* Translate to old style CD volume (R). */ + sb16ConvVolumeR(pThis, 0x28, val); + break; + + case 0x38: /* Translate to old style line volume (L). */ + sb16ConvVolumeL(pThis, 0x2E, val); + break; + + case 0x39: /* Translate to old style line volume (R). */ + sb16ConvVolumeR(pThis, 0x2E, val); + break; + + case 0x80: + { + int irq = irq_of_magic(val); + LogFlowFunc(("setting irq to %d (val=%#x)\n", irq, val)); + if (irq > 0) + pThis->irq = irq; + break; + } + + case 0x81: + { + int dma, hdma; + + dma = lsbindex (val & 0xf); + hdma = lsbindex (val & 0xf0); + if (dma != pThis->dma || hdma != pThis->hdma) + LogFlow(("SB16: attempt to change DMA 8bit %d(%d), 16bit %d(%d) (val=%#x)\n", + dma, pThis->dma, hdma, pThis->hdma, val)); +#if 0 + pThis->dma = dma; + pThis->hdma = hdma; +#endif + break; + } + + case 0x82: + LogFlowFunc(("attempt to write into IRQ status register (val=%#x)\n", val)); + return VINF_SUCCESS; + + default: + if (pThis->mixer_nreg >= 0x80) + LogFlowFunc(("attempt to write mixer[%#x] <- %#x\n", pThis->mixer_nreg, val)); + break; + } + + pThis->mixer_regs[pThis->mixer_nreg] = val; + + /* Update the master (mixer) volume. */ + if ( fUpdateMaster + || fUpdateStream) + { + sb16UpdateVolume(pThis); + } + + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{PFNIOMIOPORTOUT} + */ +static DECLCALLBACK(int) mixer_write(PPDMDEVINS pDevIns, void *opaque, RTIOPORT nport, uint32_t val, unsigned cb) +{ + RT_NOREF(pDevIns); + PSB16STATE pThis = (PSB16STATE)opaque; + int iport = nport - pThis->port; + switch (cb) + { + case 1: + switch (iport) + { + case 4: + mixer_write_indexb(pThis, val); + break; + case 5: + mixer_write_datab(pThis, val); + break; + } + break; + case 2: + mixer_write_indexb(pThis, val & 0xff); + mixer_write_datab(pThis, (val >> 8) & 0xff); + break; + default: + AssertMsgFailed(("Port=%#x cb=%d u32=%#x\n", nport, cb, val)); + break; + } + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{PFNIOMIOPORTIN} + */ +static DECLCALLBACK(int) mixer_read(PPDMDEVINS pDevIns, void *opaque, RTIOPORT nport, uint32_t *pu32, unsigned cb) +{ + RT_NOREF(pDevIns, cb, nport); + PSB16STATE pThis = (PSB16STATE)opaque; + +#ifndef DEBUG_SB16_MOST + if (pThis->mixer_nreg != 0x82) + LogFlowFunc(("mixer_read[%#x] -> %#x\n", pThis->mixer_nreg, pThis->mixer_regs[pThis->mixer_nreg])); +#else + LogFlowFunc(("mixer_read[%#x] -> %#x\n", pThis->mixer_nreg, pThis->mixer_regs[pThis->mixer_nreg])); +#endif + *pu32 = pThis->mixer_regs[pThis->mixer_nreg]; + return VINF_SUCCESS; +} + +/** + * Called by sb16DMARead. + */ +static int sb16WriteAudio(PSB16STATE pThis, int nchan, uint32_t dma_pos, uint32_t dma_len, int len) +{ + uint8_t tmpbuf[_4K]; /** @todo Have a buffer on the heap. */ + uint32_t cbToWrite = len; + uint32_t cbWrittenTotal = 0; + + while (cbToWrite) + { + uint32_t cbToRead = RT_MIN(dma_len - dma_pos, cbToWrite); + if (cbToRead > sizeof(tmpbuf)) + cbToRead = sizeof(tmpbuf); + + uint32_t cbRead = 0; + int rc2 = PDMDevHlpDMAReadMemory(pThis->pDevInsR3, nchan, tmpbuf, dma_pos, cbToRead, &cbRead); + AssertMsgRC(rc2, (" from DMA failed: %Rrc\n", rc2)); + +#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA + if (cbRead) + { + RTFILE fh; + RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "sb16WriteAudio.pcm", + RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + RTFileWrite(fh, tmpbuf, cbRead, NULL); + RTFileClose(fh); + } +#endif + /* + * Write data to the backends. + */ + uint32_t cbWritten = 0; + + /* Just multiplex the output to the connected backends. + * No need to utilize the virtual mixer here (yet). */ + PSB16DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) + { + if (!pDrv->Out.pStream) + continue; + + if (!DrvAudioHlpStreamStatusCanWrite(pDrv->pConnector->pfnStreamGetStatus(pDrv->pConnector, pDrv->Out.pStream))) + continue; + + uint32_t cbWrittenToStream = 0; + rc2 = pDrv->pConnector->pfnStreamWrite(pDrv->pConnector, pDrv->Out.pStream, tmpbuf, cbRead, &cbWrittenToStream); + + LogFlowFunc(("\tLUN#%RU8: rc=%Rrc, cbWrittenToStream=%RU32\n", pDrv->uLUN, rc2, cbWrittenToStream)); + } + + cbWritten = cbRead; /* Always report everything written, as the backends need to keep up themselves. */ + + LogFlowFunc(("\tcbToRead=%RU32, cbToWrite=%RU32, cbWritten=%RU32, cbLeft=%RU32\n", + cbToRead, cbToWrite, cbWritten, cbToWrite - cbWrittenTotal)); + + Assert(cbToWrite >= cbWritten); + cbToWrite -= cbWritten; + dma_pos = (dma_pos + cbWritten) % dma_len; + cbWrittenTotal += cbWritten; + + if (!cbWritten) + break; + } + + return cbWrittenTotal; +} + +/** + * @callback_method_impl{FNDMATRANSFERHANDLER, + * Worker callback for both DMA channels.} + */ +static DECLCALLBACK(uint32_t) sb16DMARead(PPDMDEVINS pDevIns, void *opaque, unsigned nchan, uint32_t dma_pos, uint32_t dma_len) +{ + RT_NOREF(pDevIns); + PSB16STATE pThis = (PSB16STATE)opaque; + int till, copy, written, free; + + if (pThis->block_size <= 0) + { + LogFlowFunc(("invalid block size=%d nchan=%d dma_pos=%d dma_len=%d\n", + pThis->block_size, nchan, dma_pos, dma_len)); + return dma_pos; + } + + if (pThis->left_till_irq < 0) + pThis->left_till_irq = pThis->block_size; + + free = dma_len; + + copy = free; + till = pThis->left_till_irq; + +#ifdef DEBUG_SB16_MOST + LogFlowFunc(("pos:%06d %d till:%d len:%d\n", dma_pos, free, till, dma_len)); +#endif + + if (copy >= till) + { + if (0 == pThis->dma_auto) + { + copy = till; + } + else + { + if (copy >= till + pThis->block_size) + copy = till; /* Make sure we won't skip IRQs. */ + } + } + + written = sb16WriteAudio(pThis, nchan, dma_pos, dma_len, copy); + dma_pos = (dma_pos + written) % dma_len; + pThis->left_till_irq -= written; + + if (pThis->left_till_irq <= 0) + { + pThis->mixer_regs[0x82] |= (nchan & 4) ? 2 : 1; + PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 1); + if (0 == pThis->dma_auto) + { + sb16Control(pThis, 0); + sb16SpeakerControl(pThis, 0); + } + } + + Log3Func(("pos %d/%d free %5d till %5d copy %5d written %5d block_size %5d\n", + dma_pos, dma_len, free, pThis->left_till_irq, copy, written, + pThis->block_size)); + + while (pThis->left_till_irq <= 0) + pThis->left_till_irq += pThis->block_size; + + return dma_pos; +} + +static void sb16TimerMaybeStart(PSB16STATE pThis) +{ + LogFlowFunc(("cStreamsActive=%RU8\n", pThis->cStreamsActive)); + + if (pThis->cStreamsActive == 0) /* Only start the timer if there are no active streams. */ + return; + + if (!pThis->pTimerIO) + return; + + /* Set timer flag. */ + ASMAtomicXchgBool(&pThis->fTimerActive, true); + + /* Update current time timestamp. */ + pThis->uTimerTSIO = TMTimerGet(pThis->pTimerIO); + + /* Fire off timer. */ + TMTimerSet(pThis->pTimerIO, TMTimerGet(pThis->pTimerIO) + pThis->cTimerTicksIO); +} + +static void sb16TimerMaybeStop(PSB16STATE pThis) +{ + LogFlowFunc(("cStreamsActive=%RU8\n", pThis->cStreamsActive)); + + if (pThis->cStreamsActive) /* Some streams still active? Bail out. */ + return; + + if (!pThis->pTimerIO) + return; + + /* Set timer flag. */ + ASMAtomicXchgBool(&pThis->fTimerActive, false); +} + +/** + * @callback_method_impl{FNTMTIMERDEV} + */ +static DECLCALLBACK(void) sb16TimerIO(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) +{ + RT_NOREF(pDevIns); + PSB16STATE pThis = (PSB16STATE)pvUser; + Assert(pThis == PDMINS_2_DATA(pDevIns, PSB16STATE)); + AssertPtr(pThis); + + uint64_t cTicksNow = TMTimerGet(pTimer); + bool fIsPlaying = false; /* Whether one or more streams are still playing. */ + bool fDoTransfer = false; + + pThis->uTimerTSIO = cTicksNow; + + PSB16DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) + { + PPDMAUDIOSTREAM pStream = pDrv->Out.pStream; + if (!pStream) + continue; + + PPDMIAUDIOCONNECTOR pConn = pDrv->pConnector; + if (!pConn) + continue; + + int rc2 = pConn->pfnStreamIterate(pConn, pStream); + if (RT_SUCCESS(rc2)) + { + rc2 = pConn->pfnStreamPlay(pConn, pStream, NULL /* cPlayed */); + if (RT_FAILURE(rc2)) + { + LogFlowFunc(("%s: Failed playing stream, rc=%Rrc\n", pStream->szName, rc2)); + continue; + } + + /* Only do the next DMA transfer if we're able to write the remaining data block. */ + fDoTransfer = pConn->pfnStreamGetWritable(pConn, pStream) > (unsigned)pThis->left_till_irq; + } + + PDMAUDIOSTREAMSTS strmSts = pConn->pfnStreamGetStatus(pConn, pStream); + fIsPlaying |= ( (strmSts & PDMAUDIOSTREAMSTS_FLAG_ENABLED) + || (strmSts & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE)); + } + + bool fTimerActive = ASMAtomicReadBool(&pThis->fTimerActive); + bool fKickTimer = fTimerActive || fIsPlaying; + + LogFlowFunc(("fTimerActive=%RTbool, fIsPlaying=%RTbool\n", fTimerActive, fIsPlaying)); + + if (fDoTransfer) + { + /* Schedule the next transfer. */ + PDMDevHlpDMASchedule(pThis->pDevInsR3); + + /* Kick the timer at least one more time. */ + fKickTimer = true; + } + + /* + * Recording. + */ + /** @todo Implement recording. */ + + if (fKickTimer) + { + /* Kick the timer again. */ + uint64_t cTicks = pThis->cTimerTicksIO; + /** @todo adjust cTicks down by now much cbOutMin represents. */ + TMTimerSet(pThis->pTimerIO, cTicksNow + cTicks); + } +} + +/** + * @callback_method_impl{FNSSMDEVLIVEEXEC} + */ +static DECLCALLBACK(int) sb16LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass) +{ + RT_NOREF(uPass); + PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE); + + SSMR3PutS32(pSSM, pThis->irqCfg); + SSMR3PutS32(pSSM, pThis->dmaCfg); + SSMR3PutS32(pSSM, pThis->hdmaCfg); + SSMR3PutS32(pSSM, pThis->portCfg); + SSMR3PutS32(pSSM, pThis->verCfg); + return VINF_SSM_DONT_CALL_AGAIN; +} + +/** + * Worker for sb16SaveExec. + */ +static int sb16Save(PSSMHANDLE pSSM, PSB16STATE pThis) +{ + SSMR3PutS32(pSSM, pThis->irq); + SSMR3PutS32(pSSM, pThis->dma); + SSMR3PutS32(pSSM, pThis->hdma); + SSMR3PutS32(pSSM, pThis->port); + SSMR3PutS32(pSSM, pThis->ver); + SSMR3PutS32(pSSM, pThis->in_index); + SSMR3PutS32(pSSM, pThis->out_data_len); + SSMR3PutS32(pSSM, pThis->fmt_stereo); + SSMR3PutS32(pSSM, pThis->fmt_signed); + SSMR3PutS32(pSSM, pThis->fmt_bits); + + SSMR3PutU32(pSSM, pThis->fmt); + + SSMR3PutS32(pSSM, pThis->dma_auto); + SSMR3PutS32(pSSM, pThis->block_size); + SSMR3PutS32(pSSM, pThis->fifo); + SSMR3PutS32(pSSM, pThis->freq); + SSMR3PutS32(pSSM, pThis->time_const); + SSMR3PutS32(pSSM, pThis->speaker); + SSMR3PutS32(pSSM, pThis->needed_bytes); + SSMR3PutS32(pSSM, pThis->cmd); + SSMR3PutS32(pSSM, pThis->use_hdma); + SSMR3PutS32(pSSM, pThis->highspeed); + SSMR3PutS32(pSSM, pThis->can_write); + SSMR3PutS32(pSSM, pThis->v2x6); + + SSMR3PutU8 (pSSM, pThis->csp_param); + SSMR3PutU8 (pSSM, pThis->csp_value); + SSMR3PutU8 (pSSM, pThis->csp_mode); + SSMR3PutU8 (pSSM, pThis->csp_param); /* Bug compatible! */ + SSMR3PutMem(pSSM, pThis->csp_regs, 256); + SSMR3PutU8 (pSSM, pThis->csp_index); + SSMR3PutMem(pSSM, pThis->csp_reg83, 4); + SSMR3PutS32(pSSM, pThis->csp_reg83r); + SSMR3PutS32(pSSM, pThis->csp_reg83w); + + SSMR3PutMem(pSSM, pThis->in2_data, sizeof(pThis->in2_data)); + SSMR3PutMem(pSSM, pThis->out_data, sizeof(pThis->out_data)); + SSMR3PutU8 (pSSM, pThis->test_reg); + SSMR3PutU8 (pSSM, pThis->last_read_byte); + + SSMR3PutS32(pSSM, pThis->nzero); + SSMR3PutS32(pSSM, pThis->left_till_irq); + SSMR3PutS32(pSSM, pThis->dma_running); + SSMR3PutS32(pSSM, pThis->bytes_per_second); + SSMR3PutS32(pSSM, pThis->align); + + SSMR3PutS32(pSSM, pThis->mixer_nreg); + return SSMR3PutMem(pSSM, pThis->mixer_regs, 256); +} + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) sb16SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE); + + sb16LiveExec(pDevIns, pSSM, 0); + return sb16Save(pSSM, pThis); +} + +/** + * Worker for sb16LoadExec. + */ +static int sb16Load(PSSMHANDLE pSSM, PSB16STATE pThis) +{ + SSMR3GetS32(pSSM, &pThis->irq); + SSMR3GetS32(pSSM, &pThis->dma); + SSMR3GetS32(pSSM, &pThis->hdma); + SSMR3GetS32(pSSM, &pThis->port); + SSMR3GetS32(pSSM, &pThis->ver); + SSMR3GetS32(pSSM, &pThis->in_index); + SSMR3GetS32(pSSM, &pThis->out_data_len); + SSMR3GetS32(pSSM, &pThis->fmt_stereo); + SSMR3GetS32(pSSM, &pThis->fmt_signed); + SSMR3GetS32(pSSM, &pThis->fmt_bits); + + SSMR3GetU32(pSSM, (uint32_t *)&pThis->fmt); + + SSMR3GetS32(pSSM, &pThis->dma_auto); + SSMR3GetS32(pSSM, &pThis->block_size); + SSMR3GetS32(pSSM, &pThis->fifo); + SSMR3GetS32(pSSM, &pThis->freq); + SSMR3GetS32(pSSM, &pThis->time_const); + SSMR3GetS32(pSSM, &pThis->speaker); + SSMR3GetS32(pSSM, &pThis->needed_bytes); + SSMR3GetS32(pSSM, &pThis->cmd); + SSMR3GetS32(pSSM, &pThis->use_hdma); + SSMR3GetS32(pSSM, &pThis->highspeed); + SSMR3GetS32(pSSM, &pThis->can_write); + SSMR3GetS32(pSSM, &pThis->v2x6); + + SSMR3GetU8 (pSSM, &pThis->csp_param); + SSMR3GetU8 (pSSM, &pThis->csp_value); + SSMR3GetU8 (pSSM, &pThis->csp_mode); + SSMR3GetU8 (pSSM, &pThis->csp_param); /* Bug compatible! */ + SSMR3GetMem(pSSM, pThis->csp_regs, 256); + SSMR3GetU8 (pSSM, &pThis->csp_index); + SSMR3GetMem(pSSM, pThis->csp_reg83, 4); + SSMR3GetS32(pSSM, &pThis->csp_reg83r); + SSMR3GetS32(pSSM, &pThis->csp_reg83w); + + SSMR3GetMem(pSSM, pThis->in2_data, sizeof(pThis->in2_data)); + SSMR3GetMem(pSSM, pThis->out_data, sizeof(pThis->out_data)); + SSMR3GetU8 (pSSM, &pThis->test_reg); + SSMR3GetU8 (pSSM, &pThis->last_read_byte); + + SSMR3GetS32(pSSM, &pThis->nzero); + SSMR3GetS32(pSSM, &pThis->left_till_irq); + SSMR3GetS32(pSSM, &pThis->dma_running); + SSMR3GetS32(pSSM, &pThis->bytes_per_second); + SSMR3GetS32(pSSM, &pThis->align); + + int32_t mixer_nreg = 0; + int rc = SSMR3GetS32(pSSM, &mixer_nreg); + AssertRCReturn(rc, rc); + pThis->mixer_nreg = (uint8_t)mixer_nreg; + rc = SSMR3GetMem(pSSM, pThis->mixer_regs, 256); + AssertRCReturn(rc, rc); + + if (pThis->dma_running) + { + sb16CheckAndReOpenOut(pThis); + sb16Control(pThis, 1); + sb16SpeakerControl(pThis, pThis->speaker); + } + + /* Update the master (mixer) and PCM out volumes. */ + sb16UpdateVolume(pThis); + + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) sb16LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE); + + AssertMsgReturn( uVersion == SB16_SAVE_STATE_VERSION + || uVersion == SB16_SAVE_STATE_VERSION_VBOX_30, + ("%u\n", uVersion), + VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION); + if (uVersion > SB16_SAVE_STATE_VERSION_VBOX_30) + { + int32_t irq; + SSMR3GetS32 (pSSM, &irq); + int32_t dma; + SSMR3GetS32 (pSSM, &dma); + int32_t hdma; + SSMR3GetS32 (pSSM, &hdma); + int32_t port; + SSMR3GetS32 (pSSM, &port); + int32_t ver; + int rc = SSMR3GetS32 (pSSM, &ver); + AssertRCReturn (rc, rc); + + if ( irq != pThis->irqCfg + || dma != pThis->dmaCfg + || hdma != pThis->hdmaCfg + || port != pThis->portCfg + || ver != pThis->verCfg) + { + return SSMR3SetCfgError(pSSM, RT_SRC_POS, + N_("config changed: irq=%x/%x dma=%x/%x hdma=%x/%x port=%x/%x ver=%x/%x (saved/config)"), + irq, pThis->irqCfg, + dma, pThis->dmaCfg, + hdma, pThis->hdmaCfg, + port, pThis->portCfg, + ver, pThis->verCfg); + } + } + + if (uPass != SSM_PASS_FINAL) + return VINF_SUCCESS; + + return sb16Load(pSSM, pThis); +} + +/** + * Creates a PDM audio stream for a specific driver. + * + * @returns IPRT status code. + * @param pThis SB16 state. + * @param pCfg Stream configuration to use. + * @param pDrv Driver stream to create PDM stream for. + */ +static int sb16CreateDrvStream(PSB16STATE pThis, PPDMAUDIOSTREAMCFG pCfg, PSB16DRIVER pDrv) +{ + RT_NOREF(pThis); + + AssertReturn(pCfg->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + Assert(DrvAudioHlpStreamCfgIsValid(pCfg)); + + PPDMAUDIOSTREAMCFG pCfgHost = DrvAudioHlpStreamCfgDup(pCfg); + if (!pCfgHost) + return VERR_NO_MEMORY; + + LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pCfgHost->szName)); + + AssertMsg(pDrv->Out.pStream == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN)); + + /* Disable pre-buffering for SB16; not needed for that bit of data. */ + pCfgHost->Backend.cfPreBuf = 0; + + int rc = pDrv->pConnector->pfnStreamCreate(pDrv->pConnector, pCfgHost, pCfg /* pCfgGuest */, &pDrv->Out.pStream); + if (RT_SUCCESS(rc)) + { + pDrv->pConnector->pfnStreamRetain(pDrv->pConnector, pDrv->Out.pStream); + LogFlowFunc(("LUN#%RU8: Created output \"%s\", rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc)); + } + + DrvAudioHlpStreamCfgFree(pCfgHost); + + return rc; +} + +/** + * Destroys a PDM audio stream of a specific driver. + * + * @param pThis SB16 state. + * @param pDrv Driver stream to destroy PDM stream for. + */ +static void sb16DestroyDrvStream(PSB16STATE pThis, PSB16DRIVER pDrv) +{ + AssertPtrReturnVoid(pThis); + AssertPtrReturnVoid(pDrv); + + if (pDrv->Out.pStream) + { + pDrv->pConnector->pfnStreamRelease(pDrv->pConnector, pDrv->Out.pStream); + + int rc2 = pDrv->pConnector->pfnStreamControl(pDrv->pConnector, pDrv->Out.pStream, PDMAUDIOSTREAMCMD_DISABLE); + AssertRC(rc2); + + rc2 = pDrv->pConnector->pfnStreamDestroy(pDrv->pConnector, pDrv->Out.pStream); + AssertRC(rc2); + + pDrv->Out.pStream = NULL; + } +} + +/** + * Checks if the output stream needs to be (re-)created and does so if needed. + * + * @return VBox status code. + * @param pThis SB16 state. + */ +static int sb16CheckAndReOpenOut(PSB16STATE pThis) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + if (pThis->freq > 0) + { + /* At the moment we only have one stream, the output stream. */ + PDMAUDIOSTREAMCFG Cfg; + RT_ZERO(Cfg); + + Cfg.Props.uHz = pThis->freq; + Cfg.Props.cChannels = 1 << pThis->fmt_stereo; + Cfg.Props.cBytes = pThis->fmt_bits / 8; + Cfg.Props.fSigned = RT_BOOL(pThis->fmt_signed); + Cfg.Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(Cfg.Props.cBytes, Cfg.Props.cChannels); + + if (!DrvAudioHlpPCMPropsAreEqual(&Cfg.Props, &pThis->Out.Cfg.Props)) + { + Cfg.enmDir = PDMAUDIODIR_OUT; + Cfg.DestSource.Dest = PDMAUDIOPLAYBACKDEST_FRONT; + Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + + strcpy(Cfg.szName, "Output"); + + sb16CloseOut(pThis); + + rc = sb16OpenOut(pThis, &Cfg); + AssertRC(rc); + } + } + else + sb16CloseOut(pThis); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static int sb16OpenOut(PSB16STATE pThis, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + if (!DrvAudioHlpStreamCfgIsValid(pCfg)) + return VERR_INVALID_PARAMETER; + + int rc = DrvAudioHlpStreamCfgCopy(&pThis->Out.Cfg, pCfg); + if (RT_FAILURE(rc)) + return rc; + + /* Set scheduling hint (if available). */ + if (pThis->cTimerTicksIO) + pThis->Out.Cfg.Device.uSchedulingHintMs = 1000 /* ms */ / (TMTimerGetFreq(pThis->pTimerIO) / pThis->cTimerTicksIO); + + PSB16DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) + { + int rc2 = sb16CreateDrvStream(pThis, &pThis->Out.Cfg, pDrv); + if (RT_FAILURE(rc2)) + LogFunc(("Attaching stream failed with %Rrc\n", rc2)); + + /* Do not pass failure to rc here, as there might be drivers which aren't + * configured / ready yet. */ + } + + sb16UpdateVolume(pThis); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static void sb16CloseOut(PSB16STATE pThis) +{ + AssertPtrReturnVoid(pThis); + + LogFlowFuncEnter(); + + PSB16DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) + sb16DestroyDrvStream(pThis, pDrv); + + LogFlowFuncLeave(); +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) sb16QueryInterface(struct PDMIBASE *pInterface, const char *pszIID) +{ + PSB16STATE pThis = RT_FROM_MEMBER(pInterface, SB16STATE, IBase); + Assert(&pThis->IBase == pInterface); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase); + return NULL; +} + + +/** + * Attach command, internal version. + * + * This is called to let the device attach to a driver for a specified LUN + * during runtime. This is not called during VM construction, the device + * constructor has to attach to all the available drivers. + * + * @returns VBox status code. + * @param pThis SB16 state. + * @param uLUN The logical unit which is being detached. + * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines. + * @param ppDrv Attached driver instance on success. Optional. + */ +static int sb16AttachInternal(PSB16STATE pThis, unsigned uLUN, uint32_t fFlags, PSB16DRIVER *ppDrv) +{ + RT_NOREF(fFlags); + + /* + * Attach driver. + */ + char *pszDesc; + if (RTStrAPrintf(&pszDesc, "Audio driver port (SB16) for LUN #%u", uLUN) <= 0) + AssertLogRelFailedReturn(VERR_NO_MEMORY); + + PPDMIBASE pDrvBase; + int rc = PDMDevHlpDriverAttach(pThis->pDevInsR3, uLUN, + &pThis->IBase, &pDrvBase, pszDesc); + if (RT_SUCCESS(rc)) + { + PSB16DRIVER pDrv = (PSB16DRIVER)RTMemAllocZ(sizeof(SB16DRIVER)); + if (pDrv) + { + pDrv->pDrvBase = pDrvBase; + pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR); + AssertMsg(pDrv->pConnector != NULL, ("Configuration error: LUN #%u has no host audio interface, rc=%Rrc\n", uLUN, rc)); + pDrv->pSB16State = pThis; + pDrv->uLUN = uLUN; + + /* + * For now we always set the driver at LUN 0 as our primary + * host backend. This might change in the future. + */ + if (pDrv->uLUN == 0) + pDrv->fFlags |= PDMAUDIODRVFLAGS_PRIMARY; + + LogFunc(("LUN#%RU8: pCon=%p, drvFlags=0x%x\n", uLUN, pDrv->pConnector, pDrv->fFlags)); + + /* Attach to driver list if not attached yet. */ + if (!pDrv->fAttached) + { + RTListAppend(&pThis->lstDrv, &pDrv->Node); + pDrv->fAttached = true; + } + + if (ppDrv) + *ppDrv = pDrv; + } + else + rc = VERR_NO_MEMORY; + } + else if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + LogFunc(("No attached driver for LUN #%u\n", uLUN)); + + if (RT_FAILURE(rc)) + { + /* Only free this string on failure; + * must remain valid for the live of the driver instance. */ + RTStrFree(pszDesc); + } + + LogFunc(("iLUN=%u, fFlags=0x%x, rc=%Rrc\n", uLUN, fFlags, rc)); + return rc; +} + +/** + * Detach command, internal version. + * + * This is called to let the device detach from a driver for a specified LUN + * during runtime. + * + * @returns VBox status code. + * @param pThis SB16 state. + * @param pDrv Driver to detach device from. + * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines. + */ +static int sb16DetachInternal(PSB16STATE pThis, PSB16DRIVER pDrv, uint32_t fFlags) +{ + RT_NOREF(fFlags); + + sb16DestroyDrvStream(pThis, pDrv); + + RTListNodeRemove(&pDrv->Node); + + LogFunc(("uLUN=%u, fFlags=0x%x\n", pDrv->uLUN, fFlags)); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnAttach} + */ +static DECLCALLBACK(int) sb16Attach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags) +{ + PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE); + + LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags)); + + PSB16DRIVER pDrv; + int rc2 = sb16AttachInternal(pThis, uLUN, fFlags, &pDrv); + if (RT_SUCCESS(rc2)) + rc2 = sb16CreateDrvStream(pThis, &pThis->Out.Cfg, pDrv); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnDetach} + */ +static DECLCALLBACK(void) sb16Detach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags) +{ + PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE); + + LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags)); + + PSB16DRIVER pDrv, pDrvNext; + RTListForEachSafe(&pThis->lstDrv, pDrv, pDrvNext, SB16DRIVER, Node) + { + if (pDrv->uLUN == uLUN) + { + int rc2 = sb16DetachInternal(pThis, pDrv, fFlags); + if (RT_SUCCESS(rc2)) + { + RTMemFree(pDrv); + pDrv = NULL; + } + + break; + } + } +} + +/** + * Re-attaches (replaces) a driver with a new driver. + * + * @returns VBox status code. + * @param pThis Device instance. + * @param pDrv Driver instance used for attaching to. + * If NULL is specified, a new driver will be created and appended + * to the driver list. + * @param uLUN The logical unit which is being re-detached. + * @param pszDriver New driver name to attach. + */ +static int sb16Reattach(PSB16STATE pThis, PSB16DRIVER pDrv, uint8_t uLUN, const char *pszDriver) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pszDriver, VERR_INVALID_POINTER); + + int rc; + + if (pDrv) + { + rc = sb16DetachInternal(pThis, pDrv, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + rc = PDMDevHlpDriverDetach(pThis->pDevInsR3, PDMIBASE_2_PDMDRV(pDrv->pDrvBase), 0 /* fFlags */); + + if (RT_FAILURE(rc)) + return rc; + + pDrv = NULL; + } + + PVM pVM = PDMDevHlpGetVM(pThis->pDevInsR3); + PCFGMNODE pRoot = CFGMR3GetRoot(pVM); + PCFGMNODE pDev0 = CFGMR3GetChild(pRoot, "Devices/sb16/0/"); + + /* Remove LUN branch. */ + CFGMR3RemoveNode(CFGMR3GetChildF(pDev0, "LUN#%u/", uLUN)); + + if (pDrv) + { + /* Re-use the driver instance so detach it before. */ + rc = PDMDevHlpDriverDetach(pThis->pDevInsR3, PDMIBASE_2_PDMDRV(pDrv->pDrvBase), 0 /* fFlags */); + if (RT_FAILURE(rc)) + return rc; + } + +#define RC_CHECK() if (RT_FAILURE(rc)) { AssertReleaseRC(rc); break; } + + do + { + PCFGMNODE pLunL0; + rc = CFGMR3InsertNodeF(pDev0, &pLunL0, "LUN#%u/", uLUN); RC_CHECK(); + rc = CFGMR3InsertString(pLunL0, "Driver", "AUDIO"); RC_CHECK(); + rc = CFGMR3InsertNode(pLunL0, "Config/", NULL); RC_CHECK(); + + PCFGMNODE pLunL1, pLunL2; + rc = CFGMR3InsertNode (pLunL0, "AttachedDriver/", &pLunL1); RC_CHECK(); + rc = CFGMR3InsertNode (pLunL1, "Config/", &pLunL2); RC_CHECK(); + rc = CFGMR3InsertString(pLunL1, "Driver", pszDriver); RC_CHECK(); + + rc = CFGMR3InsertString(pLunL2, "AudioDriver", pszDriver); RC_CHECK(); + + } while (0); + + if (RT_SUCCESS(rc)) + rc = sb16AttachInternal(pThis, uLUN, 0 /* fFlags */, NULL /* ppDrv */); + + LogFunc(("pThis=%p, uLUN=%u, pszDriver=%s, rc=%Rrc\n", pThis, uLUN, pszDriver, rc)); + +#undef RC_CHECK + + return rc; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + */ +static DECLCALLBACK(void) sb16DevReset(PPDMDEVINS pDevIns) +{ + PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE); + + /* Bring back the device to initial state, and especially make + * sure there's no interrupt or DMA activity. + */ + PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0); + + pThis->mixer_regs[0x82] = 0; + pThis->csp_regs[5] = 1; + pThis->csp_regs[9] = 0xf8; + + pThis->dma_auto = 0; + pThis->in_index = 0; + pThis->out_data_len = 0; + pThis->left_till_irq = 0; + pThis->needed_bytes = 0; + pThis->block_size = -1; + pThis->nzero = 0; + pThis->highspeed = 0; + pThis->v2x6 = 0; + pThis->cmd = -1; + + sb16MixerReset(pThis); + sb16SpeakerControl(pThis, 0); + sb16Control(pThis, 0); + sb16CmdResetLegacy(pThis); +} + +/** + * Powers off the device. + * + * @param pDevIns Device instance to power off. + */ +static DECLCALLBACK(void) sb16PowerOff(PPDMDEVINS pDevIns) +{ + PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE); + + LogRel2(("SB16: Powering off ...\n")); + + PSB16DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) + sb16DestroyDrvStream(pThis, pDrv); +} + +/** + * @interface_method_impl{PDMDEVREG,pfnDestruct} + */ +static DECLCALLBACK(int) sb16Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */ + PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE); + + LogFlowFuncEnter(); + + PSB16DRIVER pDrv; + while (!RTListIsEmpty(&pThis->lstDrv)) + { + pDrv = RTListGetFirst(&pThis->lstDrv, SB16DRIVER, Node); + + RTListNodeRemove(&pDrv->Node); + RTMemFree(pDrv); + } + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) sb16Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + RT_NOREF(iInstance); + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */ + PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE); + + /* + * Initialize the data so sb16Destruct runs without a hitch if we return early. + */ + pThis->pDevInsR3 = pDevIns; + pThis->IBase.pfnQueryInterface = sb16QueryInterface; + pThis->cmd = -1; + + pThis->csp_regs[5] = 1; + pThis->csp_regs[9] = 0xf8; + + RTListInit(&pThis->lstDrv); + + /* + * Validations. + */ + Assert(iInstance == 0); + if (!CFGMR3AreValuesValid(pCfg, + "IRQ\0" + "DMA\0" + "DMA16\0" + "Port\0" + "Version\0" + "TimerHz\0")) + return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, + N_("Invalid configuration for SB16 device")); + + /* + * Read config data. + */ + int rc = CFGMR3QuerySIntDef(pCfg, "IRQ", &pThis->irq, 5); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("SB16 configuration error: Failed to get the \"IRQ\" value")); + pThis->irqCfg = pThis->irq; + + rc = CFGMR3QuerySIntDef(pCfg, "DMA", &pThis->dma, 1); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("SB16 configuration error: Failed to get the \"DMA\" value")); + pThis->dmaCfg = pThis->dma; + + rc = CFGMR3QuerySIntDef(pCfg, "DMA16", &pThis->hdma, 5); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("SB16 configuration error: Failed to get the \"DMA16\" value")); + pThis->hdmaCfg = pThis->hdma; + + RTIOPORT Port; + rc = CFGMR3QueryPortDef(pCfg, "Port", &Port, 0x220); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("SB16 configuration error: Failed to get the \"Port\" value")); + pThis->port = Port; + pThis->portCfg = Port; + + uint16_t u16Version; + rc = CFGMR3QueryU16Def(pCfg, "Version", &u16Version, 0x0405); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("SB16 configuration error: Failed to get the \"Version\" value")); + pThis->ver = u16Version; + pThis->verCfg = u16Version; + + uint16_t uTimerHz; + rc = CFGMR3QueryU16Def(pCfg, "TimerHz", &uTimerHz, 100 /* Hz */); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("SB16 configuration error: failed to read Hertz (Hz) rate as unsigned integer")); + /* + * Setup the mixer now that we've got the irq and dma channel numbers. + */ + pThis->mixer_regs[0x80] = magic_of_irq(pThis->irq); + pThis->mixer_regs[0x81] = (1 << pThis->dma) | (1 << pThis->hdma); + pThis->mixer_regs[0x82] = 2 << 5; + + sb16MixerReset(pThis); + + /* + * Create timer(s), register & attach stuff. + */ + rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIRQ, pThis, + TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "SB16 IRQ timer", &pThis->pTimerIRQ); + if (RT_FAILURE(rc)) + AssertMsgFailedReturn(("Error creating IRQ timer, rc=%Rrc\n", rc), rc); + + rc = PDMDevHlpIOPortRegister(pDevIns, pThis->port + 0x04, 2, pThis, mixer_write, mixer_read, NULL, NULL, "SB16"); + if (RT_FAILURE(rc)) + return rc; + rc = PDMDevHlpIOPortRegister(pDevIns, pThis->port + 0x06, 10, pThis, dsp_write, dsp_read, NULL, NULL, "SB16"); + if (RT_FAILURE(rc)) + return rc; + + rc = PDMDevHlpDMARegister(pDevIns, pThis->hdma, sb16DMARead, pThis); + if (RT_FAILURE(rc)) + return rc; + rc = PDMDevHlpDMARegister(pDevIns, pThis->dma, sb16DMARead, pThis); + if (RT_FAILURE(rc)) + return rc; + + pThis->can_write = 1; + + rc = PDMDevHlpSSMRegister3(pDevIns, SB16_SAVE_STATE_VERSION, sizeof(SB16STATE), sb16LiveExec, sb16SaveExec, sb16LoadExec); + if (RT_FAILURE(rc)) + return rc; + + /* + * Attach driver. + */ + uint8_t uLUN; + for (uLUN = 0; uLUN < UINT8_MAX; ++uLUN) + { + LogFunc(("Trying to attach driver for LUN #%RU8 ...\n", uLUN)); + rc = sb16AttachInternal(pThis, uLUN, 0 /* fFlags */, NULL /* ppDrv */); + if (RT_FAILURE(rc)) + { + if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + rc = VINF_SUCCESS; + else if (rc == VERR_AUDIO_BACKEND_INIT_FAILED) + { + sb16Reattach(pThis, NULL /* pDrv */, uLUN, "NullAudio"); + PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", + N_("Host audio backend initialization has failed. Selecting the NULL audio backend " + "with the consequence that no sound is audible")); + /* Attaching to the NULL audio backend will never fail. */ + rc = VINF_SUCCESS; + } + break; + } + } + + LogFunc(("cLUNs=%RU8, rc=%Rrc\n", uLUN, rc)); + + sb16CmdResetLegacy(pThis); + +#ifdef VBOX_WITH_AUDIO_SB16_ONETIME_INIT + PSB16DRIVER pDrv; + RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node) + { + /* + * Only primary drivers are critical for the VM to run. Everything else + * might not worth showing an own error message box in the GUI. + */ + if (!(pDrv->fFlags & PDMAUDIODRVFLAGS_PRIMARY)) + continue; + + PPDMIAUDIOCONNECTOR pCon = pDrv->pConnector; + AssertPtr(pCon); + + /** @todo No input streams available for SB16 yet. */ + + if (!pDrv->Out.pStream) + continue; + + bool fValidOut = pCon->pfnStreamGetStatus(pCon, pDrv->Out.pStream) & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED; + if (!fValidOut) + { + LogRel(("SB16: Falling back to NULL backend (no sound audible)\n")); + + sb16CmdResetLegacy(pThis); + sb16Reattach(pThis, pDrv, pDrv->uLUN, "NullAudio"); + + PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding", + N_("No audio devices could be opened. Selecting the NULL audio backend " + "with the consequence that no sound is audible")); + } + } +#endif + + if (RT_SUCCESS(rc)) + { + rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIO, pThis, + TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "SB16 IO timer", &pThis->pTimerIO); + if (RT_SUCCESS(rc)) + { + pThis->cTimerTicksIO = TMTimerGetFreq(pThis->pTimerIO) / uTimerHz; + pThis->uTimerTSIO = TMTimerGet(pThis->pTimerIO); + LogFunc(("Timer ticks=%RU64 (%RU16 Hz)\n", pThis->cTimerTicksIO, uTimerHz)); + } + else + AssertMsgFailedReturn(("Error creating I/O timer, rc=%Rrc\n", rc), rc); + } + +#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA + RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "sb16WriteAudio.pcm"); +#endif + + return VINF_SUCCESS; +} + +const PDMDEVREG g_DeviceSB16 = +{ + /* u32Version */ + PDM_DEVREG_VERSION, + /* szName */ + "sb16", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Sound Blaster 16 Controller", + /* fFlags */ + PDM_DEVREG_FLAGS_DEFAULT_BITS, + /* fClass */ + PDM_DEVREG_CLASS_AUDIO, + /* cMaxInstances */ + 1, + /* cbInstance */ + sizeof(SB16STATE), + /* pfnConstruct */ + sb16Construct, + /* pfnDestruct */ + sb16Destruct, + /* pfnRelocate */ + NULL, + /* pfnMemSetup */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + sb16DevReset, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + sb16Attach, + /* pfnDetach */ + sb16Detach, + /* pfnQueryInterface */ + NULL, + /* pfnInitComplete */ + NULL, + /* pfnPowerOff */ + sb16PowerOff, + /* pfnSoftReset */ + NULL, + /* u32VersionEnd */ + PDM_DEVREG_VERSION +}; + diff --git a/src/VBox/Devices/Audio/DrvAudio.cpp b/src/VBox/Devices/Audio/DrvAudio.cpp new file mode 100644 index 00000000..c11fe1e6 --- /dev/null +++ b/src/VBox/Devices/Audio/DrvAudio.cpp @@ -0,0 +1,3676 @@ +/* $Id: DrvAudio.cpp $ */ +/** @file + * Intermediate audio driver header. + * + * @remarks Intermediate audio driver for connecting the audio device emulation + * with the host backend. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define LOG_GROUP LOG_GROUP_DRV_AUDIO +#include <VBox/log.h> +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <VBox/vmm/mm.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include <iprt/alloc.h> +#include <iprt/asm-math.h> +#include <iprt/assert.h> +#include <iprt/circbuf.h> +#include <iprt/string.h> +#include <iprt/uuid.h> + +#include "VBoxDD.h" + +#include <ctype.h> +#include <stdlib.h> + +#include "DrvAudio.h" +#include "AudioMixBuffer.h" + +#ifdef VBOX_WITH_AUDIO_ENUM +static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIODEVICEENUM pDevEnum); +#endif + +static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream); +static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd); +static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd); +static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq); +static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream); +static void drvAudioStreamFree(PPDMAUDIOSTREAM pStream); +static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream); +static int drvAudioStreamInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest); +static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream); +static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream); +static void drvAudioStreamDropInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream); +static void drvAudioStreamResetInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream); + +#ifndef VBOX_AUDIO_TESTCASE + +# if 0 /* unused */ + +static PDMAUDIOFMT drvAudioGetConfFormat(PCFGMNODE pCfgHandle, const char *pszKey, + PDMAUDIOFMT enmDefault, bool *pfDefault) +{ + if ( pCfgHandle == NULL + || pszKey == NULL) + { + *pfDefault = true; + return enmDefault; + } + + char *pszValue = NULL; + int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue); + if (RT_FAILURE(rc)) + { + *pfDefault = true; + return enmDefault; + } + + PDMAUDIOFMT fmt = DrvAudioHlpStrToAudFmt(pszValue); + if (fmt == PDMAUDIOFMT_INVALID) + { + *pfDefault = true; + return enmDefault; + } + + *pfDefault = false; + return fmt; +} + +static int drvAudioGetConfInt(PCFGMNODE pCfgHandle, const char *pszKey, + int iDefault, bool *pfDefault) +{ + + if ( pCfgHandle == NULL + || pszKey == NULL) + { + *pfDefault = true; + return iDefault; + } + + uint64_t u64Data = 0; + int rc = CFGMR3QueryInteger(pCfgHandle, pszKey, &u64Data); + if (RT_FAILURE(rc)) + { + *pfDefault = true; + return iDefault; + + } + + *pfDefault = false; + return u64Data; +} + +static const char *drvAudioGetConfStr(PCFGMNODE pCfgHandle, const char *pszKey, + const char *pszDefault, bool *pfDefault) +{ + if ( pCfgHandle == NULL + || pszKey == NULL) + { + *pfDefault = true; + return pszDefault; + } + + char *pszValue = NULL; + int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue); + if (RT_FAILURE(rc)) + { + *pfDefault = true; + return pszDefault; + } + + *pfDefault = false; + return pszValue; +} + +# endif /* unused */ + +#ifdef LOG_ENABLED +/** + * Converts an audio stream status to a string. + * + * @returns Stringified stream status flags. Must be free'd with RTStrFree(). + * "NONE" if no flags set. + * @param fStatus Stream status flags to convert. + */ +static char *dbgAudioStreamStatusToStr(PDMAUDIOSTREAMSTS fStatus) +{ +#define APPEND_FLAG_TO_STR(_aFlag) \ + if (fStatus & PDMAUDIOSTREAMSTS_FLAG_##_aFlag) \ + { \ + if (pszFlags) \ + { \ + rc2 = RTStrAAppend(&pszFlags, " "); \ + if (RT_FAILURE(rc2)) \ + break; \ + } \ + \ + rc2 = RTStrAAppend(&pszFlags, #_aFlag); \ + if (RT_FAILURE(rc2)) \ + break; \ + } \ + + char *pszFlags = NULL; + int rc2 = VINF_SUCCESS; + + do + { + APPEND_FLAG_TO_STR(INITIALIZED ); + APPEND_FLAG_TO_STR(ENABLED ); + APPEND_FLAG_TO_STR(PAUSED ); + APPEND_FLAG_TO_STR(PENDING_DISABLE); + APPEND_FLAG_TO_STR(PENDING_REINIT ); + } while (0); + + if (!pszFlags) + rc2 = RTStrAAppend(&pszFlags, "NONE"); + + if ( RT_FAILURE(rc2) + && pszFlags) + { + RTStrFree(pszFlags); + pszFlags = NULL; + } + +#undef APPEND_FLAG_TO_STR + + return pszFlags; +} +#endif /* defined(VBOX_STRICT) || defined(LOG_ENABLED) */ + +# if 0 /* unused */ +static int drvAudioProcessOptions(PCFGMNODE pCfgHandle, const char *pszPrefix, audio_option *paOpts, size_t cOpts) +{ + AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER); + AssertPtrReturn(pszPrefix, VERR_INVALID_POINTER); + /* oaOpts and cOpts are optional. */ + + PCFGMNODE pCfgChildHandle = NULL; + PCFGMNODE pCfgChildChildHandle = NULL; + + /* If pCfgHandle is NULL, let NULL be passed to get int and get string functions.. + * The getter function will return default values. + */ + if (pCfgHandle != NULL) + { + /* If its audio general setting, need to traverse to one child node. + * /Devices/ichac97/0/LUN#0/Config/Audio + */ + if(!strncmp(pszPrefix, "AUDIO", 5)) /** @todo Use a \#define */ + { + pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle); + if(pCfgChildHandle) + pCfgHandle = pCfgChildHandle; + } + else + { + /* If its driver specific configuration , then need to traverse two level deep child + * child nodes. for eg. in case of DirectSoundConfiguration item + * /Devices/ichac97/0/LUN#0/Config/Audio/DirectSoundConfig + */ + pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle); + if (pCfgChildHandle) + { + pCfgChildChildHandle = CFGMR3GetFirstChild(pCfgChildHandle); + if (pCfgChildChildHandle) + pCfgHandle = pCfgChildChildHandle; + } + } + } + + for (size_t i = 0; i < cOpts; i++) + { + audio_option *pOpt = &paOpts[i]; + if (!pOpt->valp) + { + LogFlowFunc(("Option value pointer for `%s' is not set\n", pOpt->name)); + continue; + } + + bool fUseDefault; + + switch (pOpt->tag) + { + case AUD_OPT_BOOL: + case AUD_OPT_INT: + { + int *intp = (int *)pOpt->valp; + *intp = drvAudioGetConfInt(pCfgHandle, pOpt->name, *intp, &fUseDefault); + + break; + } + + case AUD_OPT_FMT: + { + PDMAUDIOFMT *fmtp = (PDMAUDIOFMT *)pOpt->valp; + *fmtp = drvAudioGetConfFormat(pCfgHandle, pOpt->name, *fmtp, &fUseDefault); + + break; + } + + case AUD_OPT_STR: + { + const char **strp = (const char **)pOpt->valp; + *strp = drvAudioGetConfStr(pCfgHandle, pOpt->name, *strp, &fUseDefault); + + break; + } + + default: + LogFlowFunc(("Bad value tag for option `%s' - %d\n", pOpt->name, pOpt->tag)); + fUseDefault = false; + break; + } + + if (!pOpt->overridenp) + pOpt->overridenp = &pOpt->overriden; + + *pOpt->overridenp = !fUseDefault; + } + + return VINF_SUCCESS; +} +# endif /* unused */ +#endif /* !VBOX_AUDIO_TESTCASE */ + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl} + */ +static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface, + PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + + if (!pStream) + return VINF_SUCCESS; + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd))); + + rc = drvAudioStreamControlInternal(pThis, pStream, enmStreamCmd); + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + return rc; +} + +/** + * Controls an audio stream. + * + * @returns IPRT status code. + * @param pThis Pointer to driver instance. + * @param pStream Stream to control. + * @param enmStreamCmd Control command. + */ +static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + LogFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd))); + +#ifdef LOG_ENABLED + char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); + LogFlowFunc(("fStatus=%s\n", pszStreamSts)); + RTStrFree(pszStreamSts); +#endif /* LOG_ENABLED */ + + int rc = VINF_SUCCESS; + + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + { + if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)) + { + /* Is a pending disable outstanding? Then disable first. */ + if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE) + rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); + + if (RT_SUCCESS(rc)) + rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_ENABLE); + + if (RT_SUCCESS(rc)) + pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_ENABLED; + } + break; + } + + case PDMAUDIOSTREAMCMD_DISABLE: + { + if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED) + { + /* + * For playback (output) streams first mark the host stream as pending disable, + * so that the rest of the remaining audio data will be played first before + * closing the stream. + */ + if (pStream->enmDir == PDMAUDIODIR_OUT) + { + LogFunc(("[%s] Pending disable/pause\n", pStream->szName)); + pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE; + } + + /* Can we close the host stream as well (not in pending disable mode)? */ + if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE)) + { + rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); + if (RT_SUCCESS(rc)) + drvAudioStreamResetInternal(pThis, pStream); + } + } + break; + } + + case PDMAUDIOSTREAMCMD_PAUSE: + { + if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)) + { + rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_PAUSE); + if (RT_SUCCESS(rc)) + pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PAUSED; + } + break; + } + + case PDMAUDIOSTREAMCMD_RESUME: + { + if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED) + { + rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_RESUME); + if (RT_SUCCESS(rc)) + pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_PAUSED; + } + break; + } + + case PDMAUDIOSTREAMCMD_DROP: + { + rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DROP); + if (RT_SUCCESS(rc)) + { + drvAudioStreamDropInternal(pThis, pStream); + } + break; + } + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + + if (RT_FAILURE(rc)) + LogFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc)); + + return rc; +} + +/** + * Controls a stream's backend. + * If the stream has no backend available, VERR_NOT_FOUND is returned. + * + * @returns IPRT status code. + * @param pThis Pointer to driver instance. + * @param pStream Stream to control. + * @param enmStreamCmd Control command. + */ +static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + +#ifdef LOG_ENABLED + char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); + LogFlowFunc(("[%s] enmStreamCmd=%s, fStatus=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd), pszStreamSts)); + RTStrFree(pszStreamSts); +#endif /* LOG_ENABLED */ + + if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */ + return VINF_SUCCESS; + + LogRel2(("Audio: %s stream '%s'\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), pStream->szName)); + + int rc = VINF_SUCCESS; + + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + { + if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)) + rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_ENABLE); + break; + } + + case PDMAUDIOSTREAMCMD_DISABLE: + { + if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED) + rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DISABLE); + break; + } + + case PDMAUDIOSTREAMCMD_PAUSE: + { + /* Only pause if the stream is enabled. */ + if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)) + break; + + if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)) + rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_PAUSE); + break; + } + + case PDMAUDIOSTREAMCMD_RESUME: + { + /* Only need to resume if the stream is enabled. */ + if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)) + break; + + if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED) + rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_RESUME); + break; + } + + case PDMAUDIOSTREAMCMD_DRAIN: + { + rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DRAIN); + break; + } + + case PDMAUDIOSTREAMCMD_DROP: + { + rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DROP); + break; + } + + default: + { + AssertMsgFailed(("Command %RU32 not implemented\n", enmStreamCmd)); + rc = VERR_NOT_IMPLEMENTED; + break; + } + } + + if (RT_FAILURE(rc)) + { + if ( rc != VERR_NOT_IMPLEMENTED + && rc != VERR_NOT_SUPPORTED) + LogRel(("Audio: %s stream '%s' failed with %Rrc\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), pStream->szName, rc)); + + LogFunc(("[%s] %s failed with %Rrc\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd), rc)); + } + + return rc; +} + +/** + * Initializes an audio stream with a given host and guest stream configuration. + * + * @returns IPRT status code. + * @param pThis Pointer to driver instance. + * @param pStream Stream to initialize. + * @param pCfgHost Stream configuration to use for the host side (backend). + * @param pCfgGuest Stream configuration to use for the guest side. + */ +static int drvAudioStreamInitInternal(PDRVAUDIO pThis, + PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER); + + /* + * Init host stream. + */ + + /* Set the host's default audio data layout. */ + pCfgHost->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + +#ifdef DEBUG + LogFunc(("[%s] Requested host format:\n", pStream->szName)); + DrvAudioHlpStreamCfgPrint(pCfgHost); +#endif + + LogRel2(("Audio: Creating stream '%s'\n", pStream->szName)); + LogRel2(("Audio: Guest %s format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n", + pCfgGuest->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName, + pCfgGuest->Props.uHz, pCfgGuest->Props.cBytes * 8, pCfgGuest->Props.fSigned ? "S" : "U", + pCfgGuest->Props.cChannels, pCfgGuest->Props.cChannels == 1 ? "Channel" : "Channels")); + LogRel2(("Audio: Requested host %s format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n", + pCfgHost->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName, + pCfgHost->Props.uHz, pCfgHost->Props.cBytes * 8, pCfgHost->Props.fSigned ? "S" : "U", + pCfgHost->Props.cChannels, pCfgHost->Props.cChannels == 1 ? "Channel" : "Channels")); + + PDMAUDIOSTREAMCFG CfgHostAcq; + int rc = drvAudioStreamCreateInternalBackend(pThis, pStream, pCfgHost, &CfgHostAcq); + if (RT_FAILURE(rc)) + return rc; + +#ifdef DEBUG + LogFunc(("[%s] Acquired host format:\n", pStream->szName)); + DrvAudioHlpStreamCfgPrint(&CfgHostAcq); +#endif + + LogRel2(("Audio: Acquired host %s format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n", + CfgHostAcq.enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName, + CfgHostAcq.Props.uHz, CfgHostAcq.Props.cBytes * 8, CfgHostAcq.Props.fSigned ? "S" : "U", + CfgHostAcq.Props.cChannels, CfgHostAcq.Props.cChannels == 1 ? "Channel" : "Channels")); + + /* Let the user know if the backend changed some of the tweakable values. */ + if (CfgHostAcq.Backend.cfBufferSize != pCfgHost->Backend.cfBufferSize) + LogRel2(("Audio: Backend changed buffer size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n", + DrvAudioHlpFramesToMilli(pCfgHost->Backend.cfBufferSize, &pCfgHost->Props), pCfgHost->Backend.cfBufferSize, + DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfBufferSize, &CfgHostAcq.Props), CfgHostAcq.Backend.cfBufferSize)); + + if (CfgHostAcq.Backend.cfPeriod != pCfgHost->Backend.cfPeriod) + LogRel2(("Audio: Backend changed period size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n", + DrvAudioHlpFramesToMilli(pCfgHost->Backend.cfPeriod, &pCfgHost->Props), pCfgHost->Backend.cfPeriod, + DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPeriod, &CfgHostAcq.Props), CfgHostAcq.Backend.cfPeriod)); + + if (CfgHostAcq.Backend.cfPreBuf != pCfgHost->Backend.cfPreBuf) + LogRel2(("Audio: Backend changed pre-buffering size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n", + DrvAudioHlpFramesToMilli(pCfgHost->Backend.cfPreBuf, &pCfgHost->Props), pCfgHost->Backend.cfPreBuf, + DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPreBuf, &CfgHostAcq.Props), CfgHostAcq.Backend.cfPreBuf)); + /* + * Configure host buffers. + */ + + /* Check if the backend did return sane values and correct if necessary. + * Should never happen with our own backends, but you never know ... */ + if (CfgHostAcq.Backend.cfBufferSize < CfgHostAcq.Backend.cfPreBuf) + { + LogRel2(("Audio: Warning: Pre-buffering size (%RU32 frames) of stream '%s' does not match buffer size (%RU32 frames), " + "setting pre-buffering size to %RU32 frames\n", + CfgHostAcq.Backend.cfPreBuf, pStream->szName, CfgHostAcq.Backend.cfBufferSize, CfgHostAcq.Backend.cfBufferSize)); + CfgHostAcq.Backend.cfPreBuf = CfgHostAcq.Backend.cfBufferSize; + } + + if (CfgHostAcq.Backend.cfPeriod > CfgHostAcq.Backend.cfBufferSize) + { + LogRel2(("Audio: Warning: Period size (%RU32 frames) of stream '%s' does not match buffer size (%RU32 frames), setting to %RU32 frames\n", + CfgHostAcq.Backend.cfPeriod, pStream->szName, CfgHostAcq.Backend.cfBufferSize, CfgHostAcq.Backend.cfBufferSize)); + CfgHostAcq.Backend.cfPeriod = CfgHostAcq.Backend.cfBufferSize; + } + + uint64_t msBufferSize = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfBufferSize, &CfgHostAcq.Props); + + LogRel2(("Audio: Buffer size of stream '%s' is %RU64ms (%RU32 frames)\n", + pStream->szName, msBufferSize, CfgHostAcq.Backend.cfBufferSize)); + + /* If no own pre-buffer is set, let the backend choose. */ + uint64_t msPreBuf = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPreBuf, &CfgHostAcq.Props); + LogRel2(("Audio: Pre-buffering size of stream '%s' is %RU64ms (%RU32 frames)\n", + pStream->szName, msPreBuf, CfgHostAcq.Backend.cfPreBuf)); + + /* Make sure the configured buffer size by the backend at least can hold the configured latency. */ + const uint32_t msPeriod = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPeriod, &CfgHostAcq.Props); + + LogRel2(("Audio: Period size of stream '%s' is %RU64ms (%RU32 frames)\n", + pStream->szName, msPeriod, CfgHostAcq.Backend.cfPeriod)); + + if ( pCfgGuest->Device.uSchedulingHintMs /* Any scheduling hint set? */ + && pCfgGuest->Device.uSchedulingHintMs > msPeriod) /* This might lead to buffer underflows. */ + { + LogRel(("Audio: Warning: Scheduling hint of stream '%s' is bigger (%RU64ms) than used period size (%RU64ms)\n", + pStream->szName, pCfgGuest->Device.uSchedulingHintMs, msPeriod)); + } + + /* Destroy any former mixing buffer. */ + AudioMixBufDestroy(&pStream->Host.MixBuf); + + /* Make sure to (re-)set the host buffer's shift size. */ + CfgHostAcq.Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(CfgHostAcq.Props.cBytes, CfgHostAcq.Props.cChannels); + + rc = AudioMixBufInit(&pStream->Host.MixBuf, pStream->szName, &CfgHostAcq.Props, CfgHostAcq.Backend.cfBufferSize); + AssertRC(rc); + + /* Make a copy of the acquired host stream configuration. */ + rc = DrvAudioHlpStreamCfgCopy(&pStream->Host.Cfg, &CfgHostAcq); + AssertRC(rc); + + /* + * Init guest stream. + */ + + if (pCfgGuest->Device.uSchedulingHintMs) + LogRel2(("Audio: Stream '%s' got a scheduling hint of %RU32ms (%RU32 bytes)\n", + pStream->szName, pCfgGuest->Device.uSchedulingHintMs, + DrvAudioHlpMilliToBytes(pCfgGuest->Device.uSchedulingHintMs, &pCfgGuest->Props))); + + /* Destroy any former mixing buffer. */ + AudioMixBufDestroy(&pStream->Guest.MixBuf); + + /* Set the guests's default audio data layout. */ + pCfgGuest->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + + /* Make sure to (re-)set the guest buffer's shift size. */ + pCfgGuest->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgGuest->Props.cBytes, pCfgGuest->Props.cChannels); + + rc = AudioMixBufInit(&pStream->Guest.MixBuf, pStream->szName, &pCfgGuest->Props, CfgHostAcq.Backend.cfBufferSize); + AssertRC(rc); + + /* Make a copy of the guest stream configuration. */ + rc = DrvAudioHlpStreamCfgCopy(&pStream->Guest.Cfg, pCfgGuest); + AssertRC(rc); + + if (RT_FAILURE(rc)) + LogRel(("Audio: Creating stream '%s' failed with %Rrc\n", pStream->szName, rc)); + + if (pCfgGuest->enmDir == PDMAUDIODIR_IN) + { + /* Host (Parent) -> Guest (Child). */ + rc = AudioMixBufLinkTo(&pStream->Host.MixBuf, &pStream->Guest.MixBuf); + AssertRC(rc); + } + else + { + /* Guest (Parent) -> Host (Child). */ + rc = AudioMixBufLinkTo(&pStream->Guest.MixBuf, &pStream->Host.MixBuf); + AssertRC(rc); + } + +#ifdef VBOX_WITH_STATISTICS + char szStatName[255]; + + if (pCfgGuest->enmDir == PDMAUDIODIR_IN) + { + RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesCaptured", pStream->szName); + PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalFramesCaptured, + szStatName, STAMUNIT_COUNT, "Total frames played."); + RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesCaptured", pStream->szName); + PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalTimesCaptured, + szStatName, STAMUNIT_COUNT, "Total number of playbacks."); + RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesRead", pStream->szName); + PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalFramesRead, + szStatName, STAMUNIT_COUNT, "Total frames read."); + RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesRead", pStream->szName); + PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalTimesRead, + szStatName, STAMUNIT_COUNT, "Total number of reads."); + } + else if (pCfgGuest->enmDir == PDMAUDIODIR_OUT) + { + RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesPlayed", pStream->szName); + PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesPlayed, + szStatName, STAMUNIT_COUNT, "Total frames played."); + + RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesPlayed", pStream->szName); + PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesPlayed, + szStatName, STAMUNIT_COUNT, "Total number of playbacks."); + RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesWritten", pStream->szName); + PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesWritten, + szStatName, STAMUNIT_COUNT, "Total frames written."); + + RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesWritten", pStream->szName); + PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesWritten, + szStatName, STAMUNIT_COUNT, "Total number of writes."); + } + else + AssertFailed(); +#endif + + LogFlowFunc(("[%s] Returning %Rrc\n", pStream->szName, rc)); + return rc; +} + +/** + * Frees an audio stream and its allocated resources. + * + * @param pStream Audio stream to free. + * After this call the pointer will not be valid anymore. + */ +static void drvAudioStreamFree(PPDMAUDIOSTREAM pStream) +{ + if (!pStream) + return; + + LogFunc(("[%s]\n", pStream->szName)); + + if (pStream->pvBackend) + { + Assert(pStream->cbBackend); + RTMemFree(pStream->pvBackend); + pStream->pvBackend = NULL; + } + + RTMemFree(pStream); + pStream = NULL; +} + +#ifdef VBOX_WITH_AUDIO_CALLBACKS +/** + * Schedules a re-initialization of all current audio streams. + * The actual re-initialization will happen at some later point in time. + * + * @returns IPRT status code. + * @param pThis Pointer to driver instance. + */ +static int drvAudioScheduleReInitInternal(PDRVAUDIO pThis) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + LogFunc(("\n")); + + /* Mark all host streams to re-initialize. */ + PPDMAUDIOSTREAM pStream; + RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node) + pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT; + +# ifdef VBOX_WITH_AUDIO_ENUM + /* Re-enumerate all host devices as soon as possible. */ + pThis->fEnumerateDevices = true; +# endif + + return VINF_SUCCESS; +} +#endif /* VBOX_WITH_AUDIO_CALLBACKS */ + +/** + * Re-initializes an audio stream with its existing host and guest stream configuration. + * This might be the case if the backend told us we need to re-initialize because something + * on the host side has changed. + * + * @returns IPRT status code. + * @param pThis Pointer to driver instance. + * @param pStream Stream to re-initialize. + */ +static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + LogFlowFunc(("[%s]\n", pStream->szName)); + + /* + * Gather current stream status. + */ + bool fIsEnabled = RT_BOOL(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED); /* Stream is enabled? */ + + /* + * Destroy and re-create stream on backend side. + */ + int rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); + if (RT_SUCCESS(rc)) + { + rc = drvAudioStreamDestroyInternalBackend(pThis, pStream); + if (RT_SUCCESS(rc)) + { + rc = drvAudioStreamCreateInternalBackend(pThis, pStream, &pStream->Host.Cfg, NULL /* pCfgAcq */); + /** @todo Validate (re-)acquired configuration with pStream->Host.Cfg? */ + } + } + + /* Drop all old data. */ + drvAudioStreamDropInternal(pThis, pStream); + + /* + * Restore previous stream state. + */ + if (fIsEnabled) + rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_ENABLE); + + if (RT_FAILURE(rc)) + LogRel(("Audio: Re-initializing stream '%s' failed with %Rrc\n", pStream->szName, rc)); + + LogFunc(("[%s] Returning %Rrc\n", pStream->szName, rc)); + return rc; +} + +/** + * Drops all audio data (and associated state) of a stream. + * + * @param pThis Pointer to driver instance. + * @param pStream Stream to drop data for. + */ +static void drvAudioStreamDropInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +{ + RT_NOREF(pThis); + + LogFunc(("[%s]\n", pStream->szName)); + + AudioMixBufReset(&pStream->Guest.MixBuf); + AudioMixBufReset(&pStream->Host.MixBuf); + + pStream->tsLastIteratedNs = 0; + pStream->tsLastPlayedCapturedNs = 0; + pStream->tsLastReadWrittenNs = 0; + + pStream->fThresholdReached = false; +} + +/** + * Resets a given audio stream. + * + * @param pThis Pointer to driver instance. + * @param pStream Stream to reset. + */ +static void drvAudioStreamResetInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +{ + drvAudioStreamDropInternal(pThis, pStream); + + LogFunc(("[%s]\n", pStream->szName)); + + pStream->fStatus = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED; + +#ifdef VBOX_WITH_STATISTICS + /* + * Reset statistics. + */ + if (pStream->enmDir == PDMAUDIODIR_IN) + { + STAM_COUNTER_RESET(&pStream->In.Stats.TotalFramesCaptured); + STAM_COUNTER_RESET(&pStream->In.Stats.TotalFramesRead); + STAM_COUNTER_RESET(&pStream->In.Stats.TotalTimesCaptured); + STAM_COUNTER_RESET(&pStream->In.Stats.TotalTimesRead); + } + else if (pStream->enmDir == PDMAUDIODIR_OUT) + { + STAM_COUNTER_RESET(&pStream->Out.Stats.TotalFramesPlayed); + STAM_COUNTER_RESET(&pStream->Out.Stats.TotalFramesWritten); + STAM_COUNTER_RESET(&pStream->Out.Stats.TotalTimesPlayed); + STAM_COUNTER_RESET(&pStream->Out.Stats.TotalTimesWritten); + } + else + AssertFailed(); +#endif +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamWrite} + */ +static DECLCALLBACK(int) drvAudioStreamWrite(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + /* pcbWritten is optional. */ + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT, + ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n", + pStream->szName, DrvAudioHlpAudDirToStr(pStream->enmDir))); + + AssertMsg(DrvAudioHlpBytesIsAligned(cbBuf, &pStream->Guest.Cfg.Props), + ("Stream '%s' got a non-frame-aligned write (%RU32 bytes)\n", pStream->szName, cbBuf)); + + uint32_t cbWrittenTotal = 0; + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + +#ifdef VBOX_WITH_STATISTICS + STAM_PROFILE_ADV_START(&pThis->Stats.DelayOut, out); +#endif + +#ifdef LOG_ENABLED + char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); + AssertPtr(pszStreamSts); +#endif + + /* Whether to discard the incoming data or not. */ + bool fToBitBucket = false; + + do + { + if ( !pThis->Out.fEnabled + || !DrvAudioHlpStreamStatusIsReady(pStream->fStatus)) + { + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + + if (pThis->pHostDrvAudio) + { + /* If the backend's stream is not writable, all written data goes to /dev/null. */ + if (!DrvAudioHlpStreamStatusCanWrite( + pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStream->pvBackend))) + { + fToBitBucket = true; + break; + } + } + + const uint32_t cbFree = AudioMixBufFreeBytes(&pStream->Host.MixBuf); + if (cbFree < cbBuf) + LogRel2(("Audio: Lost audio output (%RU64ms, %RU32 free but needs %RU32) due to full host stream buffer '%s'\n", + DrvAudioHlpBytesToMilli(cbBuf - cbFree, &pStream->Host.Cfg.Props), cbFree, cbBuf, pStream->szName)); + + uint32_t cbToWrite = RT_MIN(cbBuf, cbFree); + if (!cbToWrite) + { + rc = VERR_BUFFER_OVERFLOW; + break; + } + + /* We use the guest side mixing buffer as an intermediate buffer to do some + * (first) processing (if needed), so always write the incoming data at offset 0. */ + uint32_t cfGstWritten = 0; + rc = AudioMixBufWriteAt(&pStream->Guest.MixBuf, 0 /* offFrames */, pvBuf, cbToWrite, &cfGstWritten); + if ( RT_FAILURE(rc) + || !cfGstWritten) + { + AssertMsgFailed(("[%s] Write failed: cbToWrite=%RU32, cfWritten=%RU32, rc=%Rrc\n", + pStream->szName, cbToWrite, cfGstWritten, rc)); + break; + } + + if (pThis->Out.Cfg.Dbg.fEnabled) + DrvAudioHlpFileWrite(pStream->Out.Dbg.pFileStreamWrite, pvBuf, cbToWrite, 0 /* fFlags */); + + uint32_t cfGstMixed = 0; + if (cfGstWritten) + { + int rc2 = AudioMixBufMixToParentEx(&pStream->Guest.MixBuf, 0 /* cSrcOffset */, cfGstWritten /* cSrcFrames */, + &cfGstMixed /* pcSrcMixed */); + if (RT_FAILURE(rc2)) + { + AssertMsgFailed(("[%s] Mixing failed: cbToWrite=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n", + pStream->szName, cbToWrite, cfGstWritten, cfGstMixed, rc2)); + } + else + { + const uint64_t tsNowNs = RTTimeNanoTS(); + + Log3Func(("[%s] Writing %RU32 frames (%RU64ms)\n", + pStream->szName, cfGstWritten, DrvAudioHlpFramesToMilli(cfGstWritten, &pStream->Guest.Cfg.Props))); + + Log3Func(("[%s] Last written %RU64ns (%RU64ms), now filled with %RU64ms -- %RU8%%\n", + pStream->szName, tsNowNs - pStream->tsLastReadWrittenNs, + (tsNowNs - pStream->tsLastReadWrittenNs) / RT_NS_1MS, + DrvAudioHlpFramesToMilli(AudioMixBufUsed(&pStream->Host.MixBuf), &pStream->Host.Cfg.Props), + AudioMixBufUsed(&pStream->Host.MixBuf) * 100 / AudioMixBufSize(&pStream->Host.MixBuf))); + + pStream->tsLastReadWrittenNs = tsNowNs; + /* Keep going. */ + } + + if (RT_SUCCESS(rc)) + rc = rc2; + + cbWrittenTotal = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfGstWritten); + +#ifdef VBOX_WITH_STATISTICS + STAM_COUNTER_ADD(&pThis->Stats.TotalFramesWritten, cfGstWritten); + STAM_COUNTER_ADD(&pThis->Stats.TotalFramesMixedOut, cfGstMixed); + Assert(cfGstWritten >= cfGstMixed); + STAM_COUNTER_ADD(&pThis->Stats.TotalFramesLostOut, cfGstWritten - cfGstMixed); + STAM_COUNTER_ADD(&pThis->Stats.TotalBytesWritten, cbWrittenTotal); + + STAM_COUNTER_ADD(&pStream->Out.Stats.TotalFramesWritten, cfGstWritten); + STAM_COUNTER_INC(&pStream->Out.Stats.TotalTimesWritten); +#endif + } + + Log3Func(("[%s] Dbg: cbBuf=%RU32, cbToWrite=%RU32, cfHstUsed=%RU32, cfHstfLive=%RU32, cfGstWritten=%RU32, " + "cfGstMixed=%RU32, cbWrittenTotal=%RU32, rc=%Rrc\n", + pStream->szName, cbBuf, cbToWrite, AudioMixBufUsed(&pStream->Host.MixBuf), + AudioMixBufLive(&pStream->Host.MixBuf), cfGstWritten, cfGstMixed, cbWrittenTotal, rc)); + + } while (0); + +#ifdef LOG_ENABLED + RTStrFree(pszStreamSts); +#endif + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + if (RT_SUCCESS(rc)) + { + if (fToBitBucket) + { + Log3Func(("[%s] Backend stream not ready (yet), discarding written data\n", pStream->szName)); + cbWrittenTotal = cbBuf; /* Report all data as being written to the caller. */ + } + + if (pcbWritten) + *pcbWritten = cbWrittenTotal; + } + + return rc; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain} + */ +static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +{ + AssertPtrReturn(pInterface, UINT32_MAX); + AssertPtrReturn(pStream, UINT32_MAX); + + NOREF(pInterface); + + return ++pStream->cRefs; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease} + */ +static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +{ + AssertPtrReturn(pInterface, UINT32_MAX); + AssertPtrReturn(pStream, UINT32_MAX); + + NOREF(pInterface); + + if (pStream->cRefs > 1) /* 1 reference always is kept by this audio driver. */ + pStream->cRefs--; + + return pStream->cRefs; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate} + */ +static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + rc = drvAudioStreamIterateInternal(pThis, pStream); + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + if (RT_FAILURE(rc)) + LogFlowFuncLeaveRC(rc); + + return rc; +} + +/** + * Does one iteration of an audio stream. + * This function gives the backend the chance of iterating / altering data and + * does the actual mixing between the guest <-> host mixing buffers. + * + * @returns IPRT status code. + * @param pThis Pointer to driver instance. + * @param pStream Stream to iterate. + */ +static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + if (!pThis->pHostDrvAudio) + return VINF_SUCCESS; + + if (!pStream) + return VINF_SUCCESS; + + int rc; + + /* Is the stream scheduled for re-initialization? Do so now. */ + if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT) + { +#ifdef VBOX_WITH_AUDIO_ENUM + if (pThis->fEnumerateDevices) + { + /* Re-enumerate all host devices. */ + drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */); + + pThis->fEnumerateDevices = false; + } +#endif /* VBOX_WITH_AUDIO_ENUM */ + + /* Remove the pending re-init flag in any case, regardless whether the actual re-initialization succeeded + * or not. If it failed, the backend needs to notify us again to try again at some later point in time. */ + pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT; + + rc = drvAudioStreamReInitInternal(pThis, pStream); + if (RT_FAILURE(rc)) + return rc; + } + +#ifdef LOG_ENABLED + char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); + Log3Func(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts)); + RTStrFree(pszStreamSts); +#endif /* LOG_ENABLED */ + + /* Not enabled or paused? Skip iteration. */ + if ( !(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED) + || (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)) + { + return VINF_SUCCESS; + } + + /* Whether to try closing a pending to close stream. */ + bool fTryClosePending = false; + + do + { + rc = pThis->pHostDrvAudio->pfnStreamIterate(pThis->pHostDrvAudio, pStream->pvBackend); + if (RT_FAILURE(rc)) + break; + + if (pStream->enmDir == PDMAUDIODIR_OUT) + { + /* No audio frames to transfer from guest to host (anymore)? + * Then try closing this stream if marked so in the next block. */ + const uint32_t cfLive = AudioMixBufLive(&pStream->Host.MixBuf); + fTryClosePending = cfLive == 0; + Log3Func(("[%s] fTryClosePending=%RTbool, cfLive=%RU32\n", pStream->szName, fTryClosePending, cfLive)); + } + + /* Has the host stream marked as pending to disable? + * Try disabling the stream then. */ + if ( pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE + && fTryClosePending) + { + /* Tell the backend to drain the stream, that is, play the remaining (buffered) data + * on the backend side. */ + rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DRAIN); + if (rc == VERR_NOT_SUPPORTED) /* Not all backends support draining. */ + rc = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + { + if (pThis->pHostDrvAudio->pfnStreamGetPending) /* Optional to implement. */ + { + const uint32_t cxPending = pThis->pHostDrvAudio->pfnStreamGetPending(pThis->pHostDrvAudio, pStream->pvBackend); + Log3Func(("[%s] cxPending=%RU32\n", pStream->szName, cxPending)); + + /* Only try close pending if no audio data is pending on the backend-side anymore. */ + fTryClosePending = cxPending == 0; + } + + if (fTryClosePending) + { + LogFunc(("[%s] Closing pending stream\n", pStream->szName)); + rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); + if (RT_SUCCESS(rc)) + { + pStream->fStatus &= ~(PDMAUDIOSTREAMSTS_FLAG_ENABLED | PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE); + drvAudioStreamDropInternal(pThis, pStream); + } + else + LogFunc(("[%s] Backend vetoed against closing pending input stream, rc=%Rrc\n", pStream->szName, rc)); + } + } + } + + } while (0); + + /* Update timestamps. */ + pStream->tsLastIteratedNs = RTTimeNanoTS(); + + if (RT_FAILURE(rc)) + LogFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc)); + + return rc; +} + +/** + * Plays an audio host output stream which has been configured for non-interleaved (layout) data. + * + * @return IPRT status code. + * @param pThis Pointer to driver instance. + * @param pStream Stream to play. + * @param cfToPlay Number of audio frames to play. + * @param pcfPlayed Returns number of audio frames played. Optional. + */ +static int drvAudioStreamPlayNonInterleaved(PDRVAUDIO pThis, + PPDMAUDIOSTREAM pStream, uint32_t cfToPlay, uint32_t *pcfPlayed) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + /* pcfPlayed is optional. */ + + if (!cfToPlay) + { + if (pcfPlayed) + *pcfPlayed = 0; + return VINF_SUCCESS; + } + + /* Sanity. */ + Assert(pStream->enmDir == PDMAUDIODIR_OUT); + Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED); + + int rc = VINF_SUCCESS; + + uint32_t cfPlayedTotal = 0; + + uint8_t auBuf[256]; /** @todo Get rid of this here. */ + + uint32_t cfLeft = cfToPlay; + uint32_t cbChunk = sizeof(auBuf); + + while (cfLeft) + { + uint32_t cfRead = 0; + rc = AudioMixBufAcquireReadBlock(&pStream->Host.MixBuf, + auBuf, RT_MIN(cbChunk, AUDIOMIXBUF_F2B(&pStream->Host.MixBuf, cfLeft)), + &cfRead); + if (RT_FAILURE(rc)) + break; + + uint32_t cbRead = AUDIOMIXBUF_F2B(&pStream->Host.MixBuf, cfRead); + Assert(cbRead <= cbChunk); + + uint32_t cfPlayed = 0; + uint32_t cbPlayed = 0; + rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStream->pvBackend, + auBuf, cbRead, &cbPlayed); + if ( RT_SUCCESS(rc) + && cbPlayed) + { + if (pThis->Out.Cfg.Dbg.fEnabled) + DrvAudioHlpFileWrite(pStream->Out.Dbg.pFilePlayNonInterleaved, auBuf, cbPlayed, 0 /* fFlags */); + + if (cbRead != cbPlayed) + LogRel2(("Audio: Host stream '%s' played wrong amount (%RU32 bytes read but played %RU32)\n", + pStream->szName, cbRead, cbPlayed)); + + cfPlayed = AUDIOMIXBUF_B2F(&pStream->Host.MixBuf, cbPlayed); + cfPlayedTotal += cfPlayed; + + Assert(cfLeft >= cfPlayed); + cfLeft -= cfPlayed; + } + + AudioMixBufReleaseReadBlock(&pStream->Host.MixBuf, cfPlayed); + + if (RT_FAILURE(rc)) + break; + } + + Log3Func(("[%s] Played %RU32/%RU32 frames, rc=%Rrc\n", pStream->szName, cfPlayedTotal, cfToPlay, rc)); + + if (RT_SUCCESS(rc)) + { + if (pcfPlayed) + *pcfPlayed = cfPlayedTotal; + } + + return rc; +} + +/** + * Plays an audio host output stream which has been configured for raw audio (layout) data. + * + * @return IPRT status code. + * @param pThis Pointer to driver instance. + * @param pStream Stream to play. + * @param cfToPlay Number of audio frames to play. + * @param pcfPlayed Returns number of audio frames played. Optional. + */ +static int drvAudioStreamPlayRaw(PDRVAUDIO pThis, + PPDMAUDIOSTREAM pStream, uint32_t cfToPlay, uint32_t *pcfPlayed) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + /* pcfPlayed is optional. */ + + /* Sanity. */ + Assert(pStream->enmDir == PDMAUDIODIR_OUT); + Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW); + + if (!cfToPlay) + { + if (pcfPlayed) + *pcfPlayed = 0; + return VINF_SUCCESS; + } + + int rc = VINF_SUCCESS; + + uint32_t cfPlayedTotal = 0; + + PDMAUDIOFRAME aFrameBuf[_4K]; /** @todo Get rid of this here. */ + + uint32_t cfLeft = cfToPlay; + while (cfLeft) + { + uint32_t cfRead = 0; + rc = AudioMixBufPeek(&pStream->Host.MixBuf, cfLeft, aFrameBuf, + RT_MIN(cfLeft, RT_ELEMENTS(aFrameBuf)), &cfRead); + + if (RT_SUCCESS(rc)) + { + if (cfRead) + { + uint32_t cfPlayed; + + /* Note: As the stream layout is RPDMAUDIOSTREAMLAYOUT_RAW, operate on audio frames + * rather on bytes. */ + Assert(cfRead <= RT_ELEMENTS(aFrameBuf)); + rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStream->pvBackend, + aFrameBuf, cfRead, &cfPlayed); + if ( RT_FAILURE(rc) + || !cfPlayed) + { + break; + } + + cfPlayedTotal += cfPlayed; + Assert(cfPlayedTotal <= cfToPlay); + + Assert(cfLeft >= cfRead); + cfLeft -= cfRead; + } + else + { + if (rc == VINF_AUDIO_MORE_DATA_AVAILABLE) /* Do another peeking round if there is more data available. */ + continue; + + break; + } + } + else if (RT_FAILURE(rc)) + break; + } + + Log3Func(("[%s] Played %RU32/%RU32 frames, rc=%Rrc\n", pStream->szName, cfPlayedTotal, cfToPlay, rc)); + + if (RT_SUCCESS(rc)) + { + if (pcfPlayed) + *pcfPlayed = cfPlayedTotal; + + } + + return rc; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface, + PPDMAUDIOSTREAM pStream, uint32_t *pcFramesPlayed) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + /* pcFramesPlayed is optional. */ + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT, + ("Stream '%s' is not an output stream and therefore cannot be played back (direction is 0x%x)\n", + pStream->szName, pStream->enmDir)); + + uint32_t cfPlayedTotal = 0; + + PDMAUDIOSTREAMSTS stsStream = pStream->fStatus; +#ifdef LOG_ENABLED + char *pszStreamSts = dbgAudioStreamStatusToStr(stsStream); + Log3Func(("[%s] Start fStatus=%s\n", pStream->szName, pszStreamSts)); + RTStrFree(pszStreamSts); +#endif /* LOG_ENABLED */ + + do + { + if (!pThis->pHostDrvAudio) + { + rc = VERR_PDM_NO_ATTACHED_DRIVER; + break; + } + + if ( !pThis->Out.fEnabled + || !DrvAudioHlpStreamStatusIsReady(stsStream)) + { + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + + const uint32_t cfLive = AudioMixBufLive(&pStream->Host.MixBuf); +#ifdef LOG_ENABLED + const uint8_t uLivePercent = (100 * cfLive) / AudioMixBufSize(&pStream->Host.MixBuf); +#endif + const uint64_t tsDeltaPlayedCapturedNs = RTTimeNanoTS() - pStream->tsLastPlayedCapturedNs; + const uint32_t cfPassedReal = DrvAudioHlpNanoToFrames(tsDeltaPlayedCapturedNs, &pStream->Host.Cfg.Props); + + const uint32_t cfPeriod = pStream->Host.Cfg.Backend.cfPeriod; + + Log3Func(("[%s] Last played %RU64ns (%RU64ms), filled with %RU64ms (%RU8%%) total (fThresholdReached=%RTbool)\n", + pStream->szName, tsDeltaPlayedCapturedNs, tsDeltaPlayedCapturedNs / RT_NS_1MS_64, + DrvAudioHlpFramesToMilli(cfLive, &pStream->Host.Cfg.Props), uLivePercent, pStream->fThresholdReached)); + + if ( pStream->fThresholdReached /* Has the treshold been reached (e.g. are we in playing stage) ... */ + && cfLive == 0) /* ... and we now have no live samples to process? */ + { + LogRel2(("Audio: Buffer underrun for stream '%s' occurred (%RU64ms passed)\n", + pStream->szName, DrvAudioHlpFramesToMilli(cfPassedReal, &pStream->Host.Cfg.Props))); + + if (pStream->Host.Cfg.Backend.cfPreBuf) /* Any pre-buffering configured? */ + { + /* Enter pre-buffering stage again. */ + pStream->fThresholdReached = false; + } + } + + bool fDoPlay = pStream->fThresholdReached; + bool fJustStarted = false; + if (!fDoPlay) + { + /* Did we reach the backend's playback (pre-buffering) threshold? Can be 0 if no threshold set. */ + if (cfLive >= pStream->Host.Cfg.Backend.cfPreBuf) + { + LogRel2(("Audio: Stream '%s' buffering complete\n", pStream->szName)); + Log3Func(("[%s] Dbg: Buffering complete\n", pStream->szName)); + fDoPlay = true; + } + /* Some audio files are shorter than the pre-buffering level (e.g. the "click" Explorer sounds on some Windows guests), + * so make sure that we also play those by checking if the stream already is pending disable mode, even if we didn't + * hit the pre-buffering watermark yet. + * + * To reproduce, use "Windows Navigation Start.wav" on Windows 7 (2824 samples). */ + else if ( cfLive + && pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE) + { + LogRel2(("Audio: Stream '%s' buffering complete (short sound)\n", pStream->szName)); + Log3Func(("[%s] Dbg: Buffering complete (short)\n", pStream->szName)); + fDoPlay = true; + } + + if (fDoPlay) + { + pStream->fThresholdReached = true; + fJustStarted = true; + LogRel2(("Audio: Stream '%s' started playing\n", pStream->szName)); + Log3Func(("[%s] Dbg: started playing\n", pStream->szName)); + } + else /* Not yet, so still buffering audio data. */ + LogRel2(("Audio: Stream '%s' is buffering (%RU8%% complete)\n", + pStream->szName, (100 * cfLive) / pStream->Host.Cfg.Backend.cfPreBuf)); + } + + if (fDoPlay) + { + uint32_t cfWritable = PDMAUDIOPCMPROPS_B2F(&pStream->Host.Cfg.Props, + pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStream->pvBackend)); + + uint32_t cfToPlay = 0; + if (fJustStarted) + cfToPlay = RT_MIN(cfWritable, cfPeriod); + + if (!cfToPlay) + { + /* Did we reach/pass (in real time) the device scheduling slot? + * Play as much as we can write to the backend then. */ + if (cfPassedReal >= DrvAudioHlpMilliToFrames(pStream->Guest.Cfg.Device.uSchedulingHintMs, &pStream->Host.Cfg.Props)) + cfToPlay = cfWritable; + } + + if (cfToPlay > cfLive) /* Don't try to play more than available. */ + cfToPlay = cfLive; +#ifdef DEBUG + Log3Func(("[%s] Playing %RU32 frames (%RU64ms), now filled with %RU64ms -- %RU8%%\n", + pStream->szName, cfToPlay, DrvAudioHlpFramesToMilli(cfToPlay, &pStream->Host.Cfg.Props), + DrvAudioHlpFramesToMilli(AudioMixBufUsed(&pStream->Host.MixBuf), &pStream->Host.Cfg.Props), + AudioMixBufUsed(&pStream->Host.MixBuf) * 100 / AudioMixBufSize(&pStream->Host.MixBuf))); +#endif + if (cfToPlay) + { + if (pThis->pHostDrvAudio->pfnStreamPlayBegin) + pThis->pHostDrvAudio->pfnStreamPlayBegin(pThis->pHostDrvAudio, pStream->pvBackend); + + if (RT_LIKELY(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED)) + { + rc = drvAudioStreamPlayNonInterleaved(pThis, pStream, cfToPlay, &cfPlayedTotal); + } + else if (pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW) + { + rc = drvAudioStreamPlayRaw(pThis, pStream, cfToPlay, &cfPlayedTotal); + } + else + AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + + if (pThis->pHostDrvAudio->pfnStreamPlayEnd) + pThis->pHostDrvAudio->pfnStreamPlayEnd(pThis->pHostDrvAudio, pStream->pvBackend); + + pStream->tsLastPlayedCapturedNs = RTTimeNanoTS(); + } + + Log3Func(("[%s] Dbg: fJustStarted=%RTbool, cfSched=%RU32 (%RU64ms), cfPassedReal=%RU32 (%RU64ms), " + "cfLive=%RU32 (%RU64ms), cfPeriod=%RU32 (%RU64ms), cfWritable=%RU32 (%RU64ms), " + "-> cfToPlay=%RU32 (%RU64ms), cfPlayed=%RU32 (%RU64ms)\n", + pStream->szName, fJustStarted, + DrvAudioHlpMilliToFrames(pStream->Guest.Cfg.Device.uSchedulingHintMs, &pStream->Host.Cfg.Props), + pStream->Guest.Cfg.Device.uSchedulingHintMs, + cfPassedReal, DrvAudioHlpFramesToMilli(cfPassedReal, &pStream->Host.Cfg.Props), + cfLive, DrvAudioHlpFramesToMilli(cfLive, &pStream->Host.Cfg.Props), + cfPeriod, DrvAudioHlpFramesToMilli(cfPeriod, &pStream->Host.Cfg.Props), + cfWritable, DrvAudioHlpFramesToMilli(cfWritable, &pStream->Host.Cfg.Props), + cfToPlay, DrvAudioHlpFramesToMilli(cfToPlay, &pStream->Host.Cfg.Props), + cfPlayedTotal, DrvAudioHlpFramesToMilli(cfPlayedTotal, &pStream->Host.Cfg.Props))); + } + + if (RT_SUCCESS(rc)) + { + AudioMixBufFinish(&pStream->Host.MixBuf, cfPlayedTotal); + +#ifdef VBOX_WITH_STATISTICS + STAM_COUNTER_ADD (&pThis->Stats.TotalFramesOut, cfPlayedTotal); + STAM_PROFILE_ADV_STOP(&pThis->Stats.DelayOut, out); + + STAM_COUNTER_ADD (&pStream->Out.Stats.TotalFramesPlayed, cfPlayedTotal); + STAM_COUNTER_INC (&pStream->Out.Stats.TotalTimesPlayed); +#endif + } + + } while (0); + +#ifdef LOG_ENABLED + uint32_t cfLive = AudioMixBufLive(&pStream->Host.MixBuf); + pszStreamSts = dbgAudioStreamStatusToStr(stsStream); + Log3Func(("[%s] End fStatus=%s, cfLive=%RU32, cfPlayedTotal=%RU32, rc=%Rrc\n", + pStream->szName, pszStreamSts, cfLive, cfPlayedTotal, rc)); + RTStrFree(pszStreamSts); +#endif /* LOG_ENABLED */ + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + if (RT_SUCCESS(rc)) + { + if (pcFramesPlayed) + *pcFramesPlayed = cfPlayedTotal; + } + + if (RT_FAILURE(rc)) + LogFlowFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc)); + + return rc; +} + +/** + * Captures non-interleaved input from a host stream. + * + * @returns IPRT status code. + * @param pThis Driver instance. + * @param pStream Stream to capture from. + * @param pcfCaptured Number of (host) audio frames captured. Optional. + */ +static int drvAudioStreamCaptureNonInterleaved(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, uint32_t *pcfCaptured) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + /* pcfCaptured is optional. */ + + /* Sanity. */ + Assert(pStream->enmDir == PDMAUDIODIR_IN); + Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED); + + int rc = VINF_SUCCESS; + + uint32_t cfCapturedTotal = 0; + + AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable); + + uint8_t auBuf[_1K]; /** @todo Get rid of this. */ + uint32_t cbBuf = sizeof(auBuf); + + uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStream->pvBackend); + if (!cbReadable) + Log2Func(("[%s] No readable data available\n", pStream->szName)); + + uint32_t cbFree = AudioMixBufFreeBytes(&pStream->Guest.MixBuf); /* Parent */ + if (!cbFree) + Log2Func(("[%s] Buffer full\n", pStream->szName)); + + if (cbReadable > cbFree) /* More data readable than we can store at the moment? Limit. */ + cbReadable = cbFree; + + while (cbReadable) + { + uint32_t cbCaptured; + rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStream->pvBackend, + auBuf, RT_MIN(cbReadable, cbBuf), &cbCaptured); + if (RT_FAILURE(rc)) + { + int rc2 = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); + AssertRC(rc2); + + break; + } + + Assert(cbCaptured <= cbBuf); + if (cbCaptured > cbBuf) /* Paranoia. */ + cbCaptured = cbBuf; + + if (!cbCaptured) /* Nothing captured? Take a shortcut. */ + break; + + /* We use the host side mixing buffer as an intermediate buffer to do some + * (first) processing (if needed), so always write the incoming data at offset 0. */ + uint32_t cfHstWritten = 0; + rc = AudioMixBufWriteAt(&pStream->Host.MixBuf, 0 /* offFrames */, auBuf, cbCaptured, &cfHstWritten); + if ( RT_FAILURE(rc) + || !cfHstWritten) + { + AssertMsgFailed(("[%s] Write failed: cbCaptured=%RU32, cfHstWritten=%RU32, rc=%Rrc\n", + pStream->szName, cbCaptured, cfHstWritten, rc)); + break; + } + + if (pThis->In.Cfg.Dbg.fEnabled) + DrvAudioHlpFileWrite(pStream->In.Dbg.pFileCaptureNonInterleaved, auBuf, cbCaptured, 0 /* fFlags */); + + uint32_t cfHstMixed = 0; + if (cfHstWritten) + { + int rc2 = AudioMixBufMixToParentEx(&pStream->Host.MixBuf, 0 /* cSrcOffset */, cfHstWritten /* cSrcFrames */, + &cfHstMixed /* pcSrcMixed */); + Log3Func(("[%s] cbCaptured=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n", + pStream->szName, cbCaptured, cfHstWritten, cfHstMixed, rc2)); + AssertRC(rc2); + } + + Assert(cbReadable >= cbCaptured); + cbReadable -= cbCaptured; + cfCapturedTotal += cfHstMixed; + } + + if (RT_SUCCESS(rc)) + { + if (cfCapturedTotal) + Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCapturedTotal, rc)); + } + else + LogFunc(("[%s] Capturing failed with rc=%Rrc\n", pStream->szName, rc)); + + if (pcfCaptured) + *pcfCaptured = cfCapturedTotal; + + return rc; +} + +/** + * Captures raw input from a host stream. + * Raw input means that the backend directly operates on PDMAUDIOFRAME structs without + * no data layout processing done in between. + * + * Needed for e.g. the VRDP audio backend (in Main). + * + * @returns IPRT status code. + * @param pThis Driver instance. + * @param pStream Stream to capture from. + * @param pcfCaptured Number of (host) audio frames captured. Optional. + */ +static int drvAudioStreamCaptureRaw(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, uint32_t *pcfCaptured) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + /* pcfCaptured is optional. */ + + /* Sanity. */ + Assert(pStream->enmDir == PDMAUDIODIR_IN); + Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW); + + int rc = VINF_SUCCESS; + + uint32_t cfCapturedTotal = 0; + + AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable); + + /* Note: Raw means *audio frames*, not bytes! */ + uint32_t cfReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStream->pvBackend); + if (!cfReadable) + Log2Func(("[%s] No readable data available\n", pStream->szName)); + + const uint32_t cfFree = AudioMixBufFree(&pStream->Guest.MixBuf); /* Parent */ + if (!cfFree) + Log2Func(("[%s] Buffer full\n", pStream->szName)); + + if (cfReadable > cfFree) /* More data readable than we can store at the moment? Limit. */ + cfReadable = cfFree; + + while (cfReadable) + { + PPDMAUDIOFRAME paFrames; + uint32_t cfWritable; + rc = AudioMixBufPeekMutable(&pStream->Host.MixBuf, cfReadable, &paFrames, &cfWritable); + if ( RT_FAILURE(rc) + || !cfWritable) + { + break; + } + + uint32_t cfCaptured; + rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStream->pvBackend, + paFrames, cfWritable, &cfCaptured); + if (RT_FAILURE(rc)) + { + int rc2 = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); + AssertRC(rc2); + + break; + } + + Assert(cfCaptured <= cfWritable); + if (cfCaptured > cfWritable) /* Paranoia. */ + cfCaptured = cfWritable; + + Assert(cfReadable >= cfCaptured); + cfReadable -= cfCaptured; + cfCapturedTotal += cfCaptured; + } + + Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCapturedTotal, rc)); + + if (pcfCaptured) + *pcfCaptured = cfCapturedTotal; + + return rc; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface, + PPDMAUDIOSTREAM pStream, uint32_t *pcFramesCaptured) +{ + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + AssertMsg(pStream->enmDir == PDMAUDIODIR_IN, + ("Stream '%s' is not an input stream and therefore cannot be captured (direction is 0x%x)\n", + pStream->szName, pStream->enmDir)); + + uint32_t cfCaptured = 0; + +#ifdef LOG_ENABLED + char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); + Log3Func(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts)); + RTStrFree(pszStreamSts); +#endif /* LOG_ENABLED */ + + do + { + if (!pThis->pHostDrvAudio) + { + rc = VERR_PDM_NO_ATTACHED_DRIVER; + break; + } + + if ( !pThis->In.fEnabled + || !DrvAudioHlpStreamStatusCanRead(pStream->fStatus)) + { + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + + /* + * Do the actual capturing. + */ + + if (pThis->pHostDrvAudio->pfnStreamCaptureBegin) + pThis->pHostDrvAudio->pfnStreamCaptureBegin(pThis->pHostDrvAudio, pStream->pvBackend); + + if (RT_LIKELY(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED)) + { + rc = drvAudioStreamCaptureNonInterleaved(pThis, pStream, &cfCaptured); + } + else if (pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW) + { + rc = drvAudioStreamCaptureRaw(pThis, pStream, &cfCaptured); + } + else + AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + + if (pThis->pHostDrvAudio->pfnStreamCaptureEnd) + pThis->pHostDrvAudio->pfnStreamCaptureEnd(pThis->pHostDrvAudio, pStream->pvBackend); + + if (RT_SUCCESS(rc)) + { + Log3Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCaptured, rc)); + +#ifdef VBOX_WITH_STATISTICS + STAM_COUNTER_ADD(&pThis->Stats.TotalFramesIn, cfCaptured); + + STAM_COUNTER_ADD(&pStream->In.Stats.TotalFramesCaptured, cfCaptured); +#endif + } + else if (RT_UNLIKELY(RT_FAILURE(rc))) + { + LogRel(("Audio: Capturing stream '%s' failed with %Rrc\n", pStream->szName, rc)); + } + + } while (0); + + if (pcFramesCaptured) + *pcFramesCaptured = cfCaptured; + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + if (RT_FAILURE(rc)) + LogFlowFuncLeaveRC(rc); + + return rc; +} + +#ifdef VBOX_WITH_AUDIO_CALLBACKS +/** + * Duplicates an audio callback. + * + * @returns Pointer to duplicated callback, or NULL on failure. + * @param pCB Callback to duplicate. + */ +static PPDMAUDIOCBRECORD drvAudioCallbackDuplicate(PPDMAUDIOCBRECORD pCB) +{ + AssertPtrReturn(pCB, NULL); + + PPDMAUDIOCBRECORD pCBCopy = (PPDMAUDIOCBRECORD)RTMemDup((void *)pCB, sizeof(PDMAUDIOCBRECORD)); + if (!pCBCopy) + return NULL; + + if (pCB->pvCtx) + { + pCBCopy->pvCtx = RTMemDup(pCB->pvCtx, pCB->cbCtx); + if (!pCBCopy->pvCtx) + { + RTMemFree(pCBCopy); + return NULL; + } + + pCBCopy->cbCtx = pCB->cbCtx; + } + + return pCBCopy; +} + +/** + * Destroys a given callback. + * + * @param pCB Callback to destroy. + */ +static void drvAudioCallbackDestroy(PPDMAUDIOCBRECORD pCB) +{ + if (!pCB) + return; + + RTListNodeRemove(&pCB->Node); + if (pCB->pvCtx) + { + Assert(pCB->cbCtx); + RTMemFree(pCB->pvCtx); + } + RTMemFree(pCB); +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnRegisterCallbacks} + */ +static DECLCALLBACK(int) drvAudioRegisterCallbacks(PPDMIAUDIOCONNECTOR pInterface, + PPDMAUDIOCBRECORD paCallbacks, size_t cCallbacks) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(paCallbacks, VERR_INVALID_POINTER); + AssertReturn(cCallbacks, VERR_INVALID_PARAMETER); + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + for (size_t i = 0; i < cCallbacks; i++) + { + PPDMAUDIOCBRECORD pCB = drvAudioCallbackDuplicate(&paCallbacks[i]); + if (!pCB) + { + rc = VERR_NO_MEMORY; + break; + } + + switch (pCB->enmSource) + { + case PDMAUDIOCBSOURCE_DEVICE: + { + switch (pCB->Device.enmType) + { + case PDMAUDIODEVICECBTYPE_DATA_INPUT: + RTListAppend(&pThis->In.lstCB, &pCB->Node); + break; + + case PDMAUDIODEVICECBTYPE_DATA_OUTPUT: + RTListAppend(&pThis->Out.lstCB, &pCB->Node); + break; + + default: + AssertMsgFailed(("Not supported\n")); + break; + } + + break; + } + + default: + AssertMsgFailed(("Not supported\n")); + break; + } + } + + /** @todo Undo allocations on error. */ + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + return rc; +} +#endif /* VBOX_WITH_AUDIO_CALLBACKS */ + +#ifdef VBOX_WITH_AUDIO_CALLBACKS +/** + * Backend callback implementation. + * + * Important: No calls back to the backend within this function, as the backend + * might hold any locks / critical sections while executing this callback. + * Will result in some ugly deadlocks (or at least locking order violations) then. + * + * @copydoc FNPDMHOSTAUDIOCALLBACK + */ +static DECLCALLBACK(int) drvAudioBackendCallback(PPDMDRVINS pDrvIns, + PDMAUDIOBACKENDCBTYPE enmType, void *pvUser, size_t cbUser) +{ + AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER); + RT_NOREF(pvUser, cbUser); + /* pvUser and cbUser are optional. */ + + /* Get the upper driver (PDMIAUDIOCONNECTOR). */ + AssertPtr(pDrvIns->pUpBase); + PPDMIAUDIOCONNECTOR pInterface = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR); + AssertPtr(pInterface); + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, rc); + + LogFunc(("pThis=%p, enmType=%RU32, pvUser=%p, cbUser=%zu\n", pThis, enmType, pvUser, cbUser)); + + switch (enmType) + { + case PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED: + LogRel(("Audio: Device configuration of driver '%s' has changed\n", pThis->szName)); + rc = drvAudioScheduleReInitInternal(pThis); + break; + + default: + AssertMsgFailed(("Not supported\n")); + break; + } + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} +#endif /* VBOX_WITH_AUDIO_CALLBACKS */ + +#ifdef VBOX_WITH_AUDIO_ENUM +/** + * Enumerates all host audio devices. + * This functionality might not be implemented by all backends and will return VERR_NOT_SUPPORTED + * if not being supported. + * + * @returns IPRT status code. + * @param pThis Driver instance to be called. + * @param fLog Whether to print the enumerated device to the release log or not. + * @param pDevEnum Where to store the device enumeration. + */ +static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIODEVICEENUM pDevEnum) +{ + int rc; + + /* + * If the backend supports it, do a device enumeration. + */ + if (pThis->pHostDrvAudio->pfnGetDevices) + { + PDMAUDIODEVICEENUM DevEnum; + rc = pThis->pHostDrvAudio->pfnGetDevices(pThis->pHostDrvAudio, &DevEnum); + if (RT_SUCCESS(rc)) + { + if (fLog) + LogRel(("Audio: Found %RU16 devices for driver '%s'\n", DevEnum.cDevices, pThis->szName)); + + PPDMAUDIODEVICE pDev; + RTListForEach(&DevEnum.lstDevices, pDev, PDMAUDIODEVICE, Node) + { + if (fLog) + { + char *pszFlags = DrvAudioHlpAudDevFlagsToStrA(pDev->fFlags); + + LogRel(("Audio: Device '%s':\n", pDev->szName)); + LogRel(("Audio: \tUsage = %s\n", DrvAudioHlpAudDirToStr(pDev->enmUsage))); + LogRel(("Audio: \tFlags = %s\n", pszFlags ? pszFlags : "<NONE>")); + LogRel(("Audio: \tInput channels = %RU8\n", pDev->cMaxInputChannels)); + LogRel(("Audio: \tOutput channels = %RU8\n", pDev->cMaxOutputChannels)); + + if (pszFlags) + RTStrFree(pszFlags); + } + } + + if (pDevEnum) + rc = DrvAudioHlpDeviceEnumCopy(pDevEnum, &DevEnum); + + DrvAudioHlpDeviceEnumFree(&DevEnum); + } + else + { + if (fLog) + LogRel(("Audio: Device enumeration for driver '%s' failed with %Rrc\n", pThis->szName, rc)); + /* Not fatal. */ + } + } + else + { + rc = VERR_NOT_SUPPORTED; + + if (fLog) + LogRel2(("Audio: Host driver '%s' does not support audio device enumeration, skipping\n", pThis->szName)); + } + + LogFunc(("Returning %Rrc\n", rc)); + return rc; +} +#endif /* VBOX_WITH_AUDIO_ENUM */ + +/** + * Initializes the host backend and queries its initial configuration. + * If the host backend fails, VERR_AUDIO_BACKEND_INIT_FAILED will be returned. + * + * Note: As this routine is called when attaching to the device LUN in the + * device emulation, we either check for success or VERR_AUDIO_BACKEND_INIT_FAILED. + * Everything else is considered as fatal and must be handled separately in + * the device emulation! + * + * @return IPRT status code. + * @param pThis Driver instance to be called. + * @param pCfgHandle CFGM configuration handle to use for this driver. + */ +static int drvAudioHostInit(PDRVAUDIO pThis, PCFGMNODE pCfgHandle) +{ + /* pCfgHandle is optional. */ + NOREF(pCfgHandle); + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + AssertPtr(pThis->pHostDrvAudio); + int rc = pThis->pHostDrvAudio->pfnInit(pThis->pHostDrvAudio); + if (RT_FAILURE(rc)) + { + LogRel(("Audio: Initialization of host driver '%s' failed with %Rrc\n", pThis->szName, rc)); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + + /* + * Get the backend configuration. + */ + rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, &pThis->BackendCfg); + if (RT_FAILURE(rc)) + { + LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->szName, rc)); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + + pThis->In.cStreamsFree = pThis->BackendCfg.cMaxStreamsIn; + pThis->Out.cStreamsFree = pThis->BackendCfg.cMaxStreamsOut; + + LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree)); + + LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once\n", + pThis->szName, + /* Clamp for logging. Unlimited streams are defined by UINT32_MAX. */ + RT_MIN(64, pThis->In.cStreamsFree), RT_MIN(64, pThis->Out.cStreamsFree))); + +#ifdef VBOX_WITH_AUDIO_ENUM + int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */); + if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */ + AssertRC(rc2); + + RT_NOREF(rc2); + /* Ignore rc. */ +#endif + +#ifdef VBOX_WITH_AUDIO_CALLBACKS + /* + * If the backend supports it, offer a callback to this connector. + */ + if (pThis->pHostDrvAudio->pfnSetCallback) + { + rc2 = pThis->pHostDrvAudio->pfnSetCallback(pThis->pHostDrvAudio, drvAudioBackendCallback); + if (RT_FAILURE(rc2)) + LogRel(("Audio: Error registering callback for host driver '%s', rc=%Rrc\n", pThis->szName, rc2)); + /* Not fatal. */ + } +#endif + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +/** + * Handles state changes for all audio streams. + * + * @param pDrvIns Pointer to driver instance. + * @param enmCmd Stream command to set for all streams. + */ +static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + + LogFlowFunc(("enmCmd=%s\n", DrvAudioHlpStreamCmdToStr(enmCmd))); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc2); + + if (pThis->pHostDrvAudio) + { + PPDMAUDIOSTREAM pStream; + RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node) + drvAudioStreamControlInternal(pThis, pStream, enmCmd); + } + + rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); +} + +/** + * Retrieves an audio configuration from the specified CFGM node. + * + * @return VBox status code. + * @param pThis Driver instance to be called. + * @param pCfg Where to store the retrieved audio configuration to. + * @param pNode Where to get the audio configuration from. + */ +static int drvAudioGetCfgFromCFGM(PDRVAUDIO pThis, PDRVAUDIOCFG pCfg, PCFGMNODE pNode) +{ + RT_NOREF(pThis); + + /* Debug stuff. */ + CFGMR3QueryBoolDef(pNode, "DebugEnabled", &pCfg->Dbg.fEnabled, false); + int rc2 = CFGMR3QueryString(pNode, "DebugPathOut", pCfg->Dbg.szPathOut, sizeof(pCfg->Dbg.szPathOut)); + if ( RT_FAILURE(rc2) + || !strlen(pCfg->Dbg.szPathOut)) + { + RTStrPrintf(pCfg->Dbg.szPathOut, sizeof(pCfg->Dbg.szPathOut), VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH); + } + + if (pCfg->Dbg.fEnabled) + LogRel(("Audio: Debugging for driver '%s' enabled (audio data written to '%s')\n", pThis->szName, pCfg->Dbg.szPathOut)); + + /* Buffering stuff. */ + CFGMR3QueryU32Def(pNode, "PeriodSizeMs", &pCfg->uPeriodSizeMs, 0); + CFGMR3QueryU32Def(pNode, "BufferSizeMs", &pCfg->uBufferSizeMs, 0); + CFGMR3QueryU32Def(pNode, "PreBufferSizeMs", &pCfg->uPreBufSizeMs, UINT32_MAX /* No custom value set */); + + LogFunc(("pCfg=%p, uPeriodSizeMs=%RU32, uBufferSizeMs=%RU32, uPreBufSizeMs=%RU32\n", + pCfg, pCfg->uPeriodSizeMs, pCfg->uBufferSizeMs, pCfg->uPreBufSizeMs)); + + return VINF_SUCCESS; +} + +/** + * Intializes an audio driver instance. + * + * @returns IPRT status code. + * @param pDrvIns Pointer to driver instance. + * @param pCfgHandle CFGM handle to use for configuration. + */ +static int drvAudioInit(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle) +{ + AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER); + AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER); + + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + + int rc = RTCritSectInit(&pThis->CritSect); + AssertRCReturn(rc, rc); + + /* + * Configure driver from CFGM. + */ +#ifdef DEBUG + CFGMR3Dump(pCfgHandle); +#endif + + pThis->fTerminate = false; + pThis->pCFGMNode = pCfgHandle; + + int rc2 = CFGMR3QueryString(pThis->pCFGMNode, "DriverName", pThis->szName, sizeof(pThis->szName)); + if (RT_FAILURE(rc2)) + RTStrPrintf(pThis->szName, sizeof(pThis->szName), "Untitled"); + + /* By default we don't enable anything if wrongly / not set-up. */ + CFGMR3QueryBoolDef(pThis->pCFGMNode, "InputEnabled", &pThis->In.fEnabled, false); + CFGMR3QueryBoolDef(pThis->pCFGMNode, "OutputEnabled", &pThis->Out.fEnabled, false); + + LogRel2(("Audio: Verbose logging for driver '%s' enabled\n", pThis->szName)); + + LogRel2(("Audio: Initial status for driver '%s' is: input is %s, output is %s\n", + pThis->szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled")); + + /* + * Load configurations. + */ + rc = drvAudioGetCfgFromCFGM(pThis, &pThis->In.Cfg, pThis->pCFGMNode); + if (RT_SUCCESS(rc)) + rc = drvAudioGetCfgFromCFGM(pThis, &pThis->Out.Cfg, pThis->pCFGMNode); + + LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc)); + return rc; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRead} + */ +static DECLCALLBACK(int) drvAudioStreamRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + /* pcbRead is optional. */ + + AssertMsg(pStream->enmDir == PDMAUDIODIR_IN, + ("Stream '%s' is not an input stream and therefore cannot be read from (direction is 0x%x)\n", + pStream->szName, pStream->enmDir)); + + uint32_t cbReadTotal = 0; + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + do + { + if ( !pThis->In.fEnabled + || !DrvAudioHlpStreamStatusCanRead(pStream->fStatus)) + { + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + + /* + * Read from the parent buffer (that is, the guest buffer) which + * should have the audio data in the format the guest needs. + */ + uint32_t cfReadTotal = 0; + + const uint32_t cfBuf = AUDIOMIXBUF_B2F(&pStream->Guest.MixBuf, cbBuf); + + uint32_t cfToRead = RT_MIN(cfBuf, AudioMixBufLive(&pStream->Guest.MixBuf)); + while (cfToRead) + { + uint32_t cfRead; + rc = AudioMixBufAcquireReadBlock(&pStream->Guest.MixBuf, + (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal), + AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfToRead), &cfRead); + if (RT_FAILURE(rc)) + break; + +#ifdef VBOX_WITH_STATISTICS + const uint32_t cbRead = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfRead); + + STAM_COUNTER_ADD(&pThis->Stats.TotalBytesRead, cbRead); + + STAM_COUNTER_ADD(&pStream->In.Stats.TotalFramesRead, cfRead); + STAM_COUNTER_INC(&pStream->In.Stats.TotalTimesRead); +#endif + Assert(cfToRead >= cfRead); + cfToRead -= cfRead; + + cfReadTotal += cfRead; + + AudioMixBufReleaseReadBlock(&pStream->Guest.MixBuf, cfRead); + } + + if (cfReadTotal) + { + if (pThis->In.Cfg.Dbg.fEnabled) + DrvAudioHlpFileWrite(pStream->In.Dbg.pFileStreamRead, + pvBuf, AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal), 0 /* fFlags */); + + AudioMixBufFinish(&pStream->Guest.MixBuf, cfReadTotal); + } + + /* If we were not able to read as much data as requested, fill up the returned + * data with silence. + * + * This is needed to keep the device emulation DMA transfers up and running at a constant rate. */ + if (cfReadTotal < cfBuf) + { + Log3Func(("[%s] Filling in silence (%RU64ms / %RU64ms)\n", pStream->szName, + DrvAudioHlpFramesToMilli(cfBuf - cfReadTotal, &pStream->Guest.Cfg.Props), + DrvAudioHlpFramesToMilli(cfBuf, &pStream->Guest.Cfg.Props))); + + DrvAudioHlpClearBuf(&pStream->Guest.Cfg.Props, + (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal), + AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfBuf - cfReadTotal), + cfBuf - cfReadTotal); + + cfReadTotal = cfBuf; + } + + cbReadTotal = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal); + + pStream->tsLastReadWrittenNs = RTTimeNanoTS(); + + Log3Func(("[%s] fEnabled=%RTbool, cbReadTotal=%RU32, rc=%Rrc\n", pStream->szName, pThis->In.fEnabled, cbReadTotal, rc)); + + } while (0); + + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + if (RT_SUCCESS(rc)) + { + if (pcbRead) + *pcbRead = cbReadTotal; + } + + return rc; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface, + PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest, + PPDMAUDIOSTREAM *ppStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER); + AssertPtrReturn(ppStream, VERR_INVALID_POINTER); + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + LogFlowFunc(("Host=%s, Guest=%s\n", pCfgHost->szName, pCfgGuest->szName)); +#ifdef DEBUG + DrvAudioHlpStreamCfgPrint(pCfgHost); + DrvAudioHlpStreamCfgPrint(pCfgGuest); +#endif + + PPDMAUDIOSTREAM pStream = NULL; + +#define RC_BREAK(x) { rc = x; break; } + + do + { + if ( !DrvAudioHlpStreamCfgIsValid(pCfgHost) + || !DrvAudioHlpStreamCfgIsValid(pCfgGuest)) + { + RC_BREAK(VERR_INVALID_PARAMETER); + } + + /* Make sure that both configurations actually intend the same thing. */ + if (pCfgHost->enmDir != pCfgGuest->enmDir) + { + AssertMsgFailed(("Stream configuration directions do not match\n")); + RC_BREAK(VERR_INVALID_PARAMETER); + } + + /* Note: cbHstStrm will contain the size of the data the backend needs to operate on. */ + size_t cbHstStrm; + if (pCfgHost->enmDir == PDMAUDIODIR_IN) + { + if (!pThis->In.cStreamsFree) + { + LogFlowFunc(("Maximum number of host input streams reached\n")); + RC_BREAK(VERR_AUDIO_NO_FREE_INPUT_STREAMS); + } + + cbHstStrm = pThis->BackendCfg.cbStreamIn; + } + else /* Out */ + { + if (!pThis->Out.cStreamsFree) + { + LogFlowFunc(("Maximum number of host output streams reached\n")); + RC_BREAK(VERR_AUDIO_NO_FREE_OUTPUT_STREAMS); + } + + cbHstStrm = pThis->BackendCfg.cbStreamOut; + } + + /* + * Allocate and initialize common state. + */ + + pStream = (PPDMAUDIOSTREAM)RTMemAllocZ(sizeof(PDMAUDIOSTREAM)); + AssertPtrBreakStmt(pStream, rc = VERR_NO_MEMORY); + + /* Retrieve host driver name for easier identification. */ + AssertPtr(pThis->pHostDrvAudio); + PPDMDRVINS pDrvAudioInst = PDMIBASE_2_PDMDRV(pThis->pDrvIns->pDownBase); + AssertPtr(pDrvAudioInst); + AssertPtr(pDrvAudioInst->pReg); + + Assert(pDrvAudioInst->pReg->szName[0] != '\0'); + RTStrPrintf(pStream->szName, RT_ELEMENTS(pStream->szName), "[%s] %s", + pDrvAudioInst->pReg->szName[0] != '\0' ? pDrvAudioInst->pReg->szName : "Untitled", + pCfgHost->szName[0] != '\0' ? pCfgHost->szName : "<Untitled>"); + + pStream->enmDir = pCfgHost->enmDir; + + /* + * Allocate and init backend-specific data. + */ + + if (cbHstStrm) /* High unlikely that backends do not have an own space for data, but better check. */ + { + pStream->pvBackend = RTMemAllocZ(cbHstStrm); + AssertPtrBreakStmt(pStream->pvBackend, rc = VERR_NO_MEMORY); + + pStream->cbBackend = cbHstStrm; + } + + /* + * Try to init the rest. + */ + + rc = drvAudioStreamInitInternal(pThis, pStream, pCfgHost, pCfgGuest); + if (RT_FAILURE(rc)) + break; + + } while (0); + +#undef RC_BREAK + + if (RT_FAILURE(rc)) + { + if (pStream) + { + int rc2 = drvAudioStreamUninitInternal(pThis, pStream); + if (RT_SUCCESS(rc2)) + { + drvAudioStreamFree(pStream); + pStream = NULL; + } + } + } + else + { + /* Append the stream to our stream list. */ + RTListAppend(&pThis->lstStreams, &pStream->Node); + + /* Set initial reference counts. */ + pStream->cRefs = 1; + + if (pCfgHost->enmDir == PDMAUDIODIR_IN) + { + if (pThis->In.Cfg.Dbg.fEnabled) + { + char szFile[RTPATH_MAX + 1]; + + int rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->In.Cfg.Dbg.szPathOut, "CaptureNonInterleaved", + pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE); + if (RT_SUCCESS(rc2)) + { + rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE, + &pStream->In.Dbg.pFileCaptureNonInterleaved); + if (RT_SUCCESS(rc2)) + rc2 = DrvAudioHlpFileOpen(pStream->In.Dbg.pFileCaptureNonInterleaved, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, + &pStream->Host.Cfg.Props); + } + + if (RT_SUCCESS(rc2)) + { + rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->In.Cfg.Dbg.szPathOut, "StreamRead", + pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE); + if (RT_SUCCESS(rc2)) + { + rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE, + &pStream->In.Dbg.pFileStreamRead); + if (RT_SUCCESS(rc2)) + rc2 = DrvAudioHlpFileOpen(pStream->In.Dbg.pFileStreamRead, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, + &pStream->Host.Cfg.Props); + } + } + } + + if (pThis->In.cStreamsFree) + pThis->In.cStreamsFree--; + } + else /* Out */ + { + if (pThis->Out.Cfg.Dbg.fEnabled) + { + char szFile[RTPATH_MAX + 1]; + + int rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->Out.Cfg.Dbg.szPathOut, "PlayNonInterleaved", + pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE); + if (RT_SUCCESS(rc2)) + { + rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE, + &pStream->Out.Dbg.pFilePlayNonInterleaved); + if (RT_SUCCESS(rc2)) + rc = DrvAudioHlpFileOpen(pStream->Out.Dbg.pFilePlayNonInterleaved, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, + &pStream->Host.Cfg.Props); + } + + if (RT_SUCCESS(rc2)) + { + rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->Out.Cfg.Dbg.szPathOut, "StreamWrite", + pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE); + if (RT_SUCCESS(rc2)) + { + rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE, + &pStream->Out.Dbg.pFileStreamWrite); + if (RT_SUCCESS(rc2)) + rc2 = DrvAudioHlpFileOpen(pStream->Out.Dbg.pFileStreamWrite, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, + &pStream->Host.Cfg.Props); + } + } + } + + if (pThis->Out.cStreamsFree) + pThis->Out.cStreamsFree--; + } + +#ifdef VBOX_WITH_STATISTICS + STAM_COUNTER_ADD(&pThis->Stats.TotalStreamsCreated, 1); +#endif + *ppStream = pStream; + } + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable} + */ +static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + bool *pfEnabled; + if (enmDir == PDMAUDIODIR_IN) + pfEnabled = &pThis->In.fEnabled; + else if (enmDir == PDMAUDIODIR_OUT) + pfEnabled = &pThis->Out.fEnabled; + else + AssertFailedReturn(VERR_INVALID_PARAMETER); + + if (fEnable != *pfEnabled) + { + LogRel(("Audio: %s %s for driver '%s'\n", + fEnable ? "Enabling" : "Disabling", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->szName)); + + PPDMAUDIOSTREAM pStream; + RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node) + { + if (pStream->enmDir != enmDir) /* Skip unwanted streams. */ + continue; + + int rc2 = drvAudioStreamControlInternal(pThis, pStream, + fEnable ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE); + if (RT_FAILURE(rc2)) + LogRel(("Audio: Failed to %s %s stream '%s', rc=%Rrc\n", + fEnable ? "enable" : "disable", enmDir == PDMAUDIODIR_IN ? "input" : "output", pStream->szName, rc2)); + + if (RT_SUCCESS(rc)) + rc = rc2; + + /* Keep going. */ + } + + *pfEnabled = fEnable; + } + + int rc3 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc3; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled} + */ +static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir) +{ + AssertPtrReturn(pInterface, false); + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc2)) + return false; + + bool *pfEnabled; + if (enmDir == PDMAUDIODIR_IN) + pfEnabled = &pThis->In.fEnabled; + else if (enmDir == PDMAUDIODIR_OUT) + pfEnabled = &pThis->Out.fEnabled; + else + AssertFailedReturn(false); + + const bool fIsEnabled = *pfEnabled; + + rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + + return fIsEnabled; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig} + */ +static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + if (pThis->pHostDrvAudio) + { + if (pThis->pHostDrvAudio->pfnGetConfig) + rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg); + else + rc = VERR_NOT_SUPPORTED; + } + else + rc = VERR_PDM_NO_ATTACHED_DRIVER; + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir) +{ + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + PDMAUDIOBACKENDSTS backendSts = PDMAUDIOBACKENDSTS_UNKNOWN; + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->pHostDrvAudio) + { + if (pThis->pHostDrvAudio->pfnGetStatus) + backendSts = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir); + } + else + backendSts = PDMAUDIOBACKENDSTS_NOT_ATTACHED; + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + LogFlowFuncLeaveRC(rc); + return backendSts; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvAudioStreamGetReadable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +{ + AssertPtrReturn(pInterface, 0); + AssertPtrReturn(pStream, 0); + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc2); + + AssertMsg(pStream->enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n")); + + uint32_t cbReadable = 0; + + if ( pThis->pHostDrvAudio + && DrvAudioHlpStreamStatusCanRead(pStream->fStatus)) + { + const uint32_t cfReadable = AudioMixBufLive(&pStream->Guest.MixBuf); + + cbReadable = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadable); + + if (!cbReadable) + { + /* + * If nothing is readable, check if the stream on the backend side is ready to be read from. + * If it isn't, return the number of bytes readable since the last read from this stream. + * + * This is needed for backends (e.g. VRDE) which do not provide any input data in certain + * situations, but the device emulation needs input data to keep the DMA transfers moving. + * Reading the actual data from a stream then will return silence then. + */ + if (!DrvAudioHlpStreamStatusCanRead( + pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStream->pvBackend))) + { + cbReadable = DrvAudioHlpNanoToBytes(RTTimeNanoTS() - pStream->tsLastReadWrittenNs, + &pStream->Host.Cfg.Props); + Log3Func(("[%s] Backend stream not ready, returning silence\n", pStream->szName)); + } + } + + /* Make sure to align the readable size to the guest's frame size. */ + cbReadable = DrvAudioHlpBytesAlign(cbReadable, &pStream->Guest.Cfg.Props); + } + + Log3Func(("[%s] cbReadable=%RU32 (%RU64ms)\n", + pStream->szName, cbReadable, DrvAudioHlpBytesToMilli(cbReadable, &pStream->Host.Cfg.Props))); + + rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + + /* Return bytes instead of audio frames. */ + return cbReadable; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +{ + AssertPtrReturn(pInterface, 0); + AssertPtrReturn(pStream, 0); + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc2); + + AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n")); + + uint32_t cbWritable = 0; + + /* Note: We don't propage the backend stream's status to the outside -- it's the job of this + * audio connector to make sense of it. */ + if (DrvAudioHlpStreamStatusCanWrite(pStream->fStatus)) + { + cbWritable = AudioMixBufFreeBytes(&pStream->Host.MixBuf); + + /* Make sure to align the writable size to the host's frame size. */ + cbWritable = DrvAudioHlpBytesAlign(cbWritable, &pStream->Host.Cfg.Props); + } + + Log3Func(("[%s] cbWritable=%RU32 (%RU64ms)\n", + pStream->szName, cbWritable, DrvAudioHlpBytesToMilli(cbWritable, &pStream->Host.Cfg.Props))); + + rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + + return cbWritable; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetStatus} + */ +static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioStreamGetStatus(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +{ + AssertPtrReturn(pInterface, false); + + if (!pStream) + return PDMAUDIOSTREAMSTS_FLAG_NONE; + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc2); + + PDMAUDIOSTREAMSTS stsStream = pStream->fStatus; + +#ifdef LOG_ENABLED + char *pszStreamSts = dbgAudioStreamStatusToStr(stsStream); + Log3Func(("[%s] %s\n", pStream->szName, pszStreamSts)); + RTStrFree(pszStreamSts); +#endif + + rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + + return stsStream; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamSetVolume} + */ +static DECLCALLBACK(int) drvAudioStreamSetVolume(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, PPDMAUDIOVOLUME pVol) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pVol, VERR_INVALID_POINTER); + + LogFlowFunc(("[%s] volL=%RU32, volR=%RU32, fMute=%RTbool\n", pStream->szName, pVol->uLeft, pVol->uRight, pVol->fMuted)); + + AudioMixBufSetVolume(&pStream->Guest.MixBuf, pVol); + AudioMixBufSetVolume(&pStream->Host.MixBuf, pVol); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc); + + LogRel2(("Audio: Destroying stream '%s'\n", pStream->szName)); + + LogFlowFunc(("[%s] cRefs=%RU32\n", pStream->szName, pStream->cRefs)); + if (pStream->cRefs > 1) + rc = VERR_WRONG_ORDER; + + if (RT_SUCCESS(rc)) + { + rc = drvAudioStreamUninitInternal(pThis, pStream); + if (RT_FAILURE(rc)) + LogRel(("Audio: Uninitializing stream '%s' failed with %Rrc\n", pStream->szName, rc)); + } + + if (RT_SUCCESS(rc)) + { + if (pStream->enmDir == PDMAUDIODIR_IN) + { + pThis->In.cStreamsFree++; + } + else /* Out */ + { + pThis->Out.cStreamsFree++; + } + + RTListNodeRemove(&pStream->Node); + + drvAudioStreamFree(pStream); + pStream = NULL; + } + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Creates an audio stream on the backend side. + * + * @returns IPRT status code. + * @param pThis Pointer to driver instance. + * @param pStream Audio stream to create the backend side for. + * @param pCfgReq Requested audio stream configuration to use for stream creation. + * @param pCfgAcq Acquired audio stream configuration returned by the backend. + * + * @note Configuration precedence for requested audio stream configuration (first has highest priority, if set): + * - per global extra-data + * - per-VM extra-data + * - requested configuration (by pCfgReq) + * - default value + */ +static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, + PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + AssertMsg((pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED) == 0, + ("Stream '%s' already initialized in backend\n", pStream->szName)); + + /* Get the right configuration for the stream to be created. */ + PDRVAUDIOCFG pDrvCfg = pCfgReq->enmDir == PDMAUDIODIR_IN ? &pThis->In.Cfg : &pThis->Out.Cfg; + + /* Fill in the tweakable parameters into the requested host configuration. + * All parameters in principle can be changed and returned by the backend via the acquired configuration. */ + + char szWhat[64]; /* Log where a value came from. */ + + /* + * Period size + */ + if (pDrvCfg->uPeriodSizeMs) + { + pCfgReq->Backend.cfPeriod = DrvAudioHlpMilliToFrames(pDrvCfg->uPeriodSizeMs, &pCfgReq->Props); + RTStrPrintf(szWhat, sizeof(szWhat), "global / per-VM"); + } + + if (!pCfgReq->Backend.cfPeriod) /* Set default period size if nothing explicitly is set. */ + { + pCfgReq->Backend.cfPeriod = DrvAudioHlpMilliToFrames(50 /* ms */, &pCfgReq->Props); + RTStrPrintf(szWhat, sizeof(szWhat), "default"); + } + else + RTStrPrintf(szWhat, sizeof(szWhat), "device-specific"); + + LogRel2(("Audio: Using %s period size (%RU64ms, %RU32 frames) for stream '%s'\n", + szWhat, + DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPeriod, &pCfgReq->Props), pCfgReq->Backend.cfPeriod, pStream->szName)); + + /* + * Buffer size + */ + if (pDrvCfg->uBufferSizeMs) + { + pCfgReq->Backend.cfBufferSize = DrvAudioHlpMilliToFrames(pDrvCfg->uBufferSizeMs, &pCfgReq->Props); + RTStrPrintf(szWhat, sizeof(szWhat), "global / per-VM"); + } + + if (!pCfgReq->Backend.cfBufferSize) /* Set default buffer size if nothing explicitly is set. */ + { + pCfgReq->Backend.cfBufferSize = DrvAudioHlpMilliToFrames(250 /* ms */, &pCfgReq->Props); + RTStrPrintf(szWhat, sizeof(szWhat), "default"); + } + else + RTStrPrintf(szWhat, sizeof(szWhat), "device-specific"); + + LogRel2(("Audio: Using %s buffer size (%RU64ms, %RU32 frames) for stream '%s'\n", + szWhat, + DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props), pCfgReq->Backend.cfBufferSize, pStream->szName)); + + /* + * Pre-buffering size + */ + if (pDrvCfg->uPreBufSizeMs != UINT32_MAX) /* Anything set via global / per-VM extra-data? */ + { + pCfgReq->Backend.cfPreBuf = DrvAudioHlpMilliToFrames(pDrvCfg->uPreBufSizeMs, &pCfgReq->Props); + RTStrPrintf(szWhat, sizeof(szWhat), "global / per-VM"); + } + else /* No, then either use the default or device-specific settings (if any). */ + { + if (pCfgReq->Backend.cfPreBuf == UINT32_MAX) /* Set default pre-buffering size if nothing explicitly is set. */ + { + /* For pre-buffering to finish the buffer at least must be full one time. */ + pCfgReq->Backend.cfPreBuf = pCfgReq->Backend.cfBufferSize; + RTStrPrintf(szWhat, sizeof(szWhat), "default"); + } + else + RTStrPrintf(szWhat, sizeof(szWhat), "device-specific"); + } + + LogRel2(("Audio: Using %s pre-buffering size (%RU64ms, %RU32 frames) for stream '%s'\n", + szWhat, + DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPreBuf, &pCfgReq->Props), pCfgReq->Backend.cfPreBuf, pStream->szName)); + + /* + * Validate input. + */ + if (pCfgReq->Backend.cfBufferSize < pCfgReq->Backend.cfPeriod) + { + LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the period size (%RU64ms)\n", + pStream->szName, DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props), + DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPeriod, &pCfgReq->Props))); + return VERR_INVALID_PARAMETER; + } + + if ( pCfgReq->Backend.cfPreBuf != UINT32_MAX /* Custom pre-buffering set? */ + && pCfgReq->Backend.cfPreBuf) + { + if (pCfgReq->Backend.cfBufferSize < pCfgReq->Backend.cfPreBuf) + { + LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the pre-buffering size (%RU64ms)\n", + pStream->szName, DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPreBuf, &pCfgReq->Props), + DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props))); + return VERR_INVALID_PARAMETER; + } + } + + /* Make the acquired host configuration the requested host configuration initially, + * in case the backend does not report back an acquired configuration. */ + int rc = DrvAudioHlpStreamCfgCopy(pCfgAcq, pCfgReq); + if (RT_FAILURE(rc)) + { + LogRel(("Audio: Creating stream '%s' with an invalid backend configuration not possible, skipping\n", + pStream->szName)); + return rc; + } + + rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pStream->pvBackend, pCfgReq, pCfgAcq); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NOT_SUPPORTED) + LogRel2(("Audio: Creating stream '%s' in backend not supported\n", pStream->szName)); + else if (rc == VERR_AUDIO_STREAM_COULD_NOT_CREATE) + LogRel2(("Audio: Stream '%s' could not be created in backend because of missing hardware / drivers\n", pStream->szName)); + else + LogRel(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pStream->szName, rc)); + + return rc; + } + + /* Validate acquired configuration. */ + if (!DrvAudioHlpStreamCfgIsValid(pCfgAcq)) + { + LogRel(("Audio: Creating stream '%s' returned an invalid backend configuration, skipping\n", pStream->szName)); + return VERR_INVALID_PARAMETER; + } + + /* Let the user know that the backend changed one of the values requested above. */ + if (pCfgAcq->Backend.cfBufferSize != pCfgReq->Backend.cfBufferSize) + LogRel2(("Audio: Buffer size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n", + pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cfBufferSize, &pCfgAcq->Props), pCfgAcq->Backend.cfBufferSize)); + + if (pCfgAcq->Backend.cfPeriod != pCfgReq->Backend.cfPeriod) + LogRel2(("Audio: Period size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n", + pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cfPeriod, &pCfgAcq->Props), pCfgAcq->Backend.cfPeriod)); + + /* Was pre-buffering requested, but the acquired configuration from the backend told us something else? */ + if ( pCfgReq->Backend.cfPreBuf + && pCfgAcq->Backend.cfPreBuf != pCfgReq->Backend.cfPreBuf) + { + LogRel2(("Audio: Pre-buffering size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n", + pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cfPreBuf, &pCfgAcq->Props), pCfgAcq->Backend.cfPreBuf)); + } + else if (pCfgReq->Backend.cfPreBuf == 0) /* Was the pre-buffering requested as being disabeld? Tell the users. */ + { + LogRel2(("Audio: Pre-buffering is disabled for stream '%s'\n", pStream->szName)); + pCfgAcq->Backend.cfPreBuf = 0; + } + + /* Sanity for detecting buggy backends. */ + AssertMsgReturn(pCfgAcq->Backend.cfPeriod < pCfgAcq->Backend.cfBufferSize, + ("Acquired period size must be smaller than buffer size\n"), + VERR_INVALID_PARAMETER); + AssertMsgReturn(pCfgAcq->Backend.cfPreBuf <= pCfgAcq->Backend.cfBufferSize, + ("Acquired pre-buffering size must be smaller or as big as the buffer size\n"), + VERR_INVALID_PARAMETER); + + pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_INITIALIZED; + + return VINF_SUCCESS; +} + +/** + * Calls the backend to give it the chance to destroy its part of the audio stream. + * + * @returns IPRT status code. + * @param pThis Pointer to driver instance. + * @param pStream Audio stream destruct backend for. + */ +static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + +#ifdef LOG_ENABLED + char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); + LogFunc(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts)); +#endif + + if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED) + { + AssertPtr(pStream->pvBackend); + + /* Check if the pointer to the host audio driver is still valid. + * It can be NULL if we were called in drvAudioDestruct, for example. */ + if (pThis->pHostDrvAudio) + rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pStream->pvBackend); + + pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_INITIALIZED; + } + +#ifdef LOG_ENABLED + RTStrFree(pszStreamSts); +#endif + + LogFlowFunc(("[%s] Returning %Rrc\n", pStream->szName, rc)); + return rc; +} + +/** + * Uninitializes an audio stream. + * + * @returns IPRT status code. + * @param pThis Pointer to driver instance. + * @param pStream Pointer to audio stream to uninitialize. + */ +static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + LogFlowFunc(("[%s] cRefs=%RU32\n", pStream->szName, pStream->cRefs)); + + if (pStream->cRefs > 1) + return VERR_WRONG_ORDER; + + int rc = drvAudioStreamControlInternal(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); + if (RT_SUCCESS(rc)) + rc = drvAudioStreamDestroyInternalBackend(pThis, pStream); + + /* Destroy mixing buffers. */ + AudioMixBufDestroy(&pStream->Guest.MixBuf); + AudioMixBufDestroy(&pStream->Host.MixBuf); + + if (RT_SUCCESS(rc)) + { +#ifdef LOG_ENABLED + if (pStream->fStatus != PDMAUDIOSTREAMSTS_FLAG_NONE) + { + char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus); + LogFunc(("[%s] Warning: Still has %s set when uninitializing\n", pStream->szName, pszStreamSts)); + RTStrFree(pszStreamSts); + } +#endif + pStream->fStatus = PDMAUDIOSTREAMSTS_FLAG_NONE; + } + + if (pStream->enmDir == PDMAUDIODIR_IN) + { +#ifdef VBOX_WITH_STATISTICS + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalFramesCaptured); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalTimesCaptured); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalFramesRead); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalTimesRead); +#endif + if (pThis->In.Cfg.Dbg.fEnabled) + { + DrvAudioHlpFileDestroy(pStream->In.Dbg.pFileCaptureNonInterleaved); + pStream->In.Dbg.pFileCaptureNonInterleaved = NULL; + + DrvAudioHlpFileDestroy(pStream->In.Dbg.pFileStreamRead); + pStream->In.Dbg.pFileStreamRead = NULL; + } + } + else if (pStream->enmDir == PDMAUDIODIR_OUT) + { +#ifdef VBOX_WITH_STATISTICS + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesPlayed); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesPlayed); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesWritten); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesWritten); +#endif + if (pThis->Out.Cfg.Dbg.fEnabled) + { + DrvAudioHlpFileDestroy(pStream->Out.Dbg.pFilePlayNonInterleaved); + pStream->Out.Dbg.pFilePlayNonInterleaved = NULL; + + DrvAudioHlpFileDestroy(pStream->Out.Dbg.pFileStreamWrite); + pStream->Out.Dbg.pFileStreamWrite = NULL; + } + } + else + AssertFailed(); + + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} + +/** + * Does the actual backend driver attaching and queries the backend's interface. + * + * @return VBox status code. + * @param pThis Pointer to driver instance. + * @param fFlags Attach flags; see PDMDrvHlpAttach(). + */ +static int drvAudioDoAttachInternal(PDRVAUDIO pThis, uint32_t fFlags) +{ + Assert(pThis->pHostDrvAudio == NULL); /* No nested attaching. */ + + /* + * Attach driver below and query its connector interface. + */ + PPDMIBASE pDownBase; + int rc = PDMDrvHlpAttach(pThis->pDrvIns, fFlags, &pDownBase); + if (RT_SUCCESS(rc)) + { + pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO); + if (!pThis->pHostDrvAudio) + { + LogRel(("Audio: Failed to query interface for underlying host driver '%s'\n", pThis->szName)); + rc = PDMDRV_SET_ERROR(pThis->pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW, + N_("Host audio backend missing or invalid")); + } + } + + if (RT_SUCCESS(rc)) + { + /* + * If everything went well, initialize the lower driver. + */ + AssertPtr(pThis->pCFGMNode); + rc = drvAudioHostInit(pThis, pThis->pCFGMNode); + } + + LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc)); + return rc; +} + + +/********************************************************************/ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID)); + + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector); + + return NULL; +} + +/** + * Power Off notification. + * + * @param pDrvIns The driver instance data. + */ +static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns) +{ + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + + LogFlowFuncEnter(); + + if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */ + return; + + /* Just destroy the host stream on the backend side. + * The rest will either be destructed by the device emulation or + * in drvAudioDestruct(). */ + PPDMAUDIOSTREAM pStream; + RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node) + { + drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE); + drvAudioStreamDestroyInternalBackend(pThis, pStream); + } + + /* + * Last call for the driver below us. + * Let it know that we reached end of life. + */ + if (pThis->pHostDrvAudio->pfnShutdown) + pThis->pHostDrvAudio->pfnShutdown(pThis->pHostDrvAudio); + + pThis->pHostDrvAudio = NULL; + + LogFlowFuncLeave(); +} + +/** + * Constructs an audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags)); + + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + + RTListInit(&pThis->lstStreams); +#ifdef VBOX_WITH_AUDIO_CALLBACKS + RTListInit(&pThis->In.lstCB); + RTListInit(&pThis->Out.lstCB); +#endif + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + /* IBase. */ + pDrvIns->IBase.pfnQueryInterface = drvAudioQueryInterface; + /* IAudioConnector. */ + pThis->IAudioConnector.pfnEnable = drvAudioEnable; + pThis->IAudioConnector.pfnIsEnabled = drvAudioIsEnabled; + pThis->IAudioConnector.pfnGetConfig = drvAudioGetConfig; + pThis->IAudioConnector.pfnGetStatus = drvAudioGetStatus; + pThis->IAudioConnector.pfnStreamCreate = drvAudioStreamCreate; + pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy; + pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain; + pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease; + pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl; + pThis->IAudioConnector.pfnStreamRead = drvAudioStreamRead; + pThis->IAudioConnector.pfnStreamWrite = drvAudioStreamWrite; + pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate; + pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable; + pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable; + pThis->IAudioConnector.pfnStreamGetStatus = drvAudioStreamGetStatus; + pThis->IAudioConnector.pfnStreamSetVolume = drvAudioStreamSetVolume; + pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay; + pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture; +#ifdef VBOX_WITH_AUDIO_CALLBACKS + pThis->IAudioConnector.pfnRegisterCallbacks = drvAudioRegisterCallbacks; +#endif + + int rc = drvAudioInit(pDrvIns, pCfg); + if (RT_SUCCESS(rc)) + { +#ifdef VBOX_WITH_STATISTICS + PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsActive, "TotalStreamsActive", + STAMUNIT_COUNT, "Total active audio streams."); + PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsCreated, "TotalStreamsCreated", + STAMUNIT_COUNT, "Total created audio streams."); + PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesRead, "TotalFramesRead", + STAMUNIT_COUNT, "Total frames read by device emulation."); + PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesWritten, "TotalFramesWritten", + STAMUNIT_COUNT, "Total frames written by device emulation "); + PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedIn, "TotalFramesMixedIn", + STAMUNIT_COUNT, "Total input frames mixed."); + PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedOut, "TotalFramesMixedOut", + STAMUNIT_COUNT, "Total output frames mixed."); + PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostIn, "TotalFramesLostIn", + STAMUNIT_COUNT, "Total input frames lost."); + PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostOut, "TotalFramesLostOut", + STAMUNIT_COUNT, "Total output frames lost."); + PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesOut, "TotalFramesOut", + STAMUNIT_COUNT, "Total frames played by backend."); + PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesIn, "TotalFramesIn", + STAMUNIT_COUNT, "Total frames captured by backend."); + PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesRead, "TotalBytesRead", + STAMUNIT_BYTES, "Total bytes read."); + PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesWritten, "TotalBytesWritten", + STAMUNIT_BYTES, "Total bytes written."); + + PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayIn, "DelayIn", + STAMUNIT_NS_PER_CALL, "Profiling of input data processing."); + PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayOut, "DelayOut", + STAMUNIT_NS_PER_CALL, "Profiling of output data processing."); +#endif + } + + rc = drvAudioDoAttachInternal(pThis, fFlags); + if (RT_FAILURE(rc)) + { + /* No lower attached driver (yet)? Not a failure, might get attached later at runtime, just skip. */ + if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + rc = VINF_SUCCESS; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Destructs an audio driver instance. + * + * @copydoc FNPDMDRVDESTRUCT + */ +static DECLCALLBACK(void) drvAudioDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + + LogFlowFuncEnter(); + + int rc2; + + if (RTCritSectIsInitialized(&pThis->CritSect)) + { + rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc2); + } + + /* + * Note: No calls here to the driver below us anymore, + * as PDM already has destroyed it. + * If you need to call something from the host driver, + * do this in drvAudioPowerOff() instead. + */ + + /* Thus, NULL the pointer to the host audio driver first, + * so that routines like drvAudioStreamDestroyInternal() don't call the driver(s) below us anymore. */ + pThis->pHostDrvAudio = NULL; + + PPDMAUDIOSTREAM pStream, pStreamNext; + RTListForEachSafe(&pThis->lstStreams, pStream, pStreamNext, PDMAUDIOSTREAM, Node) + { + rc2 = drvAudioStreamUninitInternal(pThis, pStream); + if (RT_SUCCESS(rc2)) + { + RTListNodeRemove(&pStream->Node); + + drvAudioStreamFree(pStream); + pStream = NULL; + } + } + + /* Sanity. */ + Assert(RTListIsEmpty(&pThis->lstStreams)); + +#ifdef VBOX_WITH_AUDIO_CALLBACKS + /* + * Destroy callbacks, if any. + */ + PPDMAUDIOCBRECORD pCB, pCBNext; + RTListForEachSafe(&pThis->In.lstCB, pCB, pCBNext, PDMAUDIOCBRECORD, Node) + drvAudioCallbackDestroy(pCB); + + RTListForEachSafe(&pThis->Out.lstCB, pCB, pCBNext, PDMAUDIOCBRECORD, Node) + drvAudioCallbackDestroy(pCB); +#endif + + if (RTCritSectIsInitialized(&pThis->CritSect)) + { + rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + + rc2 = RTCritSectDelete(&pThis->CritSect); + AssertRC(rc2); + } + +#ifdef VBOX_WITH_STATISTICS + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalStreamsActive); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalStreamsCreated); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesRead); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesWritten); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesMixedIn); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesMixedOut); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesLostIn); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesLostOut); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesOut); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesIn); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalBytesRead); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalBytesWritten); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.DelayIn); + PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.DelayOut); +#endif + + LogFlowFuncLeave(); +} + +/** + * Suspend notification. + * + * @param pDrvIns The driver instance data. + */ +static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns) +{ + drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE); +} + +/** + * Resume notification. + * + * @param pDrvIns The driver instance data. + */ +static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns) +{ + drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME); +} + +/** + * Attach notification. + * + * @param pDrvIns The driver instance data. + * @param fFlags Attach flags. + */ +static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags) +{ + RT_NOREF(fFlags); + + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc2); + + LogFunc(("%s\n", pThis->szName)); + + int rc = drvAudioDoAttachInternal(pThis, fFlags); + + rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + + return rc; +} + +/** + * Detach notification. + * + * @param pDrvIns The driver instance data. + * @param fFlags Detach flags. + */ +static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags) +{ + RT_NOREF(fFlags); + + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc2); + + pThis->pHostDrvAudio = NULL; + + LogFunc(("%s\n", pThis->szName)); + + rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); +} + +/** + * Audio driver registration record. + */ +const PDMDRVREG g_DrvAUDIO = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "AUDIO", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Audio connector driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + UINT32_MAX, + /* cbInstance */ + sizeof(DRVAUDIO), + /* pfnConstruct */ + drvAudioConstruct, + /* pfnDestruct */ + drvAudioDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + drvAudioSuspend, + /* pfnResume */ + drvAudioResume, + /* pfnAttach */ + drvAudioAttach, + /* pfnDetach */ + drvAudioDetach, + /* pfnPowerOff */ + drvAudioPowerOff, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff --git a/src/VBox/Devices/Audio/DrvAudio.h b/src/VBox/Devices/Audio/DrvAudio.h new file mode 100644 index 00000000..cdabbdcb --- /dev/null +++ b/src/VBox/Devices/Audio/DrvAudio.h @@ -0,0 +1,286 @@ +/* $Id: DrvAudio.h $ */ +/** @file + * Intermediate audio driver header. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_DrvAudio_h +#define VBOX_INCLUDED_SRC_Audio_DrvAudio_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <limits.h> + +#include <iprt/circbuf.h> +#include <iprt/critsect.h> +#include <iprt/file.h> +#include <iprt/path.h> + +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/pdmaudioifs.h> + +typedef enum +{ + AUD_OPT_INT, + AUD_OPT_FMT, + AUD_OPT_STR, + AUD_OPT_BOOL +} audio_option_tag_e; + +typedef struct audio_option +{ + const char *name; + audio_option_tag_e tag; + void *valp; + const char *descr; + int *overridenp; + int overriden; +} audio_option; + +#ifdef VBOX_WITH_STATISTICS +/** + * Structure for keeping stream statistics for the + * statistic manager (STAM). + */ +typedef struct DRVAUDIOSTATS +{ + STAMCOUNTER TotalStreamsActive; + STAMCOUNTER TotalStreamsCreated; + STAMCOUNTER TotalFramesRead; + STAMCOUNTER TotalFramesWritten; + STAMCOUNTER TotalFramesMixedIn; + STAMCOUNTER TotalFramesMixedOut; + STAMCOUNTER TotalFramesLostIn; + STAMCOUNTER TotalFramesLostOut; + STAMCOUNTER TotalFramesOut; + STAMCOUNTER TotalFramesIn; + STAMCOUNTER TotalBytesRead; + STAMCOUNTER TotalBytesWritten; + /** How much delay (in ms) for input processing. */ + STAMPROFILEADV DelayIn; + /** How much delay (in ms) for output processing. */ + STAMPROFILEADV DelayOut; +} DRVAUDIOSTATS, *PDRVAUDIOSTATS; +#endif + +/** + * Audio driver configuration data, tweakable via CFGM. + */ +typedef struct DRVAUDIOCFG +{ + /** Configures the period size (in ms). + * This value reflects the time in between each hardware interrupt on the + * backend (host) side. */ + uint32_t uPeriodSizeMs; + /** Configures the (ring) buffer size (in ms). Often is a multiple of uPeriodMs. */ + uint32_t uBufferSizeMs; + /** Configures the pre-buffering size (in ms). + * Time needed in buffer before the stream becomes active (pre buffering). + * The bigger this value is, the more latency for the stream will occur. + * Set to 0 to disable pre-buffering completely. + * By default set to UINT32_MAX if not set to a custom value. */ + uint32_t uPreBufSizeMs; + /** The driver's debugging configuration. */ + struct + { + /** Whether audio debugging is enabled or not. */ + bool fEnabled; + /** Where to store the debugging files. + * Defaults to VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH if not set. */ + char szPathOut[RTPATH_MAX + 1]; + } Dbg; +} DRVAUDIOCFG, *PDRVAUDIOCFG; + +/** + * Audio driver instance data. + * + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVAUDIO +{ + /** Friendly name of the driver. */ + char szName[64]; + /** Critical section for serializing access. */ + RTCRITSECT CritSect; + /** Shutdown indicator. */ + bool fTerminate; + /** Our audio connector interface. */ + PDMIAUDIOCONNECTOR IAudioConnector; + /** Pointer to the driver instance. */ + PPDMDRVINS pDrvIns; + /** Pointer to audio driver below us. */ + PPDMIHOSTAUDIO pHostDrvAudio; + /** Pointer to CFGM configuration node of this driver. */ + PCFGMNODE pCFGMNode; + /** List of audio streams. */ + RTLISTANCHOR lstStreams; +#ifdef VBOX_WITH_AUDIO_ENUM + /** Flag indicating to perform an (re-)enumeration of the host audio devices. */ + bool fEnumerateDevices; +#endif + /** Audio configuration settings retrieved from the backend. */ + PDMAUDIOBACKENDCFG BackendCfg; +#ifdef VBOX_WITH_STATISTICS + /** Statistics for the statistics manager (STAM). */ + DRVAUDIOSTATS Stats; +#endif + struct + { + /** Whether this driver's input streams are enabled or not. + * This flag overrides all the attached stream statuses. */ + bool fEnabled; + /** Max. number of free input streams. + * UINT32_MAX for unlimited streams. */ + uint32_t cStreamsFree; +#ifdef VBOX_WITH_AUDIO_CALLBACKS + RTLISTANCHOR lstCB; +#endif + /** The driver's input confguration (tweakable via CFGM). */ + DRVAUDIOCFG Cfg; + } In; + struct + { + /** Whether this driver's output streams are enabled or not. + * This flag overrides all the attached stream statuses. */ + bool fEnabled; + /** Max. number of free output streams. + * UINT32_MAX for unlimited streams. */ + uint32_t cStreamsFree; +#ifdef VBOX_WITH_AUDIO_CALLBACKS + RTLISTANCHOR lstCB; +#endif + /** The driver's output confguration (tweakable via CFGM). */ + DRVAUDIOCFG Cfg; + } Out; +} DRVAUDIO, *PDRVAUDIO; + +/** Makes a PDRVAUDIO out of a PPDMIAUDIOCONNECTOR. */ +#define PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface) \ + ( (PDRVAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVAUDIO, IAudioConnector)) ) + +/** @name Audio format helper methods. + * @{ */ +const char *DrvAudioHlpAudDirToStr(PDMAUDIODIR enmDir); +const char *DrvAudioHlpAudFmtToStr(PDMAUDIOFMT enmFmt); +bool DrvAudioHlpAudFmtIsSigned(PDMAUDIOFMT enmFmt); +uint8_t DrvAudioHlpAudFmtToBits(PDMAUDIOFMT enmFmt); +/** @} */ + +/** @name Audio calculation helper methods. + * @{ */ +void DrvAudioHlpClearBuf(const PPDMAUDIOPCMPROPS pPCMProps, void *pvBuf, size_t cbBuf, uint32_t cFrames); +uint32_t DrvAudioHlpCalcBitrate(uint8_t cBits, uint32_t uHz, uint8_t cChannels); +uint32_t DrvAudioHlpCalcBitrate(const PPDMAUDIOPCMPROPS pProps); +uint32_t DrvAudioHlpBytesAlign(uint32_t cbSize, const PPDMAUDIOPCMPROPS pProps); +bool DrvAudioHlpBytesIsAligned(uint32_t cbSize, const PPDMAUDIOPCMPROPS pProps); +uint32_t DrvAudioHlpBytesToFrames(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps); +uint64_t DrvAudioHlpBytesToMilli(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps); +uint64_t DrvAudioHlpBytesToMicro(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps); +uint64_t DrvAudioHlpBytesToNano(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps); +uint32_t DrvAudioHlpFramesToBytes(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps); +uint64_t DrvAudioHlpFramesToMilli(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps); +uint64_t DrvAudioHlpFramesToNano(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps); +uint32_t DrvAudioHlpMilliToBytes(uint64_t uMs, const PPDMAUDIOPCMPROPS pProps); +uint32_t DrvAudioHlpNanoToBytes(uint64_t uNs, const PPDMAUDIOPCMPROPS pProps); +uint32_t DrvAudioHlpMilliToFrames(uint64_t uMs, const PPDMAUDIOPCMPROPS pProps); +uint32_t DrvAudioHlpNanoToFrames(uint64_t uNs, const PPDMAUDIOPCMPROPS pProps); +/** @} */ + +/** @name Audio PCM properties helper methods. + * @{ */ +bool DrvAudioHlpPCMPropsAreEqual(const PPDMAUDIOPCMPROPS pPCMProps1, const PPDMAUDIOPCMPROPS pPCMProps2); +bool DrvAudioHlpPCMPropsAreEqual(const PPDMAUDIOPCMPROPS pPCMProps, const PPDMAUDIOSTREAMCFG pCfg); +bool DrvAudioHlpPCMPropsAreValid(const PPDMAUDIOPCMPROPS pProps); +uint32_t DrvAudioHlpPCMPropsBytesPerFrame(const PPDMAUDIOPCMPROPS pProps); +void DrvAudioHlpPCMPropsPrint(const PPDMAUDIOPCMPROPS pProps); +int DrvAudioHlpPCMPropsToStreamCfg(const PPDMAUDIOPCMPROPS pPCMProps, PPDMAUDIOSTREAMCFG pCfg); +/** @} */ + +/** @name Audio configuration helper methods. + * @{ */ +void DrvAudioHlpStreamCfgInit(PPDMAUDIOSTREAMCFG pCfg); +bool DrvAudioHlpStreamCfgIsValid(const PPDMAUDIOSTREAMCFG pCfg); +int DrvAudioHlpStreamCfgCopy(PPDMAUDIOSTREAMCFG pDstCfg, const PPDMAUDIOSTREAMCFG pSrcCfg); +PPDMAUDIOSTREAMCFG DrvAudioHlpStreamCfgDup(const PPDMAUDIOSTREAMCFG pCfg); +void DrvAudioHlpStreamCfgFree(PPDMAUDIOSTREAMCFG pCfg); +void DrvAudioHlpStreamCfgPrint(const PPDMAUDIOSTREAMCFG pCfg); +/** @} */ + +/** @name Audio stream command helper methods. + * @{ */ +const char *DrvAudioHlpStreamCmdToStr(PDMAUDIOSTREAMCMD enmCmd); +/** @} */ + +/** @name Audio stream status helper methods. + * @{ */ +bool DrvAudioHlpStreamStatusCanRead(PDMAUDIOSTREAMSTS enmStatus); +bool DrvAudioHlpStreamStatusCanWrite(PDMAUDIOSTREAMSTS enmStatus); +bool DrvAudioHlpStreamStatusIsReady(PDMAUDIOSTREAMSTS enmStatus); +/** @} */ + +/** @name Audio file (name) helper methods. + * @{ */ +int DrvAudioHlpFileNameSanitize(char *pszPath, size_t cbPath); +int DrvAudioHlpFileNameGet(char *pszFile, size_t cchFile, const char *pszPath, const char *pszName, uint32_t uInstance, PDMAUDIOFILETYPE enmType, PDMAUDIOFILENAMEFLAGS fFlags); +/** @} */ + +/** @name Audio device methods. + * @{ */ +PPDMAUDIODEVICE DrvAudioHlpDeviceAlloc(size_t cbData); +void DrvAudioHlpDeviceFree(PPDMAUDIODEVICE pDev); +PPDMAUDIODEVICE DrvAudioHlpDeviceDup(const PPDMAUDIODEVICE pDev, bool fCopyUserData); +/** @} */ + +/** @name Audio device enumartion methods. + * @{ */ +int DrvAudioHlpDeviceEnumInit(PPDMAUDIODEVICEENUM pDevEnm); +void DrvAudioHlpDeviceEnumFree(PPDMAUDIODEVICEENUM pDevEnm); +int DrvAudioHlpDeviceEnumAdd(PPDMAUDIODEVICEENUM pDevEnm, PPDMAUDIODEVICE pDev); +int DrvAudioHlpDeviceEnumCopyEx(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm, PDMAUDIODIR enmUsage); +int DrvAudioHlpDeviceEnumCopy(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm); +PPDMAUDIODEVICEENUM DrvAudioHlpDeviceEnumDup(const PPDMAUDIODEVICEENUM pDevEnm); +int DrvAudioHlpDeviceEnumCopy(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm); +int DrvAudioHlpDeviceEnumCopyEx(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm, PDMAUDIODIR enmUsage, bool fCopyUserData); +PPDMAUDIODEVICE DrvAudioHlpDeviceEnumGetDefaultDevice(const PPDMAUDIODEVICEENUM pDevEnm, PDMAUDIODIR enmDir); +uint16_t DrvAudioHlpDeviceEnumGetDeviceCount(const PPDMAUDIODEVICEENUM pDevEnm, PDMAUDIODIR enmUsage); +void DrvAudioHlpDeviceEnumPrint(const char *pszDesc, const PPDMAUDIODEVICEENUM pDevEnm); +/** @} */ + +/** @name Audio string-ify methods. + * @{ */ +const char *DrvAudioHlpAudMixerCtlToStr(PDMAUDIOMIXERCTL enmMixerCtl); +const char *DrvAudioHlpPlaybackDstToStr(const PDMAUDIOPLAYBACKDEST enmPlaybackDst); +const char *DrvAudioHlpRecSrcToStr(const PDMAUDIORECSOURCE enmRecSource); +PDMAUDIOFMT DrvAudioHlpStrToAudFmt(const char *pszFmt); +char *DrvAudioHlpAudDevFlagsToStrA(PDMAUDIODEVFLAG fFlags); +/** @} */ + +/** @name Audio file methods. + * @{ */ +int DrvAudioHlpFileCreate(PDMAUDIOFILETYPE enmType, const char *pszFile, PDMAUDIOFILEFLAGS fFlags, PPDMAUDIOFILE *ppFile); +void DrvAudioHlpFileDestroy(PPDMAUDIOFILE pFile); +int DrvAudioHlpFileOpen(PPDMAUDIOFILE pFile, uint32_t fOpen, const PPDMAUDIOPCMPROPS pProps); +int DrvAudioHlpFileClose(PPDMAUDIOFILE pFile); +int DrvAudioHlpFileDelete(PPDMAUDIOFILE pFile); +size_t DrvAudioHlpFileGetDataSize(PPDMAUDIOFILE pFile); +bool DrvAudioHlpFileIsOpen(PPDMAUDIOFILE pFile); +int DrvAudioHlpFileWrite(PPDMAUDIOFILE pFile, const void *pvBuf, size_t cbBuf, uint32_t fFlags); +/** @} */ + +#define AUDIO_MAKE_FOURCC(c0, c1, c2, c3) RT_H2LE_U32_C(RT_MAKE_U32_FROM_U8(c0, c1, c2, c3)) + +#endif /* !VBOX_INCLUDED_SRC_Audio_DrvAudio_h */ + diff --git a/src/VBox/Devices/Audio/DrvAudioCommon.cpp b/src/VBox/Devices/Audio/DrvAudioCommon.cpp new file mode 100644 index 00000000..7f2a1c78 --- /dev/null +++ b/src/VBox/Devices/Audio/DrvAudioCommon.cpp @@ -0,0 +1,1927 @@ +/* $Id: DrvAudioCommon.cpp $ */ +/** @file + * Intermedia audio driver, common routines. + * + * These are also used in the drivers which are bound to Main, e.g. the VRDE + * or the video audio recording drivers. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/alloc.h> +#include <iprt/asm-math.h> +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/string.h> +#include <iprt/uuid.h> + +#define LOG_GROUP LOG_GROUP_DRV_AUDIO +#include <VBox/log.h> + +#include <VBox/err.h> +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/mm.h> + +#include <ctype.h> +#include <stdlib.h> + +#include "DrvAudio.h" +#include "AudioMixBuffer.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Structure for building up a .WAV file header. + */ +typedef struct AUDIOWAVFILEHDR +{ + uint32_t u32RIFF; + uint32_t u32Size; + uint32_t u32WAVE; + + uint32_t u32Fmt; + uint32_t u32Size1; + uint16_t u16AudioFormat; + uint16_t u16NumChannels; + uint32_t u32SampleRate; + uint32_t u32ByteRate; + uint16_t u16BlockAlign; + uint16_t u16BitsPerSample; + + uint32_t u32ID2; + uint32_t u32Size2; +} AUDIOWAVFILEHDR, *PAUDIOWAVFILEHDR; +AssertCompileSize(AUDIOWAVFILEHDR, 11*4); + +/** + * Structure for keeeping the internal .WAV file data + */ +typedef struct AUDIOWAVFILEDATA +{ + /** The file header/footer. */ + AUDIOWAVFILEHDR Hdr; +} AUDIOWAVFILEDATA, *PAUDIOWAVFILEDATA; + + + + +/** + * Retrieves the matching PDMAUDIOFMT for given bits + signing flag. + * + * @return IPRT status code. + * @return PDMAUDIOFMT Resulting audio format or PDMAUDIOFMT_INVALID if invalid. + * @param cBits Bits to retrieve audio format for. + * @param fSigned Signed flag for bits to retrieve audio format for. + */ +PDMAUDIOFMT DrvAudioAudFmtBitsToAudFmt(uint8_t cBits, bool fSigned) +{ + if (fSigned) + { + switch (cBits) + { + case 8: return PDMAUDIOFMT_S8; + case 16: return PDMAUDIOFMT_S16; + case 32: return PDMAUDIOFMT_S32; + default: break; + } + } + else + { + switch (cBits) + { + case 8: return PDMAUDIOFMT_U8; + case 16: return PDMAUDIOFMT_U16; + case 32: return PDMAUDIOFMT_U32; + default: break; + } + } + + AssertMsgFailed(("Bogus audio bits %RU8\n", cBits)); + return PDMAUDIOFMT_INVALID; +} + +/** + * Clears a sample buffer by the given amount of audio frames with silence (according to the format + * given by the PCM properties). + * + * @param pPCMProps PCM properties to use for the buffer to clear. + * @param pvBuf Buffer to clear. + * @param cbBuf Size (in bytes) of the buffer. + * @param cFrames Number of audio frames to clear in the buffer. + */ +void DrvAudioHlpClearBuf(const PPDMAUDIOPCMPROPS pPCMProps, void *pvBuf, size_t cbBuf, uint32_t cFrames) +{ + AssertPtrReturnVoid(pPCMProps); + AssertPtrReturnVoid(pvBuf); + + if (!cbBuf || !cFrames) + return; + + Assert(pPCMProps->cBytes); + size_t cbToClear = DrvAudioHlpFramesToBytes(cFrames, pPCMProps); + Assert(cbBuf >= cbToClear); + + if (cbBuf < cbToClear) + cbToClear = cbBuf; + + Log2Func(("pPCMProps=%p, pvBuf=%p, cFrames=%RU32, fSigned=%RTbool, cBytes=%RU8\n", + pPCMProps, pvBuf, cFrames, pPCMProps->fSigned, pPCMProps->cBytes)); + + Assert(pPCMProps->fSwapEndian == false); /** @todo Swapping Endianness is not supported yet. */ + + if (pPCMProps->fSigned) + { + RT_BZERO(pvBuf, cbToClear); + } + else /* Unsigned formats. */ + { + switch (pPCMProps->cBytes) + { + case 1: /* 8 bit */ + { + memset(pvBuf, 0x80, cbToClear); + break; + } + + case 2: /* 16 bit */ + { + uint16_t *p = (uint16_t *)pvBuf; + uint16_t s = 0x0080; + + for (uint32_t i = 0; i < DrvAudioHlpBytesToFrames((uint32_t)cbToClear, pPCMProps); i++) + p[i] = s; + + break; + } + + /** @todo Add 24 bit? */ + + case 4: /* 32 bit */ + { + uint32_t *p = (uint32_t *)pvBuf; + uint32_t s = 0x00000080; + + for (uint32_t i = 0; i < DrvAudioHlpBytesToFrames((uint32_t)cbToClear, pPCMProps); i++) + p[i] = s; + + break; + } + + default: + { + AssertMsgFailed(("Invalid bytes per sample: %RU8\n", pPCMProps->cBytes)); + break; + } + } + } +} + +/** + * Returns an unique file name for this given audio connector instance. + * + * @return Allocated file name. Must be free'd using RTStrFree(). + * @param uInstance Driver / device instance. + * @param pszPath Path name of the file to delete. The path must exist. + * @param pszSuffix File name suffix to use. + */ +char *DrvAudioDbgGetFileNameA(uint8_t uInstance, const char *pszPath, const char *pszSuffix) +{ + char szFileName[64]; + RTStrPrintf(szFileName, sizeof(szFileName), "drvAudio%RU8-%s", uInstance, pszSuffix); + + char szFilePath[RTPATH_MAX]; + int rc2 = RTStrCopy(szFilePath, sizeof(szFilePath), pszPath); + AssertRC(rc2); + rc2 = RTPathAppend(szFilePath, sizeof(szFilePath), szFileName); + AssertRC(rc2); + + return RTStrDup(szFilePath); +} + +/** + * Allocates an audio device. + * + * @returns Newly allocated audio device, or NULL if failed. + * @param cbData How much additional data (in bytes) should be allocated to provide + * a (backend) specific area to store additional data. + * Optional, can be 0. + */ +PPDMAUDIODEVICE DrvAudioHlpDeviceAlloc(size_t cbData) +{ + PPDMAUDIODEVICE pDev = (PPDMAUDIODEVICE)RTMemAllocZ(sizeof(PDMAUDIODEVICE)); + if (!pDev) + return NULL; + + if (cbData) + { + pDev->pvData = RTMemAllocZ(cbData); + if (!pDev->pvData) + { + RTMemFree(pDev); + return NULL; + } + } + + pDev->cbData = cbData; + + pDev->cMaxInputChannels = 0; + pDev->cMaxOutputChannels = 0; + + return pDev; +} + +/** + * Frees an audio device. + * + * @param pDev Device to free. + */ +void DrvAudioHlpDeviceFree(PPDMAUDIODEVICE pDev) +{ + if (!pDev) + return; + + Assert(pDev->cRefCount == 0); + + if (pDev->pvData) + { + Assert(pDev->cbData); + + RTMemFree(pDev->pvData); + pDev->pvData = NULL; + } + + RTMemFree(pDev); + pDev = NULL; +} + +/** + * Duplicates an audio device entry. + * + * @returns Duplicated audio device entry on success, or NULL on failure. + * @param pDev Audio device entry to duplicate. + * @param fCopyUserData Whether to also copy the user data portion or not. + */ +PPDMAUDIODEVICE DrvAudioHlpDeviceDup(const PPDMAUDIODEVICE pDev, bool fCopyUserData) +{ + AssertPtrReturn(pDev, NULL); + + PPDMAUDIODEVICE pDevDup = DrvAudioHlpDeviceAlloc(fCopyUserData ? pDev->cbData : 0); + if (pDevDup) + { + memcpy(pDevDup, pDev, sizeof(PDMAUDIODEVICE)); + + if ( fCopyUserData + && pDevDup->cbData) + { + memcpy(pDevDup->pvData, pDev->pvData, pDevDup->cbData); + } + else + { + pDevDup->cbData = 0; + pDevDup->pvData = NULL; + } + } + + return pDevDup; +} + +/** + * Initializes an audio device enumeration structure. + * + * @returns IPRT status code. + * @param pDevEnm Device enumeration to initialize. + */ +int DrvAudioHlpDeviceEnumInit(PPDMAUDIODEVICEENUM pDevEnm) +{ + AssertPtrReturn(pDevEnm, VERR_INVALID_POINTER); + + RTListInit(&pDevEnm->lstDevices); + pDevEnm->cDevices = 0; + + return VINF_SUCCESS; +} + +/** + * Frees audio device enumeration data. + * + * @param pDevEnm Device enumeration to destroy. + */ +void DrvAudioHlpDeviceEnumFree(PPDMAUDIODEVICEENUM pDevEnm) +{ + if (!pDevEnm) + return; + + PPDMAUDIODEVICE pDev, pDevNext; + RTListForEachSafe(&pDevEnm->lstDevices, pDev, pDevNext, PDMAUDIODEVICE, Node) + { + RTListNodeRemove(&pDev->Node); + + DrvAudioHlpDeviceFree(pDev); + + pDevEnm->cDevices--; + } + + /* Sanity. */ + Assert(RTListIsEmpty(&pDevEnm->lstDevices)); + Assert(pDevEnm->cDevices == 0); +} + +/** + * Adds an audio device to a device enumeration. + * + * @return IPRT status code. + * @param pDevEnm Device enumeration to add device to. + * @param pDev Device to add. The pointer will be owned by the device enumeration then. + */ +int DrvAudioHlpDeviceEnumAdd(PPDMAUDIODEVICEENUM pDevEnm, PPDMAUDIODEVICE pDev) +{ + AssertPtrReturn(pDevEnm, VERR_INVALID_POINTER); + AssertPtrReturn(pDev, VERR_INVALID_POINTER); + + RTListAppend(&pDevEnm->lstDevices, &pDev->Node); + pDevEnm->cDevices++; + + return VINF_SUCCESS; +} + +/** + * Duplicates a device enumeration. + * + * @returns Duplicated device enumeration, or NULL on failure. + * Must be free'd with DrvAudioHlpDeviceEnumFree(). + * @param pDevEnm Device enumeration to duplicate. + */ +PPDMAUDIODEVICEENUM DrvAudioHlpDeviceEnumDup(const PPDMAUDIODEVICEENUM pDevEnm) +{ + AssertPtrReturn(pDevEnm, NULL); + + PPDMAUDIODEVICEENUM pDevEnmDup = (PPDMAUDIODEVICEENUM)RTMemAlloc(sizeof(PDMAUDIODEVICEENUM)); + if (!pDevEnmDup) + return NULL; + + int rc2 = DrvAudioHlpDeviceEnumInit(pDevEnmDup); + AssertRC(rc2); + + PPDMAUDIODEVICE pDev; + RTListForEach(&pDevEnm->lstDevices, pDev, PDMAUDIODEVICE, Node) + { + PPDMAUDIODEVICE pDevDup = DrvAudioHlpDeviceDup(pDev, true /* fCopyUserData */); + if (!pDevDup) + { + rc2 = VERR_NO_MEMORY; + break; + } + + rc2 = DrvAudioHlpDeviceEnumAdd(pDevEnmDup, pDevDup); + if (RT_FAILURE(rc2)) + { + DrvAudioHlpDeviceFree(pDevDup); + break; + } + } + + if (RT_FAILURE(rc2)) + { + DrvAudioHlpDeviceEnumFree(pDevEnmDup); + pDevEnmDup = NULL; + } + + return pDevEnmDup; +} + +/** + * Copies device enumeration entries from the source to the destination enumeration. + * + * @returns IPRT status code. + * @param pDstDevEnm Destination enumeration to store enumeration entries into. + * @param pSrcDevEnm Source enumeration to use. + * @param enmUsage Which entries to copy. Specify PDMAUDIODIR_ANY to copy all entries. + * @param fCopyUserData Whether to also copy the user data portion or not. + */ +int DrvAudioHlpDeviceEnumCopyEx(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm, + PDMAUDIODIR enmUsage, bool fCopyUserData) +{ + AssertPtrReturn(pDstDevEnm, VERR_INVALID_POINTER); + AssertPtrReturn(pSrcDevEnm, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + PPDMAUDIODEVICE pSrcDev; + RTListForEach(&pSrcDevEnm->lstDevices, pSrcDev, PDMAUDIODEVICE, Node) + { + if ( enmUsage != PDMAUDIODIR_ANY + && enmUsage != pSrcDev->enmUsage) + { + continue; + } + + PPDMAUDIODEVICE pDstDev = DrvAudioHlpDeviceDup(pSrcDev, fCopyUserData); + if (!pDstDev) + { + rc = VERR_NO_MEMORY; + break; + } + + rc = DrvAudioHlpDeviceEnumAdd(pDstDevEnm, pDstDev); + if (RT_FAILURE(rc)) + break; + } + + return rc; +} + +/** + * Copies all device enumeration entries from the source to the destination enumeration. + * + * Note: Does *not* copy the user-specific data assigned to a device enumeration entry. + * To do so, use DrvAudioHlpDeviceEnumCopyEx(). + * + * @returns IPRT status code. + * @param pDstDevEnm Destination enumeration to store enumeration entries into. + * @param pSrcDevEnm Source enumeration to use. + */ +int DrvAudioHlpDeviceEnumCopy(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm) +{ + return DrvAudioHlpDeviceEnumCopyEx(pDstDevEnm, pSrcDevEnm, PDMAUDIODIR_ANY, false /* fCopyUserData */); +} + +/** + * Returns the default device of a given device enumeration. + * This assumes that only one default device per usage is set. + * + * @returns Default device if found, or NULL if none found. + * @param pDevEnm Device enumeration to get default device for. + * @param enmUsage Usage to get default device for. + */ +PPDMAUDIODEVICE DrvAudioHlpDeviceEnumGetDefaultDevice(const PPDMAUDIODEVICEENUM pDevEnm, PDMAUDIODIR enmUsage) +{ + AssertPtrReturn(pDevEnm, NULL); + + PPDMAUDIODEVICE pDev; + RTListForEach(&pDevEnm->lstDevices, pDev, PDMAUDIODEVICE, Node) + { + if (enmUsage != PDMAUDIODIR_ANY) + { + if (enmUsage != pDev->enmUsage) /* Wrong usage? Skip. */ + continue; + } + + if (pDev->fFlags & PDMAUDIODEV_FLAGS_DEFAULT) + return pDev; + } + + return NULL; +} + +/** + * Returns the number of enumerated devices of a given device enumeration. + * + * @returns Number of devices if found, or 0 if none found. + * @param pDevEnm Device enumeration to get default device for. + * @param enmUsage Usage to get default device for. + */ +uint16_t DrvAudioHlpDeviceEnumGetDeviceCount(const PPDMAUDIODEVICEENUM pDevEnm, PDMAUDIODIR enmUsage) +{ + AssertPtrReturn(pDevEnm, 0); + + if (enmUsage == PDMAUDIODIR_ANY) + return pDevEnm->cDevices; + + uint32_t cDevs = 0; + + PPDMAUDIODEVICE pDev; + RTListForEach(&pDevEnm->lstDevices, pDev, PDMAUDIODEVICE, Node) + { + if (enmUsage == pDev->enmUsage) + cDevs++; + } + + return cDevs; +} + +/** + * Logs an audio device enumeration. + * + * @param pszDesc Logging description. + * @param pDevEnm Device enumeration to log. + */ +void DrvAudioHlpDeviceEnumPrint(const char *pszDesc, const PPDMAUDIODEVICEENUM pDevEnm) +{ + AssertPtrReturnVoid(pszDesc); + AssertPtrReturnVoid(pDevEnm); + + LogFunc(("%s: %RU16 devices\n", pszDesc, pDevEnm->cDevices)); + + PPDMAUDIODEVICE pDev; + RTListForEach(&pDevEnm->lstDevices, pDev, PDMAUDIODEVICE, Node) + { + char *pszFlags = DrvAudioHlpAudDevFlagsToStrA(pDev->fFlags); + + LogFunc(("Device '%s':\n", pDev->szName)); + LogFunc(("\tUsage = %s\n", DrvAudioHlpAudDirToStr(pDev->enmUsage))); + LogFunc(("\tFlags = %s\n", pszFlags ? pszFlags : "<NONE>")); + LogFunc(("\tInput channels = %RU8\n", pDev->cMaxInputChannels)); + LogFunc(("\tOutput channels = %RU8\n", pDev->cMaxOutputChannels)); + LogFunc(("\tData = %p (%zu bytes)\n", pDev->pvData, pDev->cbData)); + + if (pszFlags) + RTStrFree(pszFlags); + } +} + +/** + * Converts an audio direction to a string. + * + * @returns Stringified audio direction, or "Unknown", if not found. + * @param enmDir Audio direction to convert. + */ +const char *DrvAudioHlpAudDirToStr(PDMAUDIODIR enmDir) +{ + switch (enmDir) + { + case PDMAUDIODIR_UNKNOWN: return "Unknown"; + case PDMAUDIODIR_IN: return "Input"; + case PDMAUDIODIR_OUT: return "Output"; + case PDMAUDIODIR_ANY: return "Duplex"; + default: break; + } + + AssertMsgFailed(("Invalid audio direction %ld\n", enmDir)); + return "Unknown"; +} + +/** + * Converts an audio mixer control to a string. + * + * @returns Stringified audio mixer control or "Unknown", if not found. + * @param enmMixerCtl Audio mixer control to convert. + */ +const char *DrvAudioHlpAudMixerCtlToStr(PDMAUDIOMIXERCTL enmMixerCtl) +{ + switch (enmMixerCtl) + { + case PDMAUDIOMIXERCTL_VOLUME_MASTER: return "Master Volume"; + case PDMAUDIOMIXERCTL_FRONT: return "Front"; + case PDMAUDIOMIXERCTL_CENTER_LFE: return "Center / LFE"; + case PDMAUDIOMIXERCTL_REAR: return "Rear"; + case PDMAUDIOMIXERCTL_LINE_IN: return "Line-In"; + case PDMAUDIOMIXERCTL_MIC_IN: return "Microphone-In"; + default: break; + } + + AssertMsgFailed(("Invalid mixer control %ld\n", enmMixerCtl)); + return "Unknown"; +} + +/** + * Converts an audio device flags to a string. + * + * @returns Stringified audio flags. Must be free'd with RTStrFree(). + * NULL if no flags set. + * @param fFlags Audio flags to convert. + */ +char *DrvAudioHlpAudDevFlagsToStrA(PDMAUDIODEVFLAG fFlags) +{ +#define APPEND_FLAG_TO_STR(_aFlag) \ + if (fFlags & PDMAUDIODEV_FLAGS_##_aFlag) \ + { \ + if (pszFlags) \ + { \ + rc2 = RTStrAAppend(&pszFlags, " "); \ + if (RT_FAILURE(rc2)) \ + break; \ + } \ + \ + rc2 = RTStrAAppend(&pszFlags, #_aFlag); \ + if (RT_FAILURE(rc2)) \ + break; \ + } \ + + char *pszFlags = NULL; + int rc2 = VINF_SUCCESS; + + do + { + APPEND_FLAG_TO_STR(DEFAULT); + APPEND_FLAG_TO_STR(HOTPLUG); + APPEND_FLAG_TO_STR(BUGGY); + APPEND_FLAG_TO_STR(IGNORE); + APPEND_FLAG_TO_STR(LOCKED); + APPEND_FLAG_TO_STR(DEAD); + + } while (0); + + if (!pszFlags) + rc2 = RTStrAAppend(&pszFlags, "NONE"); + + if ( RT_FAILURE(rc2) + && pszFlags) + { + RTStrFree(pszFlags); + pszFlags = NULL; + } + +#undef APPEND_FLAG_TO_STR + + return pszFlags; +} + +/** + * Converts a playback destination enumeration to a string. + * + * @returns Stringified playback destination, or "Unknown", if not found. + * @param enmPlaybackDst Playback destination to convert. + */ +const char *DrvAudioHlpPlaybackDstToStr(const PDMAUDIOPLAYBACKDEST enmPlaybackDst) +{ + switch (enmPlaybackDst) + { + case PDMAUDIOPLAYBACKDEST_UNKNOWN: return "Unknown"; + case PDMAUDIOPLAYBACKDEST_FRONT: return "Front"; + case PDMAUDIOPLAYBACKDEST_CENTER_LFE: return "Center / LFE"; + case PDMAUDIOPLAYBACKDEST_REAR: return "Rear"; + default: + break; + } + + AssertMsgFailed(("Invalid playback destination %ld\n", enmPlaybackDst)); + return "Unknown"; +} + +/** + * Converts a recording source enumeration to a string. + * + * @returns Stringified recording source, or "Unknown", if not found. + * @param enmRecSrc Recording source to convert. + */ +const char *DrvAudioHlpRecSrcToStr(const PDMAUDIORECSOURCE enmRecSrc) +{ + switch (enmRecSrc) + { + case PDMAUDIORECSOURCE_UNKNOWN: return "Unknown"; + case PDMAUDIORECSOURCE_MIC: return "Microphone In"; + case PDMAUDIORECSOURCE_CD: return "CD"; + case PDMAUDIORECSOURCE_VIDEO: return "Video"; + case PDMAUDIORECSOURCE_AUX: return "AUX"; + case PDMAUDIORECSOURCE_LINE: return "Line In"; + case PDMAUDIORECSOURCE_PHONE: return "Phone"; + default: + break; + } + + AssertMsgFailed(("Invalid recording source %ld\n", enmRecSrc)); + return "Unknown"; +} + +/** + * Returns wether the given audio format has signed bits or not. + * + * @return IPRT status code. + * @return bool @c true for signed bits, @c false for unsigned. + * @param enmFmt Audio format to retrieve value for. + */ +bool DrvAudioHlpAudFmtIsSigned(PDMAUDIOFMT enmFmt) +{ + switch (enmFmt) + { + case PDMAUDIOFMT_S8: + case PDMAUDIOFMT_S16: + case PDMAUDIOFMT_S32: + return true; + + case PDMAUDIOFMT_U8: + case PDMAUDIOFMT_U16: + case PDMAUDIOFMT_U32: + return false; + + default: + break; + } + + AssertMsgFailed(("Bogus audio format %ld\n", enmFmt)); + return false; +} + +/** + * Returns the bits of a given audio format. + * + * @return IPRT status code. + * @return uint8_t Bits of audio format. + * @param enmFmt Audio format to retrieve value for. + */ +uint8_t DrvAudioHlpAudFmtToBits(PDMAUDIOFMT enmFmt) +{ + switch (enmFmt) + { + case PDMAUDIOFMT_S8: + case PDMAUDIOFMT_U8: + return 8; + + case PDMAUDIOFMT_U16: + case PDMAUDIOFMT_S16: + return 16; + + case PDMAUDIOFMT_U32: + case PDMAUDIOFMT_S32: + return 32; + + default: + break; + } + + AssertMsgFailed(("Bogus audio format %ld\n", enmFmt)); + return 0; +} + +/** + * Converts an audio format to a string. + * + * @returns Stringified audio format, or "Unknown", if not found. + * @param enmFmt Audio format to convert. + */ +const char *DrvAudioHlpAudFmtToStr(PDMAUDIOFMT enmFmt) +{ + switch (enmFmt) + { + case PDMAUDIOFMT_U8: + return "U8"; + + case PDMAUDIOFMT_U16: + return "U16"; + + case PDMAUDIOFMT_U32: + return "U32"; + + case PDMAUDIOFMT_S8: + return "S8"; + + case PDMAUDIOFMT_S16: + return "S16"; + + case PDMAUDIOFMT_S32: + return "S32"; + + default: + break; + } + + AssertMsgFailed(("Bogus audio format %ld\n", enmFmt)); + return "Unknown"; +} + +/** + * Converts a given string to an audio format. + * + * @returns Audio format for the given string, or PDMAUDIOFMT_INVALID if not found. + * @param pszFmt String to convert to an audio format. + */ +PDMAUDIOFMT DrvAudioHlpStrToAudFmt(const char *pszFmt) +{ + AssertPtrReturn(pszFmt, PDMAUDIOFMT_INVALID); + + if (!RTStrICmp(pszFmt, "u8")) + return PDMAUDIOFMT_U8; + else if (!RTStrICmp(pszFmt, "u16")) + return PDMAUDIOFMT_U16; + else if (!RTStrICmp(pszFmt, "u32")) + return PDMAUDIOFMT_U32; + else if (!RTStrICmp(pszFmt, "s8")) + return PDMAUDIOFMT_S8; + else if (!RTStrICmp(pszFmt, "s16")) + return PDMAUDIOFMT_S16; + else if (!RTStrICmp(pszFmt, "s32")) + return PDMAUDIOFMT_S32; + + AssertMsgFailed(("Invalid audio format '%s'\n", pszFmt)); + return PDMAUDIOFMT_INVALID; +} + +/** + * Checks whether two given PCM properties are equal. + * + * @returns @c true if equal, @c false if not. + * @param pProps1 First properties to compare. + * @param pProps2 Second properties to compare. + */ +bool DrvAudioHlpPCMPropsAreEqual(const PPDMAUDIOPCMPROPS pProps1, const PPDMAUDIOPCMPROPS pProps2) +{ + AssertPtrReturn(pProps1, false); + AssertPtrReturn(pProps2, false); + + if (pProps1 == pProps2) /* If the pointers match, take a shortcut. */ + return true; + + return pProps1->uHz == pProps2->uHz + && pProps1->cChannels == pProps2->cChannels + && pProps1->cBytes == pProps2->cBytes + && pProps1->fSigned == pProps2->fSigned + && pProps1->fSwapEndian == pProps2->fSwapEndian; +} + +/** + * Checks whether given PCM properties are valid or not. + * + * Returns @c true if properties are valid, @c false if not. + * @param pProps PCM properties to check. + */ +bool DrvAudioHlpPCMPropsAreValid(const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, false); + + /* Minimum 1 channel (mono), maximum 7.1 (= 8) channels. */ + bool fValid = ( pProps->cChannels >= 1 + && pProps->cChannels <= 8); + + if (fValid) + { + switch (pProps->cBytes) + { + case 1: /* 8 bit */ + if (pProps->fSigned) + fValid = false; + break; + case 2: /* 16 bit */ + if (!pProps->fSigned) + fValid = false; + break; + /** @todo Do we need support for 24 bit samples? */ + case 4: /* 32 bit */ + if (!pProps->fSigned) + fValid = false; + break; + default: + fValid = false; + break; + } + } + + if (!fValid) + return false; + + fValid &= pProps->uHz > 0; + fValid &= pProps->cShift == PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pProps->cBytes, pProps->cChannels); + fValid &= pProps->fSwapEndian == false; /** @todo Handling Big Endian audio data is not supported yet. */ + + return fValid; +} + +/** + * Checks whether the given PCM properties are equal with the given + * stream configuration. + * + * @returns @c true if equal, @c false if not. + * @param pProps PCM properties to compare. + * @param pCfg Stream configuration to compare. + */ +bool DrvAudioHlpPCMPropsAreEqual(const PPDMAUDIOPCMPROPS pProps, const PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pProps, false); + AssertPtrReturn(pCfg, false); + + return DrvAudioHlpPCMPropsAreEqual(pProps, &pCfg->Props); +} + +/** + * Returns the bytes per frame for given PCM properties. + * + * @return Bytes per (audio) frame. + * @param pProps PCM properties to retrieve bytes per frame for. + */ +uint32_t DrvAudioHlpPCMPropsBytesPerFrame(const PPDMAUDIOPCMPROPS pProps) +{ + return PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); +} + +/** + * Prints PCM properties to the debug log. + * + * @param pProps Stream configuration to log. + */ +void DrvAudioHlpPCMPropsPrint(const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturnVoid(pProps); + + Log(("uHz=%RU32, cChannels=%RU8, cBits=%RU8%s", + pProps->uHz, pProps->cChannels, pProps->cBytes * 8, pProps->fSigned ? "S" : "U")); +} + +/** + * Converts PCM properties to a audio stream configuration. + * + * @return IPRT status code. + * @param pProps PCM properties to convert. + * @param pCfg Stream configuration to store result into. + */ +int DrvAudioHlpPCMPropsToStreamCfg(const PPDMAUDIOPCMPROPS pProps, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pProps, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + DrvAudioHlpStreamCfgInit(pCfg); + + memcpy(&pCfg->Props, pProps, sizeof(PDMAUDIOPCMPROPS)); + return VINF_SUCCESS; +} + +/** + * Initializes a stream configuration with its default values. + * + * @param pCfg Stream configuration to initialize. + */ +void DrvAudioHlpStreamCfgInit(PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturnVoid(pCfg); + + RT_BZERO(pCfg, sizeof(PDMAUDIOSTREAMCFG)); + + pCfg->Backend.cfPreBuf = UINT32_MAX; /* Explicitly set to "undefined". */ +} + +/** + * Checks whether a given stream configuration is valid or not. + * + * Returns @c true if configuration is valid, @c false if not. + * @param pCfg Stream configuration to check. + */ +bool DrvAudioHlpStreamCfgIsValid(const PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pCfg, false); + + bool fValid = ( pCfg->enmDir == PDMAUDIODIR_IN + || pCfg->enmDir == PDMAUDIODIR_OUT); + + fValid &= ( pCfg->enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED + || pCfg->enmLayout == PDMAUDIOSTREAMLAYOUT_RAW); + + if (fValid) + fValid = DrvAudioHlpPCMPropsAreValid(&pCfg->Props); + + return fValid; +} + +/** + * Frees an allocated audio stream configuration. + * + * @param pCfg Audio stream configuration to free. + */ +void DrvAudioHlpStreamCfgFree(PPDMAUDIOSTREAMCFG pCfg) +{ + if (pCfg) + { + RTMemFree(pCfg); + pCfg = NULL; + } +} + +/** + * Copies a source stream configuration to a destination stream configuration. + * + * @returns IPRT status code. + * @param pDstCfg Destination stream configuration to copy source to. + * @param pSrcCfg Source stream configuration to copy to destination. + */ +int DrvAudioHlpStreamCfgCopy(PPDMAUDIOSTREAMCFG pDstCfg, const PPDMAUDIOSTREAMCFG pSrcCfg) +{ + AssertPtrReturn(pDstCfg, VERR_INVALID_POINTER); + AssertPtrReturn(pSrcCfg, VERR_INVALID_POINTER); + +#ifdef VBOX_STRICT + if (!DrvAudioHlpStreamCfgIsValid(pSrcCfg)) + { + AssertMsgFailed(("Stream config '%s' (%p) is invalid\n", pSrcCfg->szName, pSrcCfg)); + return VERR_INVALID_PARAMETER; + } +#endif + + memcpy(pDstCfg, pSrcCfg, sizeof(PDMAUDIOSTREAMCFG)); + + return VINF_SUCCESS; +} + +/** + * Duplicates an audio stream configuration. + * Must be free'd with DrvAudioHlpStreamCfgFree(). + * + * @return Duplicates audio stream configuration on success, or NULL on failure. + * @param pCfg Audio stream configuration to duplicate. + */ +PPDMAUDIOSTREAMCFG DrvAudioHlpStreamCfgDup(const PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pCfg, NULL); + +#ifdef VBOX_STRICT + if (!DrvAudioHlpStreamCfgIsValid(pCfg)) + { + AssertMsgFailed(("Stream config '%s' (%p) is invalid\n", pCfg->szName, pCfg)); + return NULL; + } +#endif + + PPDMAUDIOSTREAMCFG pDst = (PPDMAUDIOSTREAMCFG)RTMemAllocZ(sizeof(PDMAUDIOSTREAMCFG)); + if (!pDst) + return NULL; + + int rc2 = DrvAudioHlpStreamCfgCopy(pDst, pCfg); + if (RT_FAILURE(rc2)) + { + DrvAudioHlpStreamCfgFree(pDst); + pDst = NULL; + } + + AssertPtr(pDst); + return pDst; +} + +/** + * Prints an audio stream configuration to the debug log. + * + * @param pCfg Stream configuration to log. + */ +void DrvAudioHlpStreamCfgPrint(const PPDMAUDIOSTREAMCFG pCfg) +{ + if (!pCfg) + return; + + LogFunc(("szName=%s, enmDir=%RU32 (uHz=%RU32, cBits=%RU8%s, cChannels=%RU8)\n", + pCfg->szName, pCfg->enmDir, + pCfg->Props.uHz, pCfg->Props.cBytes * 8, pCfg->Props.fSigned ? "S" : "U", pCfg->Props.cChannels)); +} + +/** + * Converts a stream command to a string. + * + * @returns Stringified stream command, or "Unknown", if not found. + * @param enmCmd Stream command to convert. + */ +const char *DrvAudioHlpStreamCmdToStr(PDMAUDIOSTREAMCMD enmCmd) +{ + switch (enmCmd) + { + case PDMAUDIOSTREAMCMD_UNKNOWN: return "Unknown"; + case PDMAUDIOSTREAMCMD_ENABLE: return "Enable"; + case PDMAUDIOSTREAMCMD_DISABLE: return "Disable"; + case PDMAUDIOSTREAMCMD_PAUSE: return "Pause"; + case PDMAUDIOSTREAMCMD_RESUME: return "Resume"; + case PDMAUDIOSTREAMCMD_DRAIN: return "Drain"; + case PDMAUDIOSTREAMCMD_DROP: return "Drop"; + default: break; + } + + AssertMsgFailed(("Invalid stream command %ld\n", enmCmd)); + return "Unknown"; +} + +/** + * Returns @c true if the given stream status indicates a can-be-read-from stream, + * @c false if not. + * + * @returns @c true if ready to be read from, @c if not. + * @param enmStatus Stream status to evaluate. + */ +bool DrvAudioHlpStreamStatusCanRead(PDMAUDIOSTREAMSTS enmStatus) +{ + AssertReturn(enmStatus & PDMAUDIOSTREAMSTS_VALID_MASK, false); + + return enmStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED + && enmStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED + && !(enmStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED) + && !(enmStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT); +} + +/** + * Returns @c true if the given stream status indicates a can-be-written-to stream, + * @c false if not. + * + * @returns @c true if ready to be written to, @c if not. + * @param enmStatus Stream status to evaluate. + */ +bool DrvAudioHlpStreamStatusCanWrite(PDMAUDIOSTREAMSTS enmStatus) +{ + AssertReturn(enmStatus & PDMAUDIOSTREAMSTS_VALID_MASK, false); + + return enmStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED + && enmStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED + && !(enmStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED) + && !(enmStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE) + && !(enmStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT); +} + +/** + * Returns @c true if the given stream status indicates a ready-to-operate stream, + * @c false if not. + * + * @returns @c true if ready to operate, @c if not. + * @param enmStatus Stream status to evaluate. + */ +bool DrvAudioHlpStreamStatusIsReady(PDMAUDIOSTREAMSTS enmStatus) +{ + AssertReturn(enmStatus & PDMAUDIOSTREAMSTS_VALID_MASK, false); + + return enmStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED + && enmStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED + && !(enmStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT); +} + +/** + * Calculates the audio bit rate of the given bits per sample, the Hz and the number + * of audio channels. + * + * Divide the result by 8 to get the byte rate. + * + * @returns The calculated bit rate. + * @param cBits Number of bits per sample. + * @param uHz Hz (Hertz) rate. + * @param cChannels Number of audio channels. + */ +uint32_t DrvAudioHlpCalcBitrate(uint8_t cBits, uint32_t uHz, uint8_t cChannels) +{ + return (cBits * uHz * cChannels); +} + +/** + * Calculates the audio bit rate out of a given audio stream configuration. + * + * Divide the result by 8 to get the byte rate. + * + * @returns The calculated bit rate. + * @param pProps PCM properties to calculate bitrate for. + * + * @remark + */ +uint32_t DrvAudioHlpCalcBitrate(const PPDMAUDIOPCMPROPS pProps) +{ + return DrvAudioHlpCalcBitrate(pProps->cBytes * 8, pProps->uHz, pProps->cChannels); +} + +/** + * Aligns the given byte amount to the given PCM properties and returns the aligned + * size. + * + * @return Aligned size (in bytes). + * @param cbSize Size (in bytes) to align. + * @param pProps PCM properties to align size to. + */ +uint32_t DrvAudioHlpBytesAlign(uint32_t cbSize, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + if (!cbSize) + return 0; + + return PDMAUDIOPCMPROPS_B2F(pProps, cbSize) * PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); +} + +/** + * Returns if the the given size is properly aligned to the given PCM properties. + * + * @return @c true if properly aligned, @c false if not. + * @param cbSize Size (in bytes) to check alignment for. + * @param pProps PCM properties to use for checking the alignment. + */ +bool DrvAudioHlpBytesIsAligned(uint32_t cbSize, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + if (!cbSize) + return true; + + return (cbSize % PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */) == 0); +} + +/** + * Returns the bytes per second for given PCM properties. + * + * @returns Bytes per second. + * @param pProps PCM properties to retrieve size for. + */ +DECLINLINE(uint64_t) drvAudioHlpBytesPerSec(const PPDMAUDIOPCMPROPS pProps) +{ + return PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */) * pProps->uHz; +} + +/** + * Returns the number of audio frames for a given amount of bytes. + * + * @return Calculated audio frames for given bytes. + * @param cbBytes Bytes to convert to audio frames. + * @param pProps PCM properties to calulate frames for. + */ +uint32_t DrvAudioHlpBytesToFrames(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + return PDMAUDIOPCMPROPS_B2F(pProps, cbBytes); +} + +/** + * Returns the time (in ms) for given byte amount and PCM properties. + * + * @return uint64_t Calculated time (in ms). + * @param cbBytes Amount of bytes to calculate time for. + * @param pProps PCM properties to calculate amount of bytes for. + * + * @note Does rounding up the result. + */ +uint64_t DrvAudioHlpBytesToMilli(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + if (!pProps->uHz) /* Prevent division by zero. */ + return 0; + + const unsigned cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); + + if (!cbFrame) /* Prevent division by zero. */ + return 0; + + uint64_t uTimeMs = ((cbBytes + cbFrame - 1) / cbFrame) * RT_MS_1SEC; + + return (uTimeMs + pProps->uHz - 1) / pProps->uHz; +} + +/** + * Returns the time (in us) for given byte amount and PCM properties. + * + * @return uint64_t Calculated time (in us). + * @param cbBytes Amount of bytes to calculate time for. + * @param pProps PCM properties to calculate amount of bytes for. + * + * @note Does rounding up the result. + */ +uint64_t DrvAudioHlpBytesToMicro(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + if (!pProps->uHz) /* Prevent division by zero. */ + return 0; + + const unsigned cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); + + if (!cbFrame) /* Prevent division by zero. */ + return 0; + + uint64_t uTimeUs = ((cbBytes + cbFrame - 1) / cbFrame) * RT_US_1SEC; + + return (uTimeUs + pProps->uHz - 1) / pProps->uHz; +} + +/** + * Returns the time (in ns) for given byte amount and PCM properties. + * + * @return uint64_t Calculated time (in ns). + * @param cbBytes Amount of bytes to calculate time for. + * @param pProps PCM properties to calculate amount of bytes for. + * + * @note Does rounding up the result. + */ +uint64_t DrvAudioHlpBytesToNano(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + if (!pProps->uHz) /* Prevent division by zero. */ + return 0; + + const unsigned cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); + + if (!cbFrame) /* Prevent division by zero. */ + return 0; + + uint64_t uTimeNs = ((cbBytes + cbFrame - 1) / cbFrame) * RT_NS_1SEC; + + return (uTimeNs + pProps->uHz - 1) / pProps->uHz; +} + +/** + * Returns the bytes for a given audio frames amount and PCM properties. + * + * @return Calculated bytes for given audio frames. + * @param cFrames Amount of audio frames to calculate bytes for. + * @param pProps PCM properties to calculate bytes for. + */ +uint32_t DrvAudioHlpFramesToBytes(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + if (!cFrames) + return 0; + + return cFrames * PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); +} + +/** + * Returns the time (in ms) for given audio frames amount and PCM properties. + * + * @return uint64_t Calculated time (in ms). + * @param cFrames Amount of audio frames to calculate time for. + * @param pProps PCM properties to calculate time (in ms) for. + */ +uint64_t DrvAudioHlpFramesToMilli(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + if (!cFrames) + return 0; + + if (!pProps->uHz) /* Prevent division by zero. */ + return 0; + + return cFrames / ((double)pProps->uHz / (double)RT_MS_1SEC); +} + +/** + * Returns the time (in ns) for given audio frames amount and PCM properties. + * + * @return uint64_t Calculated time (in ns). + * @param cFrames Amount of audio frames to calculate time for. + * @param pProps PCM properties to calculate time (in ns) for. + */ +uint64_t DrvAudioHlpFramesToNano(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + if (!cFrames) + return 0; + + if (!pProps->uHz) /* Prevent division by zero. */ + return 0; + + return cFrames / ((double)pProps->uHz / (double)RT_NS_1SEC); +} + +/** + * Returns the amount of bytes for a given time (in ms) and PCM properties. + * + * Note: The result will return an amount of bytes which is aligned to the audio frame size. + * + * @return uint32_t Calculated amount of bytes. + * @param uMs Time (in ms) to calculate amount of bytes for. + * @param pProps PCM properties to calculate amount of bytes for. + */ +uint32_t DrvAudioHlpMilliToBytes(uint64_t uMs, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + if (!uMs) + return 0; + + const uint32_t uBytesPerFrame = DrvAudioHlpPCMPropsBytesPerFrame(pProps); + + uint32_t uBytes = ((double)drvAudioHlpBytesPerSec(pProps) / (double)RT_MS_1SEC) * uMs; + if (uBytes % uBytesPerFrame) /* Any remainder? Make the returned bytes an integral number to the given frames. */ + uBytes = uBytes + (uBytesPerFrame - uBytes % uBytesPerFrame); + + Assert(uBytes % uBytesPerFrame == 0); /* Paranoia. */ + + return uBytes; +} + +/** + * Returns the amount of bytes for a given time (in ns) and PCM properties. + * + * Note: The result will return an amount of bytes which is aligned to the audio frame size. + * + * @return uint32_t Calculated amount of bytes. + * @param uNs Time (in ns) to calculate amount of bytes for. + * @param pProps PCM properties to calculate amount of bytes for. + */ +uint32_t DrvAudioHlpNanoToBytes(uint64_t uNs, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + if (!uNs) + return 0; + + const uint32_t uBytesPerFrame = DrvAudioHlpPCMPropsBytesPerFrame(pProps); + + uint32_t uBytes = ((double)drvAudioHlpBytesPerSec(pProps) / (double)RT_NS_1SEC) * uNs; + if (uBytes % uBytesPerFrame) /* Any remainder? Make the returned bytes an integral number to the given frames. */ + uBytes = uBytes + (uBytesPerFrame - uBytes % uBytesPerFrame); + + Assert(uBytes % uBytesPerFrame == 0); /* Paranoia. */ + + return uBytes; +} + +/** + * Returns the amount of audio frames for a given time (in ms) and PCM properties. + * + * @return uint32_t Calculated amount of audio frames. + * @param uMs Time (in ms) to calculate amount of frames for. + * @param pProps PCM properties to calculate amount of frames for. + */ +uint32_t DrvAudioHlpMilliToFrames(uint64_t uMs, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + const uint32_t cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); + if (!cbFrame) /* Prevent division by zero. */ + return 0; + + return DrvAudioHlpMilliToBytes(uMs, pProps) / cbFrame; +} + +/** + * Returns the amount of audio frames for a given time (in ns) and PCM properties. + * + * @return uint32_t Calculated amount of audio frames. + * @param uNs Time (in ns) to calculate amount of frames for. + * @param pProps PCM properties to calculate amount of frames for. + */ +uint32_t DrvAudioHlpNanoToFrames(uint64_t uNs, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pProps, 0); + + const uint32_t cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */); + if (!cbFrame) /* Prevent division by zero. */ + return 0; + + return DrvAudioHlpNanoToBytes(uNs, pProps) / cbFrame; +} + +/** + * Sanitizes the file name component so that unsupported characters + * will be replaced by an underscore ("_"). + * + * @return IPRT status code. + * @param pszPath Path to sanitize. + * @param cbPath Size (in bytes) of path to sanitize. + */ +int DrvAudioHlpFileNameSanitize(char *pszPath, size_t cbPath) +{ + RT_NOREF(cbPath); + int rc = VINF_SUCCESS; +#ifdef RT_OS_WINDOWS + /* Filter out characters not allowed on Windows platforms, put in by + RTTimeSpecToString(). */ + /** @todo Use something like RTPathSanitize() if available later some time. */ + static RTUNICP const s_uszValidRangePairs[] = + { + ' ', ' ', + '(', ')', + '-', '.', + '0', '9', + 'A', 'Z', + 'a', 'z', + '_', '_', + 0xa0, 0xd7af, + '\0' + }; + ssize_t cReplaced = RTStrPurgeComplementSet(pszPath, s_uszValidRangePairs, '_' /* Replacement */); + if (cReplaced < 0) + rc = VERR_INVALID_UTF8_ENCODING; +#else + RT_NOREF(pszPath); +#endif + return rc; +} + +/** + * Constructs an unique file name, based on the given path and the audio file type. + * + * @returns IPRT status code. + * @param pszFile Where to store the constructed file name. + * @param cchFile Size (in characters) of the file name buffer. + * @param pszPath Base path to use. + * @param pszName A name for better identifying the file. + * @param uInstance Device / driver instance which is using this file. + * @param enmType Audio file type to construct file name for. + * @param fFlags File naming flags. + */ +int DrvAudioHlpFileNameGet(char *pszFile, size_t cchFile, const char *pszPath, const char *pszName, + uint32_t uInstance, PDMAUDIOFILETYPE enmType, PDMAUDIOFILENAMEFLAGS fFlags) +{ + AssertPtrReturn(pszFile, VERR_INVALID_POINTER); + AssertReturn(cchFile, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + /** @todo Validate fFlags. */ + + int rc; + + do + { + char szFilePath[RTPATH_MAX + 1]; + RTStrPrintf2(szFilePath, sizeof(szFilePath), "%s", pszPath); + + /* Create it when necessary. */ + if (!RTDirExists(szFilePath)) + { + rc = RTDirCreateFullPath(szFilePath, RTFS_UNIX_IRWXU); + if (RT_FAILURE(rc)) + break; + } + + char szFileName[RTPATH_MAX + 1]; + szFileName[0] = '\0'; + + if (fFlags & PDMAUDIOFILENAME_FLAG_TS) + { + RTTIMESPEC time; + if (!RTTimeSpecToString(RTTimeNow(&time), szFileName, sizeof(szFileName))) + { + rc = VERR_BUFFER_OVERFLOW; + break; + } + + rc = DrvAudioHlpFileNameSanitize(szFileName, sizeof(szFileName)); + if (RT_FAILURE(rc)) + break; + + rc = RTStrCat(szFileName, sizeof(szFileName), "-"); + if (RT_FAILURE(rc)) + break; + } + + rc = RTStrCat(szFileName, sizeof(szFileName), pszName); + if (RT_FAILURE(rc)) + break; + + rc = RTStrCat(szFileName, sizeof(szFileName), "-"); + if (RT_FAILURE(rc)) + break; + + char szInst[16]; + RTStrPrintf2(szInst, sizeof(szInst), "%RU32", uInstance); + rc = RTStrCat(szFileName, sizeof(szFileName), szInst); + if (RT_FAILURE(rc)) + break; + + switch (enmType) + { + case PDMAUDIOFILETYPE_RAW: + rc = RTStrCat(szFileName, sizeof(szFileName), ".pcm"); + break; + + case PDMAUDIOFILETYPE_WAV: + rc = RTStrCat(szFileName, sizeof(szFileName), ".wav"); + break; + + default: + AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + break; + } + + if (RT_FAILURE(rc)) + break; + + rc = RTPathAppend(szFilePath, sizeof(szFilePath), szFileName); + if (RT_FAILURE(rc)) + break; + + RTStrPrintf2(pszFile, cchFile, "%s", szFilePath); + + } while (0); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Creates an audio file. + * + * @returns IPRT status code. + * @param enmType Audio file type to open / create. + * @param pszFile File path of file to open or create. + * @param fFlags Audio file flags. + * @param ppFile Where to store the created audio file handle. + * Needs to be destroyed with DrvAudioHlpFileDestroy(). + */ +int DrvAudioHlpFileCreate(PDMAUDIOFILETYPE enmType, const char *pszFile, PDMAUDIOFILEFLAGS fFlags, PPDMAUDIOFILE *ppFile) +{ + AssertPtrReturn(pszFile, VERR_INVALID_POINTER); + /** @todo Validate fFlags. */ + + PPDMAUDIOFILE pFile = (PPDMAUDIOFILE)RTMemAlloc(sizeof(PDMAUDIOFILE)); + if (!pFile) + return VERR_NO_MEMORY; + + int rc = VINF_SUCCESS; + + switch (enmType) + { + case PDMAUDIOFILETYPE_RAW: + case PDMAUDIOFILETYPE_WAV: + pFile->enmType = enmType; + break; + + default: + rc = VERR_INVALID_PARAMETER; + break; + } + + if (RT_SUCCESS(rc)) + { + RTStrPrintf(pFile->szName, RT_ELEMENTS(pFile->szName), "%s", pszFile); + pFile->hFile = NIL_RTFILE; + pFile->fFlags = fFlags; + pFile->pvData = NULL; + pFile->cbData = 0; + } + + if (RT_FAILURE(rc)) + { + RTMemFree(pFile); + pFile = NULL; + } + else + *ppFile = pFile; + + return rc; +} + +/** + * Destroys a formerly created audio file. + * + * @param pFile Audio file (object) to destroy. + */ +void DrvAudioHlpFileDestroy(PPDMAUDIOFILE pFile) +{ + if (!pFile) + return; + + DrvAudioHlpFileClose(pFile); + + RTMemFree(pFile); + pFile = NULL; +} + +/** + * Opens or creates an audio file. + * + * @returns IPRT status code. + * @param pFile Pointer to audio file handle to use. + * @param fOpen Open flags. + * Use PDMAUDIOFILE_DEFAULT_OPEN_FLAGS for the default open flags. + * @param pProps PCM properties to use. + */ +int DrvAudioHlpFileOpen(PPDMAUDIOFILE pFile, uint32_t fOpen, const PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pFile, VERR_INVALID_POINTER); + /** @todo Validate fOpen flags. */ + AssertPtrReturn(pProps, VERR_INVALID_POINTER); + + int rc; + + if (pFile->enmType == PDMAUDIOFILETYPE_RAW) + { + rc = RTFileOpen(&pFile->hFile, pFile->szName, fOpen); + } + else if (pFile->enmType == PDMAUDIOFILETYPE_WAV) + { + Assert(pProps->cChannels); + Assert(pProps->uHz); + Assert(pProps->cBytes); + + pFile->pvData = (PAUDIOWAVFILEDATA)RTMemAllocZ(sizeof(AUDIOWAVFILEDATA)); + if (pFile->pvData) + { + pFile->cbData = sizeof(PAUDIOWAVFILEDATA); + + PAUDIOWAVFILEDATA pData = (PAUDIOWAVFILEDATA)pFile->pvData; + AssertPtr(pData); + + /* Header. */ + pData->Hdr.u32RIFF = AUDIO_MAKE_FOURCC('R','I','F','F'); + pData->Hdr.u32Size = 36; + pData->Hdr.u32WAVE = AUDIO_MAKE_FOURCC('W','A','V','E'); + + pData->Hdr.u32Fmt = AUDIO_MAKE_FOURCC('f','m','t',' '); + pData->Hdr.u32Size1 = 16; /* Means PCM. */ + pData->Hdr.u16AudioFormat = 1; /* PCM, linear quantization. */ + pData->Hdr.u16NumChannels = pProps->cChannels; + pData->Hdr.u32SampleRate = pProps->uHz; + pData->Hdr.u32ByteRate = DrvAudioHlpCalcBitrate(pProps) / 8; + pData->Hdr.u16BlockAlign = pProps->cChannels * pProps->cBytes; + pData->Hdr.u16BitsPerSample = pProps->cBytes * 8; + + /* Data chunk. */ + pData->Hdr.u32ID2 = AUDIO_MAKE_FOURCC('d','a','t','a'); + pData->Hdr.u32Size2 = 0; + + rc = RTFileOpen(&pFile->hFile, pFile->szName, fOpen); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(pFile->hFile, &pData->Hdr, sizeof(pData->Hdr), NULL); + if (RT_FAILURE(rc)) + { + RTFileClose(pFile->hFile); + pFile->hFile = NIL_RTFILE; + } + } + + if (RT_FAILURE(rc)) + { + RTMemFree(pFile->pvData); + pFile->pvData = NULL; + pFile->cbData = 0; + } + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + + if (RT_SUCCESS(rc)) + { + LogRel2(("Audio: Opened file '%s'\n", pFile->szName)); + } + else + LogRel(("Audio: Failed opening file '%s', rc=%Rrc\n", pFile->szName, rc)); + + return rc; +} + +/** + * Closes an audio file. + * + * @returns IPRT status code. + * @param pFile Audio file handle to close. + */ +int DrvAudioHlpFileClose(PPDMAUDIOFILE pFile) +{ + if (!pFile) + return VINF_SUCCESS; + + size_t cbSize = DrvAudioHlpFileGetDataSize(pFile); + + int rc = VINF_SUCCESS; + + if (pFile->enmType == PDMAUDIOFILETYPE_RAW) + { + if (RTFileIsValid(pFile->hFile)) + rc = RTFileClose(pFile->hFile); + } + else if (pFile->enmType == PDMAUDIOFILETYPE_WAV) + { + if (RTFileIsValid(pFile->hFile)) + { + PAUDIOWAVFILEDATA pData = (PAUDIOWAVFILEDATA)pFile->pvData; + if (pData) /* The .WAV file data only is valid when a file actually has been created. */ + { + /* Update the header with the current data size. */ + RTFileWriteAt(pFile->hFile, 0, &pData->Hdr, sizeof(pData->Hdr), NULL); + } + + rc = RTFileClose(pFile->hFile); + } + + if (pFile->pvData) + { + RTMemFree(pFile->pvData); + pFile->pvData = NULL; + } + } + else + AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + + if ( RT_SUCCESS(rc) + && !cbSize + && !(pFile->fFlags & PDMAUDIOFILE_FLAG_KEEP_IF_EMPTY)) + { + rc = DrvAudioHlpFileDelete(pFile); + } + + pFile->cbData = 0; + + if (RT_SUCCESS(rc)) + { + pFile->hFile = NIL_RTFILE; + LogRel2(("Audio: Closed file '%s' (%zu bytes)\n", pFile->szName, cbSize)); + } + else + LogRel(("Audio: Failed closing file '%s', rc=%Rrc\n", pFile->szName, rc)); + + return rc; +} + +/** + * Deletes an audio file. + * + * @returns IPRT status code. + * @param pFile Audio file handle to delete. + */ +int DrvAudioHlpFileDelete(PPDMAUDIOFILE pFile) +{ + AssertPtrReturn(pFile, VERR_INVALID_POINTER); + + int rc = RTFileDelete(pFile->szName); + if (RT_SUCCESS(rc)) + { + LogRel2(("Audio: Deleted file '%s'\n", pFile->szName)); + } + else if (rc == VERR_FILE_NOT_FOUND) /* Don't bitch if the file is not around (anymore). */ + rc = VINF_SUCCESS; + + if (RT_FAILURE(rc)) + LogRel(("Audio: Failed deleting file '%s', rc=%Rrc\n", pFile->szName, rc)); + + return rc; +} + +/** + * Returns the raw audio data size of an audio file. + * + * Note: This does *not* include file headers and other data which does + * not belong to the actual PCM audio data. + * + * @returns Size (in bytes) of the raw PCM audio data. + * @param pFile Audio file handle to retrieve the audio data size for. + */ +size_t DrvAudioHlpFileGetDataSize(PPDMAUDIOFILE pFile) +{ + AssertPtrReturn(pFile, 0); + + size_t cbSize = 0; + + if (pFile->enmType == PDMAUDIOFILETYPE_RAW) + { + cbSize = RTFileTell(pFile->hFile); + } + else if (pFile->enmType == PDMAUDIOFILETYPE_WAV) + { + PAUDIOWAVFILEDATA pData = (PAUDIOWAVFILEDATA)pFile->pvData; + if (pData) /* The .WAV file data only is valid when a file actually has been created. */ + cbSize = pData->Hdr.u32Size2; + } + + return cbSize; +} + +/** + * Returns whether the given audio file is open and in use or not. + * + * @return bool True if open, false if not. + * @param pFile Audio file handle to check open status for. + */ +bool DrvAudioHlpFileIsOpen(PPDMAUDIOFILE pFile) +{ + if (!pFile) + return false; + + return RTFileIsValid(pFile->hFile); +} + +/** + * Write PCM data to a wave (.WAV) file. + * + * @returns IPRT status code. + * @param pFile Audio file handle to write PCM data to. + * @param pvBuf Audio data to write. + * @param cbBuf Size (in bytes) of audio data to write. + * @param fFlags Additional write flags. Not being used at the moment and must be 0. + */ +int DrvAudioHlpFileWrite(PPDMAUDIOFILE pFile, const void *pvBuf, size_t cbBuf, uint32_t fFlags) +{ + AssertPtrReturn(pFile, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + + AssertReturn(fFlags == 0, VERR_INVALID_PARAMETER); /** @todo fFlags are currently not implemented. */ + + if (!cbBuf) + return VINF_SUCCESS; + + AssertReturn(RTFileIsValid(pFile->hFile), VERR_WRONG_ORDER); + + int rc; + + if (pFile->enmType == PDMAUDIOFILETYPE_RAW) + { + rc = RTFileWrite(pFile->hFile, pvBuf, cbBuf, NULL); + } + else if (pFile->enmType == PDMAUDIOFILETYPE_WAV) + { + PAUDIOWAVFILEDATA pData = (PAUDIOWAVFILEDATA)pFile->pvData; + AssertPtr(pData); + + rc = RTFileWrite(pFile->hFile, pvBuf, cbBuf, NULL); + if (RT_SUCCESS(rc)) + { + pData->Hdr.u32Size += (uint32_t)cbBuf; + pData->Hdr.u32Size2 += (uint32_t)cbBuf; + } + } + else + rc = VERR_NOT_SUPPORTED; + + return rc; +} + diff --git a/src/VBox/Devices/Audio/DrvHostALSAAudio.cpp b/src/VBox/Devices/Audio/DrvHostALSAAudio.cpp new file mode 100644 index 00000000..bffde104 --- /dev/null +++ b/src/VBox/Devices/Audio/DrvHostALSAAudio.cpp @@ -0,0 +1,1530 @@ +/* $Id: DrvHostALSAAudio.cpp $ */ +/** @file + * ALSA audio driver. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * -------------------------------------------------------------------- + * + * This code is based on: alsaaudio.c + * + * QEMU ALSA audio driver + * + * Copyright (c) 2005 Vassili Karpov (malc) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <VBox/log.h> +#include <iprt/alloc.h> +#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */ +#include <VBox/vmm/pdmaudioifs.h> + +RT_C_DECLS_BEGIN + #include "alsa_stubs.h" + #include "alsa_mangling.h" +RT_C_DECLS_END + +#include <alsa/asoundlib.h> +#include <alsa/control.h> /* For device enumeration. */ + +#include "DrvAudio.h" +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ + +/** Makes DRVHOSTALSAAUDIO out of PDMIHOSTAUDIO. */ +#define PDMIHOSTAUDIO_2_DRVHOSTALSAAUDIO(pInterface) \ + ( (PDRVHOSTALSAAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTALSAAUDIO, IHostAudio)) ) + + +/********************************************************************************************************************************* +* Structures * +*********************************************************************************************************************************/ + +typedef struct ALSAAUDIOSTREAM +{ + /** The stream's acquired configuration. */ + PPDMAUDIOSTREAMCFG pCfg; + union + { + struct + { + } In; + struct + { + } Out; + }; + snd_pcm_t *phPCM; + void *pvBuf; + size_t cbBuf; +} ALSAAUDIOSTREAM, *PALSAAUDIOSTREAM; + +/* latency = period_size * periods / (rate * bytes_per_frame) */ + +static int alsaStreamRecover(snd_pcm_t *phPCM); + +/** + * Host Alsa audio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHOSTALSAAUDIO +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Error count for not flooding the release log. + * UINT32_MAX for unlimited logging. */ + uint32_t cLogErrors; +} DRVHOSTALSAAUDIO, *PDRVHOSTALSAAUDIO; + +/** Maximum number of tries to recover a broken pipe. */ +#define ALSA_RECOVERY_TRIES_MAX 5 + +typedef struct ALSAAUDIOSTREAMCFG +{ + unsigned int freq; + /** PCM sound format. */ + snd_pcm_format_t fmt; + /** PCM data access type. */ + snd_pcm_access_t access; + /** Whether resampling should be performed by alsalib or not. */ + int resample; + int nchannels; + /** Buffer size (in audio frames). */ + unsigned long buffer_size; + /** Periods (in audio frames). */ + unsigned long period_size; + /** For playback: Starting to play threshold (in audio frames). + * For Capturing: Starting to capture threshold (in audio frames). */ + unsigned long threshold; +} ALSAAUDIOSTREAMCFG, *PALSAAUDIOSTREAMCFG; + + + +static snd_pcm_format_t alsaAudioPropsToALSA(PPDMAUDIOPCMPROPS pProps) +{ + switch (pProps->cBytes) + { + case 1: + return pProps->fSigned ? SND_PCM_FORMAT_S8 : SND_PCM_FORMAT_U8; + + case 2: + return pProps->fSigned ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U16_LE; + + case 4: + return pProps->fSigned ? SND_PCM_FORMAT_S32_LE : SND_PCM_FORMAT_U32_LE; + + default: + break; + } + + AssertMsgFailed(("%RU8 bytes not supported\n", pProps->cBytes)); + return SND_PCM_FORMAT_U8; +} + + +static int alsaALSAToAudioProps(snd_pcm_format_t fmt, PPDMAUDIOPCMPROPS pProps) +{ + switch (fmt) + { + case SND_PCM_FORMAT_S8: + pProps->cBytes = 1; + pProps->fSigned = true; + break; + + case SND_PCM_FORMAT_U8: + pProps->cBytes = 1; + pProps->fSigned = false; + break; + + case SND_PCM_FORMAT_S16_LE: + pProps->cBytes = 2; + pProps->fSigned = true; + break; + + case SND_PCM_FORMAT_U16_LE: + pProps->cBytes = 2; + pProps->fSigned = false; + break; + + case SND_PCM_FORMAT_S16_BE: + pProps->cBytes = 2; + pProps->fSigned = true; +#ifdef RT_LITTLE_ENDIAN + pProps->fSwapEndian = true; +#endif + break; + + case SND_PCM_FORMAT_U16_BE: + pProps->cBytes = 2; + pProps->fSigned = false; +#ifdef RT_LITTLE_ENDIAN + pProps->fSwapEndian = true; +#endif + break; + + case SND_PCM_FORMAT_S32_LE: + pProps->cBytes = 4; + pProps->fSigned = true; + break; + + case SND_PCM_FORMAT_U32_LE: + pProps->cBytes = 4; + pProps->fSigned = false; + break; + + case SND_PCM_FORMAT_S32_BE: + pProps->cBytes = 4; + pProps->fSigned = true; +#ifdef RT_LITTLE_ENDIAN + pProps->fSwapEndian = true; +#endif + break; + + case SND_PCM_FORMAT_U32_BE: + pProps->cBytes = 4; + pProps->fSigned = false; +#ifdef RT_LITTLE_ENDIAN + pProps->fSwapEndian = true; +#endif + break; + + default: + AssertMsgFailed(("Format %ld not supported\n", fmt)); + return VERR_NOT_SUPPORTED; + } + + Assert(pProps->cBytes); + Assert(pProps->cChannels); + pProps->cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pProps->cBytes, pProps->cChannels); + + return VINF_SUCCESS; +} + + +static int alsaStreamSetSWParams(snd_pcm_t *phPCM, bool fIn, PALSAAUDIOSTREAMCFG pCfgReq, PALSAAUDIOSTREAMCFG pCfgObt) +{ + if (fIn) /* For input streams there's nothing to do in here right now. */ + return VINF_SUCCESS; + + snd_pcm_sw_params_t *pSWParms = NULL; + snd_pcm_sw_params_alloca(&pSWParms); + if (!pSWParms) + return VERR_NO_MEMORY; + + int rc; + do + { + int err = snd_pcm_sw_params_current(phPCM, pSWParms); + if (err < 0) + { + LogRel(("ALSA: Failed to get current software parameters: %s\n", snd_strerror(err))); + rc = VERR_ACCESS_DENIED; + break; + } + + err = snd_pcm_sw_params_set_start_threshold(phPCM, pSWParms, pCfgReq->threshold); + if (err < 0) + { + LogRel(("ALSA: Failed to set software threshold to %ld: %s\n", pCfgReq->threshold, snd_strerror(err))); + rc = VERR_ACCESS_DENIED; + break; + } + + err = snd_pcm_sw_params_set_avail_min(phPCM, pSWParms, pCfgReq->period_size); + if (err < 0) + { + LogRel(("ALSA: Failed to set available minimum to %ld: %s\n", pCfgReq->threshold, snd_strerror(err))); + rc = VERR_ACCESS_DENIED; + break; + } + + err = snd_pcm_sw_params(phPCM, pSWParms); + if (err < 0) + { + LogRel(("ALSA: Failed to set new software parameters: %s\n", snd_strerror(err))); + rc = VERR_ACCESS_DENIED; + break; + } + + err = snd_pcm_sw_params_get_start_threshold(pSWParms, &pCfgObt->threshold); + if (err < 0) + { + LogRel(("ALSA: Failed to get start threshold\n")); + rc = VERR_ACCESS_DENIED; + break; + } + + LogFunc(("Setting threshold to %RU32 frames\n", pCfgObt->threshold)); + rc = VINF_SUCCESS; + } + while (0); + + return rc; +} + + +static int alsaStreamClose(snd_pcm_t **pphPCM) +{ + if (!pphPCM || !*pphPCM) + return VINF_SUCCESS; + + int rc; + int rc2 = snd_pcm_close(*pphPCM); + if (rc2) + { + LogRel(("ALSA: Closing PCM descriptor failed: %s\n", snd_strerror(rc2))); + rc = VERR_GENERAL_FAILURE; /** @todo */ + } + else + { + *pphPCM = NULL; + rc = VINF_SUCCESS; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +static int alsaStreamOpen(bool fIn, PALSAAUDIOSTREAMCFG pCfgReq, PALSAAUDIOSTREAMCFG pCfgObt, snd_pcm_t **pphPCM) +{ + snd_pcm_t *phPCM = NULL; + + int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + + unsigned int cChannels = pCfgReq->nchannels; + unsigned int uFreq = pCfgReq->freq; + snd_pcm_uframes_t obt_buffer_size; + + do + { + const char *pszDev = "default"; /** @todo Make this configurable through PALSAAUDIOSTREAMCFG. */ + if (!pszDev) + { + LogRel(("ALSA: Invalid or no %s device name set\n", fIn ? "input" : "output")); + rc = VERR_INVALID_PARAMETER; + break; + } + + LogRel(("ALSA: Using %s device \"%s\"\n", fIn ? "input" : "output", pszDev)); + + int err = snd_pcm_open(&phPCM, pszDev, + fIn ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK); + if (err < 0) + { + LogRel(("ALSA: Failed to open \"%s\" as %s device: %s\n", pszDev, fIn ? "input" : "output", snd_strerror(err))); + break; + } + + err = snd_pcm_nonblock(phPCM, 1); + if (err < 0) + { + LogRel(("ALSA: Error setting output non-blocking mode: %s\n", snd_strerror(err))); + break; + } + + snd_pcm_hw_params_t *pHWParms; + snd_pcm_hw_params_alloca(&pHWParms); /** @todo Check for successful allocation? */ + err = snd_pcm_hw_params_any(phPCM, pHWParms); + if (err < 0) + { + LogRel(("ALSA: Failed to initialize hardware parameters: %s\n", snd_strerror(err))); + break; + } + + err = snd_pcm_hw_params_set_access(phPCM, pHWParms, SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) + { + LogRel(("ALSA: Failed to set access type: %s\n", snd_strerror(err))); + break; + } + + err = snd_pcm_hw_params_set_format(phPCM, pHWParms, pCfgReq->fmt); + if (err < 0) + { + LogRel(("ALSA: Failed to set audio format to %d: %s\n", pCfgReq->fmt, snd_strerror(err))); + break; + } + + err = snd_pcm_hw_params_set_rate_near(phPCM, pHWParms, &uFreq, 0); + if (err < 0) + { + LogRel(("ALSA: Failed to set frequency to %uHz: %s\n", pCfgReq->freq, snd_strerror(err))); + break; + } + + err = snd_pcm_hw_params_set_channels_near(phPCM, pHWParms, &cChannels); + if (err < 0) + { + LogRel(("ALSA: Failed to set number of channels to %d\n", pCfgReq->nchannels)); + break; + } + + if ( cChannels != 1 + && cChannels != 2) + { + LogRel(("ALSA: Number of audio channels (%u) not supported\n", cChannels)); + break; + } + + snd_pcm_uframes_t period_size_f = pCfgReq->period_size; + snd_pcm_uframes_t buffer_size_f = pCfgReq->buffer_size; + + snd_pcm_uframes_t minval = period_size_f; + + int dir = 0; + err = snd_pcm_hw_params_get_period_size_min(pHWParms, &minval, &dir); + if (err < 0) + { + LogRel(("ALSA: Could not determine minimal period size\n")); + break; + } + else + { + LogFunc(("Minimal period size is: %ld\n", minval)); + if (period_size_f < minval) + period_size_f = minval; + } + + err = snd_pcm_hw_params_set_period_size_near(phPCM, pHWParms, &period_size_f, 0); + LogFunc(("Period size is: %RU32\n", period_size_f)); + if (err < 0) + { + LogRel(("ALSA: Failed to set period size %d (%s)\n", period_size_f, snd_strerror(err))); + break; + } + + minval = buffer_size_f; + err = snd_pcm_hw_params_get_buffer_size_min(pHWParms, &minval); + if (err < 0) + { + LogRel(("ALSA: Could not retrieve minimal buffer size\n")); + break; + } + else + LogFunc(("Minimal buffer size is: %RU32\n", minval)); + + err = snd_pcm_hw_params_set_buffer_size_near(phPCM, pHWParms, &buffer_size_f); + if (err < 0) + { + LogRel(("ALSA: Failed to set near buffer size %RU32: %s\n", buffer_size_f, snd_strerror(err))); + break; + } + + err = snd_pcm_hw_params(phPCM, pHWParms); + if (err < 0) + { + LogRel(("ALSA: Failed to apply audio parameters\n")); + break; + } + + err = snd_pcm_hw_params_get_buffer_size(pHWParms, &obt_buffer_size); + if (err < 0) + { + LogRel(("ALSA: Failed to get buffer size\n")); + break; + } + + snd_pcm_uframes_t obt_period_size; + err = snd_pcm_hw_params_get_period_size(pHWParms, &obt_period_size, &dir); + if (err < 0) + { + LogRel(("ALSA: Failed to get period size\n")); + break; + } + + LogRel2(("ALSA: Frequency is %dHz, period size is %RU32 frames, buffer size is %RU32 frames\n", + pCfgReq->freq, obt_period_size, obt_buffer_size)); + + err = snd_pcm_prepare(phPCM); + if (err < 0) + { + LogRel(("ALSA: Could not prepare hPCM %p\n", (void *)phPCM)); + rc = VERR_AUDIO_BACKEND_INIT_FAILED; + break; + } + + rc = alsaStreamSetSWParams(phPCM, fIn, pCfgReq, pCfgObt); + if (RT_FAILURE(rc)) + break; + + pCfgObt->fmt = pCfgReq->fmt; + pCfgObt->nchannels = cChannels; + pCfgObt->freq = uFreq; + pCfgObt->period_size = obt_period_size; + pCfgObt->buffer_size = obt_buffer_size; + + rc = VINF_SUCCESS; + } + while (0); + + if (RT_SUCCESS(rc)) + { + *pphPCM = phPCM; + } + else + alsaStreamClose(&phPCM); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +#ifdef DEBUG +static void alsaDbgErrorHandler(const char *file, int line, const char *function, + int err, const char *fmt, ...) +{ + /** @todo Implement me! */ + RT_NOREF(file, line, function, err, fmt); +} +#endif + + +static int alsaStreamGetAvail(snd_pcm_t *phPCM, snd_pcm_sframes_t *pFramesAvail) +{ + AssertPtrReturn(phPCM, VERR_INVALID_POINTER); + /* pFramesAvail is optional. */ + + int rc; + + snd_pcm_sframes_t framesAvail = snd_pcm_avail_update(phPCM); + if (framesAvail < 0) + { + if (framesAvail == -EPIPE) + { + rc = alsaStreamRecover(phPCM); + if (RT_SUCCESS(rc)) + framesAvail = snd_pcm_avail_update(phPCM); + } + else + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + else + rc = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + { + if (pFramesAvail) + *pFramesAvail = framesAvail; + } + + LogFunc(("cFrames=%ld, rc=%Rrc\n", framesAvail, rc)); + return rc; +} + + +static int alsaStreamRecover(snd_pcm_t *phPCM) +{ + AssertPtrReturn(phPCM, VERR_INVALID_POINTER); + + int err = snd_pcm_prepare(phPCM); + if (err < 0) + { + LogFunc(("Failed to recover stream %p: %s\n", phPCM, snd_strerror(err))); + return VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + + return VINF_SUCCESS; +} + + +static int alsaStreamResume(snd_pcm_t *phPCM) +{ + AssertPtrReturn(phPCM, VERR_INVALID_POINTER); + + int err = snd_pcm_resume(phPCM); + if (err < 0) + { + LogFunc(("Failed to resume stream %p: %s\n", phPCM, snd_strerror(err))); + return VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} + */ +static DECLCALLBACK(int) drvHostALSAAudioInit(PPDMIHOSTAUDIO pInterface) +{ + RT_NOREF(pInterface); + + LogFlowFuncEnter(); + + int rc = audioLoadAlsaLib(); + if (RT_FAILURE(rc)) + LogRel(("ALSA: Failed to load the ALSA shared library, rc=%Rrc\n", rc)); + else + { +#ifdef DEBUG + snd_lib_error_set_handler(alsaDbgErrorHandler); +#endif + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHostALSAAudioStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cxBuf, VERR_INVALID_PARAMETER); + /* pcbRead is optional. */ + + PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; + + snd_pcm_sframes_t cAvail; + int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cAvail); + if (RT_FAILURE(rc)) + { + LogFunc(("Error getting number of captured frames, rc=%Rrc\n", rc)); + return rc; + } + + PPDMAUDIOSTREAMCFG pCfg = pStreamALSA->pCfg; + AssertPtr(pCfg); + + if (!cAvail) /* No data yet? */ + { + snd_pcm_state_t state = snd_pcm_state(pStreamALSA->phPCM); + switch (state) + { + case SND_PCM_STATE_PREPARED: + cAvail = PDMAUDIOSTREAMCFG_B2F(pCfg, cxBuf); + break; + + case SND_PCM_STATE_SUSPENDED: + { + rc = alsaStreamResume(pStreamALSA->phPCM); + if (RT_FAILURE(rc)) + break; + + LogFlow(("Resuming suspended input stream\n")); + break; + } + + default: + LogFlow(("No frames available, state=%d\n", state)); + break; + } + + if (!cAvail) + { + if (pcxRead) + *pcxRead = 0; + return VINF_SUCCESS; + } + } + + /* + * Check how much we can read from the capture device without overflowing + * the mixer buffer. + */ + size_t cbToRead = RT_MIN((size_t)PDMAUDIOSTREAMCFG_F2B(pCfg, cAvail), cxBuf); + + LogFlowFunc(("cbToRead=%zu, cAvail=%RI32\n", cbToRead, cAvail)); + + uint32_t cbReadTotal = 0; + + snd_pcm_uframes_t cToRead; + snd_pcm_sframes_t cRead; + + while ( cbToRead + && RT_SUCCESS(rc)) + { + cToRead = RT_MIN(PDMAUDIOSTREAMCFG_B2F(pCfg, cbToRead), + PDMAUDIOSTREAMCFG_B2F(pCfg, pStreamALSA->cbBuf)); + AssertBreakStmt(cToRead, rc = VERR_NO_DATA); + cRead = snd_pcm_readi(pStreamALSA->phPCM, pStreamALSA->pvBuf, cToRead); + if (cRead <= 0) + { + switch (cRead) + { + case 0: + { + LogFunc(("No input frames available\n")); + rc = VERR_ACCESS_DENIED; + break; + } + + case -EAGAIN: + { + /* + * Don't set error here because EAGAIN means there are no further frames + * available at the moment, try later. As we might have read some frames + * already these need to be processed instead. + */ + cbToRead = 0; + break; + } + + case -EPIPE: + { + rc = alsaStreamRecover(pStreamALSA->phPCM); + if (RT_FAILURE(rc)) + break; + + LogFlowFunc(("Recovered from capturing\n")); + continue; + } + + default: + { + LogFunc(("Failed to read input frames: %s\n", snd_strerror(cRead))); + rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */ + break; + } + } + } + else + { + /* + * We should not run into a full mixer buffer or we loose samples and + * run into an endless loop if ALSA keeps producing samples ("null" + * capture device for example). + */ + uint32_t cbRead = PDMAUDIOSTREAMCFG_F2B(pCfg, cRead); + + memcpy(pvBuf, pStreamALSA->pvBuf, cbRead); + + Assert(cbToRead >= cbRead); + cbToRead -= cbRead; + cbReadTotal += cbRead; + } + } + + if (RT_SUCCESS(rc)) + { + if (pcxRead) + *pcxRead = cbReadTotal; + } + + return rc; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHostALSAAudioStreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cxBuf, uint32_t *pcxWritten) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cxBuf, VERR_INVALID_PARAMETER); + /* pcxWritten is optional. */ + + PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; + + PPDMAUDIOSTREAMCFG pCfg = pStreamALSA->pCfg; + AssertPtr(pCfg); + + int rc; + + uint32_t cbWrittenTotal = 0; + + do + { + snd_pcm_sframes_t csAvail; + rc = alsaStreamGetAvail(pStreamALSA->phPCM, &csAvail); + if (RT_FAILURE(rc)) + { + LogFunc(("Error getting number of playback frames, rc=%Rrc\n", rc)); + break; + } + + if (!csAvail) + break; + + size_t cbToWrite = RT_MIN((unsigned)PDMAUDIOSTREAMCFG_F2B(pCfg, csAvail), pStreamALSA->cbBuf); + if (!cbToWrite) + break; + + /* Do not write more than available. */ + if (cbToWrite > cxBuf) + cbToWrite = cxBuf; + + memcpy(pStreamALSA->pvBuf, pvBuf, cbToWrite); + + snd_pcm_sframes_t csWritten = 0; + + /* Don't try infinitely on recoverable errors. */ + unsigned iTry; + for (iTry = 0; iTry < ALSA_RECOVERY_TRIES_MAX; iTry++) + { + csWritten = snd_pcm_writei(pStreamALSA->phPCM, pStreamALSA->pvBuf, + PDMAUDIOSTREAMCFG_B2F(pCfg, cbToWrite)); + if (csWritten <= 0) + { + switch (csWritten) + { + case 0: + { + LogFunc(("Failed to write %zu bytes\n", cbToWrite)); + rc = VERR_ACCESS_DENIED; + break; + } + + case -EPIPE: + { + rc = alsaStreamRecover(pStreamALSA->phPCM); + if (RT_FAILURE(rc)) + break; + + LogFlowFunc(("Recovered from playback\n")); + continue; + } + + case -ESTRPIPE: + { + /* Stream was suspended and waiting for a recovery. */ + rc = alsaStreamResume(pStreamALSA->phPCM); + if (RT_FAILURE(rc)) + { + LogRel(("ALSA: Failed to resume output stream\n")); + break; + } + + LogFlowFunc(("Resumed suspended output stream\n")); + continue; + } + + default: + LogFlowFunc(("Failed to write %RU32 bytes, error unknown\n", cbToWrite)); + rc = VERR_GENERAL_FAILURE; /** @todo */ + break; + } + } + else + break; + } /* For number of tries. */ + + if ( iTry == ALSA_RECOVERY_TRIES_MAX + && csWritten <= 0) + rc = VERR_BROKEN_PIPE; + + if (RT_FAILURE(rc)) + break; + + cbWrittenTotal = PDMAUDIOSTREAMCFG_F2B(pCfg, csWritten); + + } while (0); + + if (RT_SUCCESS(rc)) + { + if (pcxWritten) + *pcxWritten = cbWrittenTotal; + } + + return rc; +} + + +static int alsaDestroyStreamIn(PALSAAUDIOSTREAM pStreamALSA) +{ + alsaStreamClose(&pStreamALSA->phPCM); + + if (pStreamALSA->pvBuf) + { + RTMemFree(pStreamALSA->pvBuf); + pStreamALSA->pvBuf = NULL; + } + + return VINF_SUCCESS; +} + + +static int alsaDestroyStreamOut(PALSAAUDIOSTREAM pStreamALSA) +{ + alsaStreamClose(&pStreamALSA->phPCM); + + if (pStreamALSA->pvBuf) + { + RTMemFree(pStreamALSA->pvBuf); + pStreamALSA->pvBuf = NULL; + } + + return VINF_SUCCESS; +} + + +static int alsaCreateStreamOut(PALSAAUDIOSTREAM pStreamALSA, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + snd_pcm_t *phPCM = NULL; + + int rc; + + do + { + ALSAAUDIOSTREAMCFG req; + req.fmt = alsaAudioPropsToALSA(&pCfgReq->Props); + req.freq = pCfgReq->Props.uHz; + req.nchannels = pCfgReq->Props.cChannels; + req.period_size = pCfgReq->Backend.cfPeriod; + req.buffer_size = pCfgReq->Backend.cfBufferSize; + req.threshold = pCfgReq->Backend.cfPreBuf; + + ALSAAUDIOSTREAMCFG obt; + rc = alsaStreamOpen(false /* fIn */, &req, &obt, &phPCM); + if (RT_FAILURE(rc)) + break; + + pCfgAcq->Props.uHz = obt.freq; + pCfgAcq->Props.cChannels = obt.nchannels; + + rc = alsaALSAToAudioProps(obt.fmt, &pCfgAcq->Props); + if (RT_FAILURE(rc)) + break; + + pCfgAcq->Backend.cfPeriod = obt.period_size; + pCfgAcq->Backend.cfBufferSize = obt.buffer_size; + pCfgAcq->Backend.cfPreBuf = obt.threshold; + + pStreamALSA->cbBuf = pCfgAcq->Backend.cfBufferSize * DrvAudioHlpPCMPropsBytesPerFrame(&pCfgAcq->Props); + pStreamALSA->pvBuf = RTMemAllocZ(pStreamALSA->cbBuf); + if (!pStreamALSA->pvBuf) + { + LogRel(("ALSA: Not enough memory for output DAC buffer (%zu frames)\n", pCfgAcq->Backend.cfBufferSize)); + rc = VERR_NO_MEMORY; + break; + } + + pStreamALSA->phPCM = phPCM; + } + while (0); + + if (RT_FAILURE(rc)) + alsaStreamClose(&phPCM); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +static int alsaCreateStreamIn(PALSAAUDIOSTREAM pStreamALSA, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + int rc; + + snd_pcm_t *phPCM = NULL; + + do + { + ALSAAUDIOSTREAMCFG req; + req.fmt = alsaAudioPropsToALSA(&pCfgReq->Props); + req.freq = pCfgReq->Props.uHz; + req.nchannels = pCfgReq->Props.cChannels; + req.period_size = DrvAudioHlpMilliToFrames(50 /* ms */, &pCfgReq->Props); /** @todo Make this configurable. */ + req.buffer_size = req.period_size * 2; /** @todo Make this configurable. */ + req.threshold = req.period_size; + + ALSAAUDIOSTREAMCFG obt; + rc = alsaStreamOpen(true /* fIn */, &req, &obt, &phPCM); + if (RT_FAILURE(rc)) + break; + + pCfgAcq->Props.uHz = obt.freq; + pCfgAcq->Props.cChannels = obt.nchannels; + + rc = alsaALSAToAudioProps(obt.fmt, &pCfgAcq->Props); + if (RT_FAILURE(rc)) + break; + + pCfgAcq->Backend.cfPeriod = obt.period_size; + pCfgAcq->Backend.cfBufferSize = obt.buffer_size; + /* No pre-buffering. */ + + pStreamALSA->cbBuf = pCfgAcq->Backend.cfBufferSize * DrvAudioHlpPCMPropsBytesPerFrame(&pCfgAcq->Props); + pStreamALSA->pvBuf = RTMemAlloc(pStreamALSA->cbBuf); + if (!pStreamALSA->pvBuf) + { + LogRel(("ALSA: Not enough memory for input ADC buffer (%zu frames)\n", pCfgAcq->Backend.cfBufferSize)); + rc = VERR_NO_MEMORY; + break; + } + + pStreamALSA->phPCM = phPCM; + } + while (0); + + if (RT_FAILURE(rc)) + alsaStreamClose(&phPCM); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +static int alsaControlStreamIn(PALSAAUDIOSTREAM pStreamALSA, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + int rc = VINF_SUCCESS; + + int err; + + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + case PDMAUDIOSTREAMCMD_RESUME: + { + err = snd_pcm_prepare(pStreamALSA->phPCM); + if (err < 0) + { + LogRel(("ALSA: Error preparing input stream: %s\n", snd_strerror(err))); + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + else + { + Assert(snd_pcm_state(pStreamALSA->phPCM) == SND_PCM_STATE_PREPARED); + + /* Only start the PCM stream for input streams. */ + err = snd_pcm_start(pStreamALSA->phPCM); + if (err < 0) + { + LogRel(("ALSA: Error starting input stream: %s\n", snd_strerror(err))); + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + } + + break; + } + + case PDMAUDIOSTREAMCMD_DISABLE: + { + err = snd_pcm_drop(pStreamALSA->phPCM); + if (err < 0) + { + LogRel(("ALSA: Error disabling input stream: %s\n", snd_strerror(err))); + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + break; + } + + case PDMAUDIOSTREAMCMD_PAUSE: + { + err = snd_pcm_drop(pStreamALSA->phPCM); + if (err < 0) + { + LogRel(("ALSA: Error pausing input stream: %s\n", snd_strerror(err))); + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +static int alsaControlStreamOut(PALSAAUDIOSTREAM pStreamALSA, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + int rc = VINF_SUCCESS; + + int err; + + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + case PDMAUDIOSTREAMCMD_RESUME: + { + err = snd_pcm_prepare(pStreamALSA->phPCM); + if (err < 0) + { + LogRel(("ALSA: Error preparing output stream: %s\n", snd_strerror(err))); + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + else + { + Assert(snd_pcm_state(pStreamALSA->phPCM) == SND_PCM_STATE_PREPARED); + } + + break; + } + + case PDMAUDIOSTREAMCMD_DISABLE: + { + err = snd_pcm_drop(pStreamALSA->phPCM); + if (err < 0) + { + LogRel(("ALSA: Error disabling output stream: %s\n", snd_strerror(err))); + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + break; + } + + case PDMAUDIOSTREAMCMD_PAUSE: + { + err = snd_pcm_drop(pStreamALSA->phPCM); + if (err < 0) + { + LogRel(("ALSA: Error pausing output stream: %s\n", snd_strerror(err))); + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + break; + } + + case PDMAUDIOSTREAMCMD_DRAIN: + { + snd_pcm_state_t streamState = snd_pcm_state(pStreamALSA->phPCM); + Log2Func(("Stream state is: %d\n", streamState)); + + if ( streamState == SND_PCM_STATE_PREPARED + || streamState == SND_PCM_STATE_RUNNING) + { + err = snd_pcm_nonblock(pStreamALSA->phPCM, 0); + if (err < 0) + { + LogRel(("ALSA: Error disabling output non-blocking mode: %s\n", snd_strerror(err))); + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + break; + } + + err = snd_pcm_drain(pStreamALSA->phPCM); + if (err < 0) + { + LogRel(("ALSA: Error draining output: %s\n", snd_strerror(err))); + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + break; + } + + err = snd_pcm_nonblock(pStreamALSA->phPCM, 1); + if (err < 0) + { + LogRel(("ALSA: Error re-enabling output non-blocking mode: %s\n", snd_strerror(err))); + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + } + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHostALSAAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "ALSA audio driver"); + + pBackendCfg->cbStreamIn = sizeof(ALSAAUDIOSTREAM); + pBackendCfg->cbStreamOut = sizeof(ALSAAUDIOSTREAM); + + /* Enumerate sound devices. */ + char **pszHints; + int err = snd_device_name_hint(-1 /* All cards */, "pcm", (void***)&pszHints); + if (err == 0) + { + char** pszHintCur = pszHints; + while (*pszHintCur != NULL) + { + char *pszDev = snd_device_name_get_hint(*pszHintCur, "NAME"); + bool fSkip = !pszDev + || !RTStrICmp("null", pszDev); + if (fSkip) + { + if (pszDev) + free(pszDev); + pszHintCur++; + continue; + } + + char *pszIOID = snd_device_name_get_hint(*pszHintCur, "IOID"); + if (pszIOID) + { +#if 0 + if (!RTStrICmp("input", pszIOID)) + + else if (!RTStrICmp("output", pszIOID)) +#endif + } + else /* NULL means bidirectional, input + output. */ + { + } + + LogRel2(("ALSA: Found %s device: %s\n", pszIOID ? RTStrToLower(pszIOID) : "bidirectional", pszDev)); + + /* Special case for ALSAAudio. */ + if ( pszDev + && RTStrIStr("pulse", pszDev) != NULL) + LogRel2(("ALSA: ALSAAudio plugin in use\n")); + + if (pszIOID) + free(pszIOID); + + if (pszDev) + free(pszDev); + + pszHintCur++; + } + + snd_device_name_free_hint((void **)pszHints); + pszHints = NULL; + } + else + LogRel2(("ALSA: Error enumerating PCM devices: %Rrc (%d)\n", RTErrConvertFromErrno(err), err)); + + /* ALSA allows exactly one input and one output used at a time for the selected device(s). */ + pBackendCfg->cMaxStreamsIn = 1; + pBackendCfg->cMaxStreamsOut = 1; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} + */ +static DECLCALLBACK(void) drvHostALSAAudioShutdown(PPDMIHOSTAUDIO pInterface) +{ + RT_NOREF(pInterface); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostALSAAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(enmDir); + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostALSAAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; + + int rc; + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + rc = alsaCreateStreamIn (pStreamALSA, pCfgReq, pCfgAcq); + else + rc = alsaCreateStreamOut(pStreamALSA, pCfgReq, pCfgAcq); + + if (RT_SUCCESS(rc)) + { + pStreamALSA->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); + if (!pStreamALSA->pCfg) + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHostALSAAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; + + if (!pStreamALSA->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc; + if (pStreamALSA->pCfg->enmDir == PDMAUDIODIR_IN) + rc = alsaDestroyStreamIn(pStreamALSA); + else + rc = alsaDestroyStreamOut(pStreamALSA); + + if (RT_SUCCESS(rc)) + { + DrvAudioHlpStreamCfgFree(pStreamALSA->pCfg); + pStreamALSA->pCfg = NULL; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} + */ +static DECLCALLBACK(int) drvHostALSAAudioStreamControl(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; + + if (!pStreamALSA->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc; + if (pStreamALSA->pCfg->enmDir == PDMAUDIODIR_IN) + rc = alsaControlStreamIn (pStreamALSA, enmStreamCmd); + else + rc = alsaControlStreamOut(pStreamALSA, enmStreamCmd); + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostALSAAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + + PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; + + uint32_t cbAvail = 0; + + snd_pcm_sframes_t cFramesAvail; + int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cFramesAvail); + if (RT_SUCCESS(rc)) + cbAvail = PDMAUDIOSTREAMCFG_F2B(pStreamALSA->pCfg, cFramesAvail); + + return cbAvail; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostALSAAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + + PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; + + uint32_t cbAvail = 0; + + snd_pcm_sframes_t cFramesAvail; + int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cFramesAvail); + if (RT_SUCCESS(rc)) + cbAvail = PDMAUDIOSTREAMCFG_F2B(pStreamALSA->pCfg, cFramesAvail); + + return cbAvail; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending} + */ +static DECLCALLBACK(uint32_t) drvHostALSAStreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, 0); + + PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream; + + snd_pcm_sframes_t cfDelay = 0; + snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->phPCM); + + int rc = VINF_SUCCESS; + + AssertPtr(pStreamALSA->pCfg); + if (pStreamALSA->pCfg->enmDir == PDMAUDIODIR_OUT) + { + /* Getting the delay (in audio frames) reports the time it will take + * to hear a new sample after all queued samples have been played out. */ + int rc2 = snd_pcm_delay(pStreamALSA->phPCM, &cfDelay); + if (RT_SUCCESS(rc)) + rc = rc2; + + /* Make sure to check the stream's status. + * If it's anything but SND_PCM_STATE_RUNNING, the delay is meaningless and therefore 0. */ + if (enmState != SND_PCM_STATE_RUNNING) + cfDelay = 0; + } + + /* Note: For input streams we never have pending data left. */ + + Log2Func(("cfDelay=%RI32, enmState=%d, rc=%d\n", cfDelay, enmState, rc)); + + return DrvAudioHlpFramesToBytes(cfDelay, &pStreamALSA->pCfg->Props); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} + */ +static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostALSAAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return (PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} + */ +static DECLCALLBACK(int) drvHostALSAAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + /* Nothing to do here for ALSA. */ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostALSAAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + + return NULL; +} + + +/** + * Construct a DirectSound Audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvHostAlsaAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO); + LogRel(("Audio: Initializing ALSA driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostALSAAudioQueryInterface; + /* IHostAudio */ + PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostALSAAudio); + pThis->IHostAudio.pfnStreamGetPending = drvHostALSAStreamGetPending; + + return VINF_SUCCESS; +} + + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvHostALSAAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "ALSAAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "ALSA host audio driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTALSAAUDIO), + /* pfnConstruct */ + drvHostAlsaAudioConstruct, + /* pfnDestruct */ + NULL, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff --git a/src/VBox/Devices/Audio/DrvHostCoreAudio.cpp b/src/VBox/Devices/Audio/DrvHostCoreAudio.cpp new file mode 100644 index 00000000..45da4242 --- /dev/null +++ b/src/VBox/Devices/Audio/DrvHostCoreAudio.cpp @@ -0,0 +1,2715 @@ +/* $Id: DrvHostCoreAudio.cpp $ */ +/** @file + * VBox audio devices - Mac OS X CoreAudio audio driver. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <VBox/log.h> + +#include "DrvAudio.h" +#include "VBoxDD.h" + +#include <iprt/asm.h> +#include <iprt/cdefs.h> +#include <iprt/circbuf.h> +#include <iprt/mem.h> + +#include <iprt/uuid.h> + +#include <CoreAudio/CoreAudio.h> +#include <CoreServices/CoreServices.h> +#include <AudioUnit/AudioUnit.h> +#include <AudioToolbox/AudioConverter.h> +#include <AudioToolbox/AudioToolbox.h> + + + +/* Enables utilizing the Core Audio converter unit for converting + * input / output from/to our requested formats. That might be more + * performant than using our own routines later down the road. */ +/** @todo Needs more investigation and testing first before enabling. */ +//# define VBOX_WITH_AUDIO_CA_CONVERTER + +/** @todo + * - Maybe make sure the threads are immediately stopped if playing/recording stops. + */ + +/* + * Most of this is based on: + * http://developer.apple.com/mac/library/technotes/tn2004/tn2097.html + * http://developer.apple.com/mac/library/technotes/tn2002/tn2091.html + * http://developer.apple.com/mac/library/qa/qa2007/qa1533.html + * http://developer.apple.com/mac/library/qa/qa2001/qa1317.html + * http://developer.apple.com/mac/library/documentation/AudioUnit/Reference/AUComponentServicesReference/Reference/reference.html + */ + +/* Prototypes needed for COREAUDIODEVICE. */ +struct DRVHOSTCOREAUDIO; +typedef struct DRVHOSTCOREAUDIO *PDRVHOSTCOREAUDIO; + +/** + * Structure for holding Core Audio-specific device data. + * This data then lives in the pvData part of the PDMAUDIODEVICE struct. + */ +typedef struct COREAUDIODEVICEDATA +{ + /** Pointer to driver instance this device is bound to. */ + PDRVHOSTCOREAUDIO pDrv; + /** The audio device ID of the currently used device (UInt32 typedef). */ + AudioDeviceID deviceID; + /** The device' UUID. */ + CFStringRef UUID; + /** List of attached (native) Core Audio streams attached to this device. */ + RTLISTANCHOR lstStreams; +} COREAUDIODEVICEDATA, *PCOREAUDIODEVICEDATA; + +/** + * Host Coreaudio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHOSTCOREAUDIO +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Critical section to serialize access. */ + RTCRITSECT CritSect; + /** Current (last reported) device enumeration. */ + PDMAUDIODEVICEENUM Devices; + /** Pointer to the currently used input device in the device enumeration. + * Can be NULL if none assigned. */ + PPDMAUDIODEVICE pDefaultDevIn; + /** Pointer to the currently used output device in the device enumeration. + * Can be NULL if none assigned. */ + PPDMAUDIODEVICE pDefaultDevOut; +#ifdef VBOX_WITH_AUDIO_CALLBACKS + /** Callback function to the upper driver. + * Can be NULL if not being used / registered. */ + PFNPDMHOSTAUDIOCALLBACK pfnCallback; +#endif +} DRVHOSTCOREAUDIO, *PDRVHOSTCOREAUDIO; + +/** Converts a pointer to DRVHOSTCOREAUDIO::IHostAudio to a PDRVHOSTCOREAUDIO. */ +#define PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface) RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio) + +/** + * Structure for holding a Core Audio unit + * and its data. + */ +typedef struct COREAUDIOUNIT +{ + /** Pointer to the device this audio unit is bound to. + * Can be NULL if not bound to a device (anymore). */ + PPDMAUDIODEVICE pDevice; + /** The actual audio unit object. */ + AudioUnit audioUnit; + /** Stream description for using with VBox: + * - When using this audio unit for input (capturing), this format states + * the unit's output format. + * - When using this audio unit for output (playback), this format states + * the unit's input format. */ + AudioStreamBasicDescription streamFmt; +} COREAUDIOUNIT, *PCOREAUDIOUNIT; + +/******************************************************************************* + * + * Helper function section + * + ******************************************************************************/ + +/* Move these down below the internal function prototypes... */ + +static void coreAudioPrintASBD(const char *pszDesc, const AudioStreamBasicDescription *pASBD) +{ + char pszSampleRate[32]; + LogRel2(("CoreAudio: %s description:\n", pszDesc)); + LogRel2(("CoreAudio:\tFormat ID: %RU32 (%c%c%c%c)\n", pASBD->mFormatID, + RT_BYTE4(pASBD->mFormatID), RT_BYTE3(pASBD->mFormatID), + RT_BYTE2(pASBD->mFormatID), RT_BYTE1(pASBD->mFormatID))); + LogRel2(("CoreAudio:\tFlags: %RU32", pASBD->mFormatFlags)); + if (pASBD->mFormatFlags & kAudioFormatFlagIsFloat) + LogRel2((" Float")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsBigEndian) + LogRel2((" BigEndian")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger) + LogRel2((" SignedInteger")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsPacked) + LogRel2((" Packed")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsAlignedHigh) + LogRel2((" AlignedHigh")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsNonInterleaved) + LogRel2((" NonInterleaved")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsNonMixable) + LogRel2((" NonMixable")); + if (pASBD->mFormatFlags & kAudioFormatFlagsAreAllClear) + LogRel2((" AllClear")); + LogRel2(("\n")); + snprintf(pszSampleRate, 32, "%.2f", (float)pASBD->mSampleRate); /** @todo r=andy Use RTStrPrint*. */ + LogRel2(("CoreAudio:\tSampleRate : %s\n", pszSampleRate)); + LogRel2(("CoreAudio:\tChannelsPerFrame: %RU32\n", pASBD->mChannelsPerFrame)); + LogRel2(("CoreAudio:\tFramesPerPacket : %RU32\n", pASBD->mFramesPerPacket)); + LogRel2(("CoreAudio:\tBitsPerChannel : %RU32\n", pASBD->mBitsPerChannel)); + LogRel2(("CoreAudio:\tBytesPerFrame : %RU32\n", pASBD->mBytesPerFrame)); + LogRel2(("CoreAudio:\tBytesPerPacket : %RU32\n", pASBD->mBytesPerPacket)); +} + +static void coreAudioPCMPropsToASBD(PDMAUDIOPCMPROPS *pPCMProps, AudioStreamBasicDescription *pASBD) +{ + AssertPtrReturnVoid(pPCMProps); + AssertPtrReturnVoid(pASBD); + + RT_BZERO(pASBD, sizeof(AudioStreamBasicDescription)); + + pASBD->mFormatID = kAudioFormatLinearPCM; + pASBD->mFormatFlags = kAudioFormatFlagIsPacked; + pASBD->mFramesPerPacket = 1; /* For uncompressed audio, set this to 1. */ + pASBD->mSampleRate = (Float64)pPCMProps->uHz; + pASBD->mChannelsPerFrame = pPCMProps->cChannels; + pASBD->mBitsPerChannel = pPCMProps->cBytes * 8; + if (pPCMProps->fSigned) + pASBD->mFormatFlags |= kAudioFormatFlagIsSignedInteger; + pASBD->mBytesPerFrame = pASBD->mChannelsPerFrame * (pASBD->mBitsPerChannel / 8); + pASBD->mBytesPerPacket = pASBD->mFramesPerPacket * pASBD->mBytesPerFrame; +} + +#ifndef VBOX_WITH_AUDIO_CALLBACKS +static int coreAudioASBDToStreamCfg(AudioStreamBasicDescription *pASBD, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pASBD, VERR_INVALID_PARAMETER); + AssertPtrReturn(pCfg, VERR_INVALID_PARAMETER); + + pCfg->Props.cChannels = pASBD->mChannelsPerFrame; + pCfg->Props.uHz = (uint32_t)pASBD->mSampleRate; + pCfg->Props.cBytes = pASBD->mBitsPerChannel / 8; + pCfg->Props.fSigned = RT_BOOL(pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger); + pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cBytes, pCfg->Props.cChannels); + + return VINF_SUCCESS; +} +#endif /* !VBOX_WITH_AUDIO_CALLBACKS */ + +#if 0 /* unused */ +static int coreAudioCFStringToCString(const CFStringRef pCFString, char **ppszString) +{ + CFIndex cLen = CFStringGetLength(pCFString) + 1; + char *pszResult = (char *)RTMemAllocZ(cLen * sizeof(char)); + if (!CFStringGetCString(pCFString, pszResult, cLen, kCFStringEncodingUTF8)) + { + RTMemFree(pszResult); + return VERR_NOT_FOUND; + } + + *ppszString = pszResult; + return VINF_SUCCESS; +} + +static AudioDeviceID coreAudioDeviceUIDtoID(const char* pszUID) +{ + /* Create a CFString out of our CString. */ + CFStringRef strUID = CFStringCreateWithCString(NULL, pszUID, kCFStringEncodingMacRoman); + + /* Fill the translation structure. */ + AudioDeviceID deviceID; + + AudioValueTranslation translation; + translation.mInputData = &strUID; + translation.mInputDataSize = sizeof(CFStringRef); + translation.mOutputData = &deviceID; + translation.mOutputDataSize = sizeof(AudioDeviceID); + + /* Fetch the translation from the UID to the device ID. */ + AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDeviceForUID, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + UInt32 uSize = sizeof(AudioValueTranslation); + OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdr, 0, NULL, &uSize, &translation); + + /* Release the temporary CFString */ + CFRelease(strUID); + + if (RT_LIKELY(err == noErr)) + return deviceID; + + /* Return the unknown device on error. */ + return kAudioDeviceUnknown; +} +#endif /* unused */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** @name Initialization status indicator used for the recreation of the AudioUnits. + * + * Global structures section + * + ******************************************************************************/ + +/** + * Enumeration for a Core Audio stream status. + */ +typedef enum COREAUDIOSTATUS +{ + /** The device is uninitialized. */ + COREAUDIOSTATUS_UNINIT = 0, + /** The device is currently initializing. */ + COREAUDIOSTATUS_IN_INIT, + /** The device is initialized. */ + COREAUDIOSTATUS_INIT, + /** The device is currently uninitializing. */ + COREAUDIOSTATUS_IN_UNINIT, +#ifndef VBOX_WITH_AUDIO_CALLBACKS + /** The device has to be reinitialized. + * Note: Only needed if VBOX_WITH_AUDIO_CALLBACKS is not defined, as otherwise + * the Audio Connector will take care of this as soon as this backend + * tells it to do so via the provided audio callback. */ + COREAUDIOSTATUS_REINIT, +#endif + /** The usual 32-bit hack. */ + COREAUDIOSTATUS_32BIT_HACK = 0x7fffffff +} COREAUDIOSTATUS, *PCOREAUDIOSTATUS; + +#ifdef VBOX_WITH_AUDIO_CA_CONVERTER + /* Error code which indicates "End of data" */ + static const OSStatus caConverterEOFDErr = 0x656F6664; /* 'eofd' */ +#endif + +/* Prototypes needed for COREAUDIOSTREAMCBCTX. */ +struct COREAUDIOSTREAM; +typedef struct COREAUDIOSTREAM *PCOREAUDIOSTREAM; + +/** + * Structure for keeping a conversion callback context. + * This is needed when using an audio converter during input/output processing. + */ +typedef struct COREAUDIOCONVCBCTX +{ + /** Pointer to the stream this context is bound to. */ + PCOREAUDIOSTREAM pStream; + /** Source stream description. */ + AudioStreamBasicDescription asbdSrc; + /** Destination stream description. */ + AudioStreamBasicDescription asbdDst; + /** Pointer to native buffer list used for rendering the source audio data into. */ + AudioBufferList *pBufLstSrc; + /** Total packet conversion count. */ + UInt32 uPacketCnt; + /** Current packet conversion index. */ + UInt32 uPacketIdx; + /** Error count, for limiting the logging. */ + UInt32 cErrors; +} COREAUDIOCONVCBCTX, *PCOREAUDIOCONVCBCTX; + +/** + * Structure for keeping the input stream specifics. + */ +typedef struct COREAUDIOSTREAMIN +{ +#ifdef VBOX_WITH_AUDIO_CA_CONVERTER + /** The audio converter if necessary. NULL if no converter is being used. */ + AudioConverterRef ConverterRef; + /** Callback context for the audio converter. */ + COREAUDIOCONVCBCTX convCbCtx; +#endif + /** The ratio between the device & the stream sample rate. */ + Float64 sampleRatio; +} COREAUDIOSTREAMIN, *PCOREAUDIOSTREAMIN; + +/** + * Structure for keeping the output stream specifics. + */ +typedef struct COREAUDIOSTREAMOUT +{ + /** Nothing here yet. */ +} COREAUDIOSTREAMOUT, *PCOREAUDIOSTREAMOUT; + +/** + * Structure for maintaining a Core Audio stream. + */ +typedef struct COREAUDIOSTREAM +{ + /** The stream's acquired configuration. */ + PPDMAUDIOSTREAMCFG pCfg; + /** Stream-specific data, depending on the stream type. */ + union + { + COREAUDIOSTREAMIN In; + COREAUDIOSTREAMOUT Out; + }; + /** List node for the device's stream list. */ + RTLISTNODE Node; + /** Pointer to driver instance this stream is bound to. */ + PDRVHOSTCOREAUDIO pDrv; + /** The stream's thread handle for maintaining the audio queue. */ + RTTHREAD hThread; + /** Flag indicating to start a stream's data processing. */ + bool fRun; + /** Whether the stream is in a running (active) state or not. + * For playback streams this means that audio data can be (or is being) played, + * for capturing streams this means that audio data is being captured (if available). */ + bool fIsRunning; + /** Thread shutdown indicator. */ + bool fShutdown; + /** Critical section for serializing access between thread + callbacks. */ + RTCRITSECT CritSect; + /** The actual audio queue being used. */ + AudioQueueRef audioQueue; + /** The audio buffers which are used with the above audio queue. */ + AudioQueueBufferRef audioBuffer[2]; + /** The acquired (final) audio format for this stream. */ + AudioStreamBasicDescription asbdStream; + /** The audio unit for this stream. */ + COREAUDIOUNIT Unit; + /** Initialization status tracker. Used when some of the device parameters + * or the device itself is changed during the runtime. */ + volatile uint32_t enmStatus; + /** An internal ring buffer for transferring data from/to the rendering callbacks. */ + PRTCIRCBUF pCircBuf; +} COREAUDIOSTREAM, *PCOREAUDIOSTREAM; + +static int coreAudioStreamInit(PCOREAUDIOSTREAM pCAStream, PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev); +#ifndef VBOX_WITH_AUDIO_CALLBACKS +static int coreAudioStreamReinit(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PPDMAUDIODEVICE pDev); +#endif +static int coreAudioStreamUninit(PCOREAUDIOSTREAM pCAStream); + +static int coreAudioStreamControl(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PDMAUDIOSTREAMCMD enmStreamCmd); + +static int coreAudioDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev); +static int coreAudioDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev); +static void coreAudioDeviceDataInit(PCOREAUDIODEVICEDATA pDevData, AudioDeviceID deviceID, bool fIsInput, PDRVHOSTCOREAUDIO pDrv); + +static OSStatus coreAudioDevPropChgCb(AudioObjectID propertyID, UInt32 nAddresses, const AudioObjectPropertyAddress properties[], void *pvUser); + +static int coreAudioStreamInitQueue(PCOREAUDIOSTREAM pCAStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq); +static void coreAudioInputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer, const AudioTimeStamp *pAudioTS, UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc); +static void coreAudioOutputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer); + +#ifdef VBOX_WITH_AUDIO_CA_CONVERTER +/** + * Initializes a conversion callback context. + * + * @return IPRT status code. + * @param pConvCbCtx Conversion callback context to initialize. + * @param pStream Pointer to stream to use. + * @param pASBDSrc Input (source) stream description to use. + * @param pASBDDst Output (destination) stream description to use. + */ +static int coreAudioInitConvCbCtx(PCOREAUDIOCONVCBCTX pConvCbCtx, PCOREAUDIOSTREAM pStream, + AudioStreamBasicDescription *pASBDSrc, AudioStreamBasicDescription *pASBDDst) +{ + AssertPtrReturn(pConvCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pASBDSrc, VERR_INVALID_POINTER); + AssertPtrReturn(pASBDDst, VERR_INVALID_POINTER); + +#ifdef DEBUG + coreAudioPrintASBD("CbCtx: Src", pASBDSrc); + coreAudioPrintASBD("CbCtx: Dst", pASBDDst); +#endif + + pConvCbCtx->pStream = pStream; + + memcpy(&pConvCbCtx->asbdSrc, pASBDSrc, sizeof(AudioStreamBasicDescription)); + memcpy(&pConvCbCtx->asbdDst, pASBDDst, sizeof(AudioStreamBasicDescription)); + + pConvCbCtx->pBufLstSrc = NULL; + pConvCbCtx->cErrors = 0; + + return VINF_SUCCESS; +} + + +/** + * Uninitializes a conversion callback context. + * + * @return IPRT status code. + * @param pConvCbCtx Conversion callback context to uninitialize. + */ +static void coreAudioUninitConvCbCtx(PCOREAUDIOCONVCBCTX pConvCbCtx) +{ + AssertPtrReturnVoid(pConvCbCtx); + + pConvCbCtx->pStream = NULL; + + RT_ZERO(pConvCbCtx->asbdSrc); + RT_ZERO(pConvCbCtx->asbdDst); + + pConvCbCtx->pBufLstSrc = NULL; + pConvCbCtx->cErrors = 0; +} +#endif /* VBOX_WITH_AUDIO_CA_CONVERTER */ + + +/** + * Does a (re-)enumeration of the host's playback + recording devices. + * + * @return IPRT status code. + * @param pThis Host audio driver instance. + * @param enmUsage Which devices to enumerate. + * @param pDevEnm Where to store the enumerated devices. + */ +static int coreAudioDevicesEnumerate(PDRVHOSTCOREAUDIO pThis, PDMAUDIODIR enmUsage, PPDMAUDIODEVICEENUM pDevEnm) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pDevEnm, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + do + { + AudioDeviceID defaultDeviceID = kAudioDeviceUnknown; + + /* Fetch the default audio device currently in use. */ + AudioObjectPropertyAddress propAdrDefaultDev = { enmUsage == PDMAUDIODIR_IN + ? kAudioHardwarePropertyDefaultInputDevice + : kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + UInt32 uSize = sizeof(defaultDeviceID); + OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdrDefaultDev, 0, NULL, &uSize, &defaultDeviceID); + if (err != noErr) + { + LogRel(("CoreAudio: Unable to determine default %s device (%RI32)\n", + enmUsage == PDMAUDIODIR_IN ? "capturing" : "playback", err)); + return VERR_NOT_FOUND; + } + + if (defaultDeviceID == kAudioDeviceUnknown) + { + LogFunc(("No default %s device found\n", enmUsage == PDMAUDIODIR_IN ? "capturing" : "playback")); + /* Keep going. */ + } + + AudioObjectPropertyAddress propAdrDevList = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propAdrDevList, 0, NULL, &uSize); + if (err != kAudioHardwareNoError) + break; + + AudioDeviceID *pDevIDs = (AudioDeviceID *)alloca(uSize); + if (pDevIDs == NULL) + break; + + err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdrDevList, 0, NULL, &uSize, pDevIDs); + if (err != kAudioHardwareNoError) + break; + + rc = DrvAudioHlpDeviceEnumInit(pDevEnm); + if (RT_FAILURE(rc)) + break; + + UInt16 cDevices = uSize / sizeof(AudioDeviceID); + + PPDMAUDIODEVICE pDev = NULL; + for (UInt16 i = 0; i < cDevices; i++) + { + if (pDev) /* Some (skipped) device to clean up first? */ + DrvAudioHlpDeviceFree(pDev); + + pDev = DrvAudioHlpDeviceAlloc(sizeof(COREAUDIODEVICEDATA)); + if (!pDev) + { + rc = VERR_NO_MEMORY; + break; + } + + /* Set usage. */ + pDev->enmUsage = enmUsage; + + /* Init backend-specific device data. */ + PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pDev->pvData; + AssertPtr(pDevData); + coreAudioDeviceDataInit(pDevData, pDevIDs[i], enmUsage == PDMAUDIODIR_IN, pThis); + + /* Check if the device is valid. */ + AudioDeviceID curDevID = pDevData->deviceID; + + /* Is the device the default device? */ + if (curDevID == defaultDeviceID) + pDev->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT; + + AudioObjectPropertyAddress propAddrCfg = { kAudioDevicePropertyStreamConfiguration, + enmUsage == PDMAUDIODIR_IN + ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster }; + + err = AudioObjectGetPropertyDataSize(curDevID, &propAddrCfg, 0, NULL, &uSize); + if (err != noErr) + continue; + + AudioBufferList *pBufList = (AudioBufferList *)RTMemAlloc(uSize); + if (!pBufList) + continue; + + err = AudioObjectGetPropertyData(curDevID, &propAddrCfg, 0, NULL, &uSize, pBufList); + if (err == noErr) + { + for (UInt32 a = 0; a < pBufList->mNumberBuffers; a++) + { + if (enmUsage == PDMAUDIODIR_IN) + pDev->cMaxInputChannels += pBufList->mBuffers[a].mNumberChannels; + else if (enmUsage == PDMAUDIODIR_OUT) + pDev->cMaxOutputChannels += pBufList->mBuffers[a].mNumberChannels; + } + } + + if (pBufList) + { + RTMemFree(pBufList); + pBufList = NULL; + } + + /* Check if the device is valid, e.g. has any input/output channels according to its usage. */ + if ( enmUsage == PDMAUDIODIR_IN + && !pDev->cMaxInputChannels) + { + continue; + } + else if ( enmUsage == PDMAUDIODIR_OUT + && !pDev->cMaxOutputChannels) + { + continue; + } + + /* Resolve the device's name. */ + AudioObjectPropertyAddress propAddrName = { kAudioObjectPropertyName, + enmUsage == PDMAUDIODIR_IN + ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster }; + uSize = sizeof(CFStringRef); + CFStringRef pcfstrName = NULL; + + err = AudioObjectGetPropertyData(curDevID, &propAddrName, 0, NULL, &uSize, &pcfstrName); + if (err != kAudioHardwareNoError) + continue; + + CFIndex uMax = CFStringGetMaximumSizeForEncoding(CFStringGetLength(pcfstrName), kCFStringEncodingUTF8) + 1; + if (uMax) + { + char *pszName = (char *)RTStrAlloc(uMax); + if ( pszName + && CFStringGetCString(pcfstrName, pszName, uMax, kCFStringEncodingUTF8)) + { + RTStrPrintf(pDev->szName, sizeof(pDev->szName), "%s", pszName); + } + + LogFunc(("Device '%s': %RU32\n", pszName, curDevID)); + + if (pszName) + { + RTStrFree(pszName); + pszName = NULL; + } + } + + CFRelease(pcfstrName); + + /* Check if the device is alive for the intended usage. */ + AudioObjectPropertyAddress propAddrAlive = { kAudioDevicePropertyDeviceIsAlive, + enmUsage == PDMAUDIODIR_IN + ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster }; + + UInt32 uAlive = 0; + uSize = sizeof(uAlive); + + err = AudioObjectGetPropertyData(curDevID, &propAddrAlive, 0, NULL, &uSize, &uAlive); + if ( (err == noErr) + && !uAlive) + { + pDev->fFlags |= PDMAUDIODEV_FLAGS_DEAD; + } + + /* Check if the device is being hogged by someone else. */ + AudioObjectPropertyAddress propAddrHogged = { kAudioDevicePropertyHogMode, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + + pid_t pid = 0; + uSize = sizeof(pid); + + err = AudioObjectGetPropertyData(curDevID, &propAddrHogged, 0, NULL, &uSize, &pid); + if ( (err == noErr) + && (pid != -1)) + { + pDev->fFlags |= PDMAUDIODEV_FLAGS_LOCKED; + } + + /* Add the device to the enumeration. */ + rc = DrvAudioHlpDeviceEnumAdd(pDevEnm, pDev); + if (RT_FAILURE(rc)) + break; + + /* NULL device pointer because it's now part of the device enumeration. */ + pDev = NULL; + } + + if (RT_FAILURE(rc)) + { + DrvAudioHlpDeviceFree(pDev); + pDev = NULL; + } + + } while (0); + + if (RT_SUCCESS(rc)) + { +#ifdef DEBUG + LogFunc(("Devices for pDevEnm=%p, enmUsage=%RU32:\n", pDevEnm, enmUsage)); + DrvAudioHlpDeviceEnumPrint("Core Audio", pDevEnm); +#endif + } + else + DrvAudioHlpDeviceEnumFree(pDevEnm); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Checks if an audio device with a specific device ID is in the given device + * enumeration or not. + * + * @retval true if the node is the last element in the list. + * @retval false otherwise. + * + * @param pEnmSrc Device enumeration to search device ID in. + * @param deviceID Device ID to search. + */ +bool coreAudioDevicesHasDevice(PPDMAUDIODEVICEENUM pEnmSrc, AudioDeviceID deviceID) +{ + PPDMAUDIODEVICE pDevSrc; + RTListForEach(&pEnmSrc->lstDevices, pDevSrc, PDMAUDIODEVICE, Node) + { + PCOREAUDIODEVICEDATA pDevSrcData = (PCOREAUDIODEVICEDATA)pDevSrc->pvData; + AssertPtr(pDevSrcData); + + if (pDevSrcData->deviceID == deviceID) + return true; + } + + return false; +} + + +/** + * Enumerates all host devices and builds a final device enumeration list, consisting + * of (duplex) input and output devices. + * + * @return IPRT status code. + * @param pThis Host audio driver instance. + * @param pEnmDst Where to store the device enumeration list. + */ +int coreAudioDevicesEnumerateAll(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICEENUM pEnmDst) +{ + PDMAUDIODEVICEENUM devEnmIn; + int rc = coreAudioDevicesEnumerate(pThis, PDMAUDIODIR_IN, &devEnmIn); + if (RT_SUCCESS(rc)) + { + PDMAUDIODEVICEENUM devEnmOut; + rc = coreAudioDevicesEnumerate(pThis, PDMAUDIODIR_OUT, &devEnmOut); + if (RT_SUCCESS(rc)) + { + /* + * Build up the final device enumeration, based on the input and output device lists + * just enumerated. + * + * Also make sure to handle duplex devices, that is, devices which act as input and output + * at the same time. + */ + + rc = DrvAudioHlpDeviceEnumInit(pEnmDst); + if (RT_SUCCESS(rc)) + { + PPDMAUDIODEVICE pDevSrcIn; + RTListForEach(&devEnmIn.lstDevices, pDevSrcIn, PDMAUDIODEVICE, Node) + { + PCOREAUDIODEVICEDATA pDevSrcInData = (PCOREAUDIODEVICEDATA)pDevSrcIn->pvData; + AssertPtr(pDevSrcInData); + + PPDMAUDIODEVICE pDevDst = DrvAudioHlpDeviceAlloc(sizeof(COREAUDIODEVICEDATA)); + if (!pDevDst) + { + rc = VERR_NO_MEMORY; + break; + } + + PCOREAUDIODEVICEDATA pDevDstData = (PCOREAUDIODEVICEDATA)pDevDst->pvData; + AssertPtr(pDevDstData); + coreAudioDeviceDataInit(pDevDstData, pDevSrcInData->deviceID, true /* fIsInput */, pThis); + + RTStrCopy(pDevDst->szName, sizeof(pDevDst->szName), pDevSrcIn->szName); + + pDevDst->enmUsage = PDMAUDIODIR_IN; /* Input device by default (simplex). */ + pDevDst->cMaxInputChannels = pDevSrcIn->cMaxInputChannels; + + /* Handle flags. */ + if (pDevSrcIn->fFlags & PDMAUDIODEV_FLAGS_DEFAULT) + pDevDst->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT; + /** @todo Handle hot plugging? */ + + /* + * Now search through the list of all found output devices and check if we found + * an output device with the same device ID as the currently handled input device. + * + * If found, this means we have to treat that device as a duplex device then. + */ + PPDMAUDIODEVICE pDevSrcOut; + RTListForEach(&devEnmOut.lstDevices, pDevSrcOut, PDMAUDIODEVICE, Node) + { + PCOREAUDIODEVICEDATA pDevSrcOutData = (PCOREAUDIODEVICEDATA)pDevSrcOut->pvData; + AssertPtr(pDevSrcOutData); + + if (pDevSrcInData->deviceID == pDevSrcOutData->deviceID) + { + pDevDst->enmUsage = PDMAUDIODIR_ANY; + pDevDst->cMaxOutputChannels = pDevSrcOut->cMaxOutputChannels; + break; + } + } + + if (RT_SUCCESS(rc)) + { + rc = DrvAudioHlpDeviceEnumAdd(pEnmDst, pDevDst); + } + else + { + DrvAudioHlpDeviceFree(pDevDst); + pDevDst = NULL; + } + } + + if (RT_SUCCESS(rc)) + { + /* + * As a last step, add all remaining output devices which have not been handled in the loop above, + * that is, all output devices which operate in simplex mode. + */ + PPDMAUDIODEVICE pDevSrcOut; + RTListForEach(&devEnmOut.lstDevices, pDevSrcOut, PDMAUDIODEVICE, Node) + { + PCOREAUDIODEVICEDATA pDevSrcOutData = (PCOREAUDIODEVICEDATA)pDevSrcOut->pvData; + AssertPtr(pDevSrcOutData); + + if (coreAudioDevicesHasDevice(pEnmDst, pDevSrcOutData->deviceID)) + continue; /* Already in our list, skip. */ + + PPDMAUDIODEVICE pDevDst = DrvAudioHlpDeviceAlloc(sizeof(COREAUDIODEVICEDATA)); + if (!pDevDst) + { + rc = VERR_NO_MEMORY; + break; + } + + PCOREAUDIODEVICEDATA pDevDstData = (PCOREAUDIODEVICEDATA)pDevDst->pvData; + AssertPtr(pDevDstData); + coreAudioDeviceDataInit(pDevDstData, pDevSrcOutData->deviceID, false /* fIsInput */, pThis); + + RTStrCopy(pDevDst->szName, sizeof(pDevDst->szName), pDevSrcOut->szName); + + pDevDst->enmUsage = PDMAUDIODIR_OUT; + pDevDst->cMaxOutputChannels = pDevSrcOut->cMaxOutputChannels; + + pDevDstData->deviceID = pDevSrcOutData->deviceID; + + /* Handle flags. */ + if (pDevSrcOut->fFlags & PDMAUDIODEV_FLAGS_DEFAULT) + pDevDst->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT; + /** @todo Handle hot plugging? */ + + rc = DrvAudioHlpDeviceEnumAdd(pEnmDst, pDevDst); + if (RT_FAILURE(rc)) + { + DrvAudioHlpDeviceFree(pDevDst); + break; + } + } + } + + if (RT_FAILURE(rc)) + DrvAudioHlpDeviceEnumFree(pEnmDst); + } + + DrvAudioHlpDeviceEnumFree(&devEnmOut); + } + + DrvAudioHlpDeviceEnumFree(&devEnmIn); + } + +#ifdef DEBUG + if (RT_SUCCESS(rc)) + DrvAudioHlpDeviceEnumPrint("Core Audio (Final)", pEnmDst); +#endif + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Initializes a Core Audio-specific device data structure. + * + * @returns IPRT status code. + * @param pDevData Device data structure to initialize. + * @param deviceID Core Audio device ID to assign this structure to. + * @param fIsInput Whether this is an input device or not. + * @param pDrv Driver instance to use. + */ +static void coreAudioDeviceDataInit(PCOREAUDIODEVICEDATA pDevData, AudioDeviceID deviceID, bool fIsInput, PDRVHOSTCOREAUDIO pDrv) +{ + AssertPtrReturnVoid(pDevData); + AssertPtrReturnVoid(pDrv); + + pDevData->deviceID = deviceID; + pDevData->pDrv = pDrv; + + /* Get the device UUID. */ + AudioObjectPropertyAddress propAdrDevUUID = { kAudioDevicePropertyDeviceUID, + fIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster }; + UInt32 uSize = sizeof(pDevData->UUID); + OSStatus err = AudioObjectGetPropertyData(pDevData->deviceID, &propAdrDevUUID, 0, NULL, &uSize, &pDevData->UUID); + if (err != noErr) + LogRel(("CoreAudio: Failed to retrieve device UUID for device %RU32 (%RI32)\n", deviceID, err)); + + RTListInit(&pDevData->lstStreams); +} + + +/** + * Propagates an audio device status to all its connected Core Audio streams. + * + * @return IPRT status code. + * @param pDev Audio device to propagate status for. + * @param enmSts Status to propagate. + */ +static int coreAudioDevicePropagateStatus(PPDMAUDIODEVICE pDev, COREAUDIOSTATUS enmSts) +{ + AssertPtrReturn(pDev, VERR_INVALID_POINTER); + + PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pDev->pvData; + AssertPtrReturn(pDevData, VERR_INVALID_POINTER); + + /* Sanity. */ + AssertPtr(pDevData->pDrv); + + LogFlowFunc(("pDev=%p, pDevData=%p, enmSts=%RU32\n", pDev, pDevData, enmSts)); + + PCOREAUDIOSTREAM pCAStream; + RTListForEach(&pDevData->lstStreams, pCAStream, COREAUDIOSTREAM, Node) + { + LogFlowFunc(("pCAStream=%p\n", pCAStream)); + + /* We move the reinitialization to the next output event. + * This make sure this thread isn't blocked and the + * reinitialization is done when necessary only. */ + ASMAtomicXchgU32(&pCAStream->enmStatus, enmSts); + } + + return VINF_SUCCESS; +} + + +static DECLCALLBACK(OSStatus) coreAudioDeviceStateChangedCb(AudioObjectID propertyID, + UInt32 nAddresses, + const AudioObjectPropertyAddress properties[], + void *pvUser) +{ + RT_NOREF(propertyID, nAddresses, properties); + + LogFlowFunc(("propertyID=%u, nAddresses=%u, pvUser=%p\n", propertyID, nAddresses, pvUser)); + + PPDMAUDIODEVICE pDev = (PPDMAUDIODEVICE)pvUser; + AssertPtr(pDev); + + PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData; + AssertPtrReturn(pData, VERR_INVALID_POINTER); + + PDRVHOSTCOREAUDIO pThis = pData->pDrv; + AssertPtr(pThis); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc2); + + UInt32 uAlive = 1; + UInt32 uSize = sizeof(UInt32); + + AudioObjectPropertyAddress propAdr = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + AudioDeviceID deviceID = pData->deviceID; + + OSStatus err = AudioObjectGetPropertyData(deviceID, &propAdr, 0, NULL, &uSize, &uAlive); + + bool fIsDead = false; + + if (err == kAudioHardwareBadDeviceError) + fIsDead = true; /* Unplugged. */ + else if ((err == kAudioHardwareNoError) && (!RT_BOOL(uAlive))) + fIsDead = true; /* Something else happened. */ + + if (fIsDead) + { + LogRel2(("CoreAudio: Device '%s' stopped functioning\n", pDev->szName)); + + /* Mark device as dead. */ + rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_UNINIT); + AssertRC(rc2); + } + + rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + + return noErr; +} + +/* Callback for getting notified when the default recording/playback device has been changed. */ +static DECLCALLBACK(OSStatus) coreAudioDefaultDeviceChangedCb(AudioObjectID propertyID, + UInt32 nAddresses, + const AudioObjectPropertyAddress properties[], + void *pvUser) +{ + RT_NOREF(propertyID, nAddresses); + + LogFlowFunc(("propertyID=%u, nAddresses=%u, pvUser=%p\n", propertyID, nAddresses, pvUser)); + + PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser; + AssertPtr(pThis); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc2); + + for (UInt32 idxAddress = 0; idxAddress < nAddresses; idxAddress++) + { + PPDMAUDIODEVICE pDev = NULL; + + /* + * Check if the default input / output device has been changed. + */ + const AudioObjectPropertyAddress *pProperty = &properties[idxAddress]; + switch (pProperty->mSelector) + { + case kAudioHardwarePropertyDefaultInputDevice: + LogFlowFunc(("kAudioHardwarePropertyDefaultInputDevice\n")); + pDev = pThis->pDefaultDevIn; + break; + + case kAudioHardwarePropertyDefaultOutputDevice: + LogFlowFunc(("kAudioHardwarePropertyDefaultOutputDevice\n")); + pDev = pThis->pDefaultDevOut; + break; + + default: + /* Skip others. */ + break; + } + + LogFlowFunc(("pDev=%p\n", pDev)); + +#ifndef VBOX_WITH_AUDIO_CALLBACKS + if (pDev) + { + PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData; + AssertPtr(pData); + + /* This listener is called on every change of the hardware + * device. So check if the default device has really changed. */ + UInt32 uSize = sizeof(AudioDeviceID); + UInt32 uResp = 0; + + OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, pProperty, 0, NULL, &uSize, &uResp); + if (err == noErr) + { + if (pData->deviceID != uResp) /* Has the device ID changed? */ + { + rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_REINIT); + AssertRC(rc2); + } + } + } +#endif /* VBOX_WITH_AUDIO_CALLBACKS */ + } + +#ifdef VBOX_WITH_AUDIO_CALLBACKS + PFNPDMHOSTAUDIOCALLBACK pfnCallback = pThis->pfnCallback; +#endif + + /* Make sure to leave the critical section before calling the callback. */ + rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + +#ifdef VBOX_WITH_AUDIO_CALLBACKS + if (pfnCallback) + /* Ignore rc */ pfnCallback(pThis->pDrvIns, PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED, NULL, 0); +#endif + + return noErr; +} + +#ifndef VBOX_WITH_AUDIO_CALLBACKS +/** + * Re-initializes a Core Audio stream with a specific audio device and stream configuration. + * + * @return IPRT status code. + * @param pThis Driver instance. + * @param pCAStream Audio stream to re-initialize. + * @param pDev Audio device to use for re-initialization. + * @param pCfg Stream configuration to use for re-initialization. + */ +static int coreAudioStreamReinitEx(PDRVHOSTCOREAUDIO pThis, + PCOREAUDIOSTREAM pCAStream, PPDMAUDIODEVICE pDev, PPDMAUDIOSTREAMCFG pCfg) +{ + LogFunc(("pCAStream=%p\n", pCAStream)); + + int rc = coreAudioStreamUninit(pCAStream); + if (RT_SUCCESS(rc)) + { + rc = coreAudioStreamInit(pCAStream, pThis, pDev); + if (RT_SUCCESS(rc)) + { + rc = coreAudioStreamInitQueue(pCAStream, pCfg /* pCfgReq */, NULL /* pCfgAcq */); + if (RT_SUCCESS(rc)) + rc = coreAudioStreamControl(pCAStream->pDrv, pCAStream, PDMAUDIOSTREAMCMD_ENABLE); + + if (RT_FAILURE(rc)) + { + int rc2 = coreAudioStreamUninit(pCAStream); + AssertRC(rc2); + } + } + } + + if (RT_FAILURE(rc)) + LogRel(("CoreAudio: Unable to re-init stream: %Rrc\n", rc)); + + return rc; +} + +/** + * Re-initializes a Core Audio stream with a specific audio device. + * + * @return IPRT status code. + * @param pThis Driver instance. + * @param pCAStream Audio stream to re-initialize. + * @param pDev Audio device to use for re-initialization. + */ +static int coreAudioStreamReinit(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PPDMAUDIODEVICE pDev) +{ + int rc = coreAudioStreamUninit(pCAStream); + if (RT_SUCCESS(rc)) + { + /* Use the acquired stream configuration from the former initialization to + * re-initialize the stream. */ + PDMAUDIOSTREAMCFG CfgAcq; + rc = coreAudioASBDToStreamCfg(&pCAStream->Unit.streamFmt, &CfgAcq); + if (RT_SUCCESS(rc)) + rc = coreAudioStreamReinitEx(pThis, pCAStream, pDev, &CfgAcq); + } + + return rc; +} +#endif /* !VBOX_WITH_AUDIO_CALLBACKS */ + +#ifdef VBOX_WITH_AUDIO_CA_CONVERTER +/* Callback to convert audio input data from one format to another. */ +static DECLCALLBACK(OSStatus) coreAudioConverterCb(AudioConverterRef inAudioConverter, + UInt32 *ioNumberDataPackets, + AudioBufferList *ioData, + AudioStreamPacketDescription **ppASPD, + void *pvUser) +{ + RT_NOREF(inAudioConverter); + + AssertPtrReturn(ioNumberDataPackets, caConverterEOFDErr); + AssertPtrReturn(ioData, caConverterEOFDErr); + + PCOREAUDIOCONVCBCTX pConvCbCtx = (PCOREAUDIOCONVCBCTX)pvUser; + AssertPtr(pConvCbCtx); + + /* Initialize values. */ + ioData->mBuffers[0].mNumberChannels = 0; + ioData->mBuffers[0].mDataByteSize = 0; + ioData->mBuffers[0].mData = NULL; + + if (ppASPD) + { + Log3Func(("Handling packet description not implemented\n")); + } + else + { + /** @todo Check converter ID? */ + + /** @todo Handled non-interleaved data by going through the full buffer list, + * not only through the first buffer like we do now. */ + Log3Func(("ioNumberDataPackets=%RU32\n", *ioNumberDataPackets)); + + UInt32 cNumberDataPackets = *ioNumberDataPackets; + Assert(pConvCbCtx->uPacketIdx + cNumberDataPackets <= pConvCbCtx->uPacketCnt); + + if (cNumberDataPackets) + { + AssertPtr(pConvCbCtx->pBufLstSrc); + Assert(pConvCbCtx->pBufLstSrc->mNumberBuffers == 1); /* Only one buffer for the source supported atm. */ + + AudioStreamBasicDescription *pSrcASBD = &pConvCbCtx->asbdSrc; + AudioBuffer *pSrcBuf = &pConvCbCtx->pBufLstSrc->mBuffers[0]; + + size_t cbOff = pConvCbCtx->uPacketIdx * pSrcASBD->mBytesPerPacket; + + cNumberDataPackets = RT_MIN((pSrcBuf->mDataByteSize - cbOff) / pSrcASBD->mBytesPerPacket, + cNumberDataPackets); + + void *pvAvail = (uint8_t *)pSrcBuf->mData + cbOff; + size_t cbAvail = RT_MIN(pSrcBuf->mDataByteSize - cbOff, cNumberDataPackets * pSrcASBD->mBytesPerPacket); + + Log3Func(("cNumberDataPackets=%RU32, cbOff=%zu, cbAvail=%zu\n", cNumberDataPackets, cbOff, cbAvail)); + + /* Set input data for the converter to use. + * Note: For VBR (Variable Bit Rates) or interleaved data handling we need multiple buffers here. */ + ioData->mNumberBuffers = 1; + + ioData->mBuffers[0].mNumberChannels = pSrcBuf->mNumberChannels; + ioData->mBuffers[0].mDataByteSize = cbAvail; + ioData->mBuffers[0].mData = pvAvail; + +#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA + RTFILE fh; + int rc = RTFileOpen(&fh,VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm", + RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + RTFileWrite(fh, pvAvail, cbAvail, NULL); + RTFileClose(fh); + } + else + AssertFailed(); +#endif + pConvCbCtx->uPacketIdx += cNumberDataPackets; + Assert(pConvCbCtx->uPacketIdx <= pConvCbCtx->uPacketCnt); + + *ioNumberDataPackets = cNumberDataPackets; + } + } + + Log3Func(("%RU32 / %RU32 -> ioNumberDataPackets=%RU32\n", + pConvCbCtx->uPacketIdx, pConvCbCtx->uPacketCnt, *ioNumberDataPackets)); + + return noErr; +} +#endif /* VBOX_WITH_AUDIO_CA_CONVERTER */ + + +/** + * Initializes a Core Audio stream. + * + * @return IPRT status code. + * @param pThis Driver instance. + * @param pCAStream Stream to initialize. + * @param pDev Audio device to use for this stream. + */ +static int coreAudioStreamInit(PCOREAUDIOSTREAM pCAStream, PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev) +{ + AssertPtrReturn(pCAStream, VERR_INVALID_POINTER); + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pDev, VERR_INVALID_POINTER); + + Assert(pCAStream->Unit.pDevice == NULL); /* Make sure no device is assigned yet. */ + AssertPtr(pDev->pvData); + Assert(pDev->cbData == sizeof(COREAUDIODEVICEDATA)); + +#ifdef DEBUG + PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData; + LogFunc(("pCAStream=%p, pDev=%p ('%s', ID=%RU32)\n", pCAStream, pDev, pDev->szName, pData->deviceID)); +#endif + + pCAStream->Unit.pDevice = pDev; + pCAStream->pDrv = pThis; + + return VINF_SUCCESS; +} + +# define CA_BREAK_STMT(stmt) \ + stmt; \ + break; + +/** + * Thread for a Core Audio stream's audio queue handling. + * This thread is required per audio queue to pump data to/from the Core Audio stream and + * handling its callbacks. + * + * @returns IPRT status code. + * @param hThreadSelf Thread handle. + * @param pvUser User argument. + */ +static DECLCALLBACK(int) coreAudioQueueThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + + PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pvUser; + AssertPtr(pCAStream); + AssertPtr(pCAStream->pCfg); + + const bool fIn = pCAStream->pCfg->enmDir == PDMAUDIODIR_IN; + + LogFunc(("Thread started for pCAStream=%p, fIn=%RTbool\n", pCAStream, fIn)); + + /* + * Create audio queue. + */ + OSStatus err; + if (fIn) + err = AudioQueueNewInput(&pCAStream->asbdStream, coreAudioInputQueueCb, pCAStream /* pvData */, + CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &pCAStream->audioQueue); + else + err = AudioQueueNewOutput(&pCAStream->asbdStream, coreAudioOutputQueueCb, pCAStream /* pvData */, + CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &pCAStream->audioQueue); + + if (err != noErr) + return VERR_GENERAL_FAILURE; /** @todo Fudge! */ + + /* + * Assign device to queue. + */ + PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pCAStream->Unit.pDevice->pvData; + AssertPtr(pData); + + UInt32 uSize = sizeof(pData->UUID); + err = AudioQueueSetProperty(pCAStream->audioQueue, kAudioQueueProperty_CurrentDevice, &pData->UUID, uSize); + if (err != noErr) + return VERR_GENERAL_FAILURE; /** @todo Fudge! */ + + const size_t cbBufSize = DrvAudioHlpFramesToBytes(pCAStream->pCfg->Backend.cfPeriod, &pCAStream->pCfg->Props); + + /* + * Allocate audio buffers. + */ + for (size_t i = 0; i < RT_ELEMENTS(pCAStream->audioBuffer); i++) + { + err = AudioQueueAllocateBuffer(pCAStream->audioQueue, cbBufSize, &pCAStream->audioBuffer[i]); + if (err != noErr) + break; + } + + if (err != noErr) + return VERR_GENERAL_FAILURE; /** @todo Fudge! */ + + /* Signal the main thread before entering the main loop. */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Enter the main loop. + */ + while (!ASMAtomicReadBool(&pCAStream->fShutdown)) + { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); + } + + /* + * Cleanup. + */ + if (fIn) + { + AudioQueueStop(pCAStream->audioQueue, 1); + } + else + { + AudioQueueStop(pCAStream->audioQueue, 0); + } + + for (size_t i = 0; i < RT_ELEMENTS(pCAStream->audioBuffer); i++) + { + if (pCAStream->audioBuffer[i]) + AudioQueueFreeBuffer(pCAStream->audioQueue, pCAStream->audioBuffer[i]); + } + + AudioQueueDispose(pCAStream->audioQueue, 1); + + LogFunc(("Thread ended for pCAStream=%p, fIn=%RTbool\n", pCAStream, fIn)); + return VINF_SUCCESS; +} + +/** + * Processes input data of an audio queue buffer and stores it into a Core Audio stream. + * + * @returns IPRT status code. + * @param pCAStream Core Audio stream to store input data into. + * @param audioBuffer Audio buffer to process input data from. + */ +int coreAudioInputQueueProcBuffer(PCOREAUDIOSTREAM pCAStream, AudioQueueBufferRef audioBuffer) +{ + PRTCIRCBUF pCircBuf = pCAStream->pCircBuf; + AssertPtr(pCircBuf); + + UInt8 *pvSrc = (UInt8 *)audioBuffer->mAudioData; + UInt8 *pvDst = NULL; + + size_t cbWritten = 0; + + size_t cbToWrite = audioBuffer->mAudioDataByteSize; + size_t cbLeft = RT_MIN(cbToWrite, RTCircBufFree(pCircBuf)); + + while (cbLeft) + { + /* Try to acquire the necessary block from the ring buffer. */ + RTCircBufAcquireWriteBlock(pCircBuf, cbLeft, (void **)&pvDst, &cbToWrite); + + if (!cbToWrite) + break; + + /* Copy the data from our ring buffer to the core audio buffer. */ + memcpy((UInt8 *)pvDst, pvSrc + cbWritten, cbToWrite); + + /* Release the read buffer, so it could be used for new data. */ + RTCircBufReleaseWriteBlock(pCircBuf, cbToWrite); + + cbWritten += cbToWrite; + + Assert(cbLeft >= cbToWrite); + cbLeft -= cbToWrite; + } + + Log3Func(("pCAStream=%p, cbBuffer=%RU32/%zu, cbWritten=%zu\n", + pCAStream, audioBuffer->mAudioDataByteSize, audioBuffer->mAudioDataBytesCapacity, cbWritten)); + + return VINF_SUCCESS; +} + +/** + * Input audio queue callback. Called whenever input data from the audio queue becomes available. + * + * @param pvUser User argument. + * @param audioQueue Audio queue to process input data from. + * @param audioBuffer Audio buffer to process input data from. Must be part of audio queue. + * @param pAudioTS Audio timestamp. + * @param cPacketDesc Number of packet descriptors. + * @param paPacketDesc Array of packet descriptors. + */ +static DECLCALLBACK(void) coreAudioInputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer, + const AudioTimeStamp *pAudioTS, + UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc) +{ + RT_NOREF(audioQueue, pAudioTS, cPacketDesc, paPacketDesc); + + PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pvUser; + AssertPtr(pCAStream); + + int rc = RTCritSectEnter(&pCAStream->CritSect); + AssertRC(rc); + + rc = coreAudioInputQueueProcBuffer(pCAStream, audioBuffer); + if (RT_SUCCESS(rc)) + AudioQueueEnqueueBuffer(audioQueue, audioBuffer, 0, NULL); + + rc = RTCritSectLeave(&pCAStream->CritSect); + AssertRC(rc); +} + +/** + * Processes output data of a Core Audio stream into an audio queue buffer. + * + * @returns IPRT status code. + * @param pCAStream Core Audio stream to process output data for. + * @param audioBuffer Audio buffer to store data into. + */ +int coreAudioOutputQueueProcBuffer(PCOREAUDIOSTREAM pCAStream, AudioQueueBufferRef audioBuffer) +{ + AssertPtr(pCAStream); + + PRTCIRCBUF pCircBuf = pCAStream->pCircBuf; + AssertPtr(pCircBuf); + + size_t cbRead = 0; + + UInt8 *pvSrc = NULL; + UInt8 *pvDst = (UInt8 *)audioBuffer->mAudioData; + + size_t cbToRead = RT_MIN(RTCircBufUsed(pCircBuf), audioBuffer->mAudioDataBytesCapacity); + size_t cbLeft = cbToRead; + + while (cbLeft) + { + /* Try to acquire the necessary block from the ring buffer. */ + RTCircBufAcquireReadBlock(pCircBuf, cbLeft, (void **)&pvSrc, &cbToRead); + + if (cbToRead) + { + /* Copy the data from our ring buffer to the core audio buffer. */ + memcpy((UInt8 *)pvDst + cbRead, pvSrc, cbToRead); + } + + /* Release the read buffer, so it could be used for new data. */ + RTCircBufReleaseReadBlock(pCircBuf, cbToRead); + + if (!cbToRead) + break; + + /* Move offset. */ + cbRead += cbToRead; + Assert(cbRead <= audioBuffer->mAudioDataBytesCapacity); + + Assert(cbToRead <= cbLeft); + cbLeft -= cbToRead; + } + + audioBuffer->mAudioDataByteSize = cbRead; + + if (audioBuffer->mAudioDataByteSize < audioBuffer->mAudioDataBytesCapacity) + { + RT_BZERO((UInt8 *)audioBuffer->mAudioData + audioBuffer->mAudioDataByteSize, + audioBuffer->mAudioDataBytesCapacity - audioBuffer->mAudioDataByteSize); + + audioBuffer->mAudioDataByteSize = audioBuffer->mAudioDataBytesCapacity; + } + + Log3Func(("pCAStream=%p, cbCapacity=%RU32, cbRead=%zu\n", + pCAStream, audioBuffer->mAudioDataBytesCapacity, cbRead)); + + return VINF_SUCCESS; +} + +/** + * Output audio queue callback. Called whenever an audio queue is ready to process more output data. + * + * @param pvUser User argument. + * @param audioQueue Audio queue to process output data for. + * @param audioBuffer Audio buffer to store output data in. Must be part of audio queue. + */ +static DECLCALLBACK(void) coreAudioOutputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer) +{ + RT_NOREF(audioQueue); + + PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pvUser; + AssertPtr(pCAStream); + + int rc = RTCritSectEnter(&pCAStream->CritSect); + AssertRC(rc); + + rc = coreAudioOutputQueueProcBuffer(pCAStream, audioBuffer); + if (RT_SUCCESS(rc)) + AudioQueueEnqueueBuffer(audioQueue, audioBuffer, 0, NULL); + + rc = RTCritSectLeave(&pCAStream->CritSect); + AssertRC(rc); +} + +/** + * Invalidates a Core Audio stream's audio queue. + * + * @returns IPRT status code. + * @param pCAStream Core Audio stream to invalidate its queue for. + */ +static int coreAudioStreamInvalidateQueue(PCOREAUDIOSTREAM pCAStream) +{ + int rc = VINF_SUCCESS; + + Log3Func(("pCAStream=%p\n", pCAStream)); + + for (size_t i = 0; i < RT_ELEMENTS(pCAStream->audioBuffer); i++) + { + AudioQueueBufferRef pBuf = pCAStream->audioBuffer[i]; + + if (pCAStream->pCfg->enmDir == PDMAUDIODIR_IN) + { + int rc2 = coreAudioInputQueueProcBuffer(pCAStream, pBuf); + if (RT_SUCCESS(rc2)) + { + AudioQueueEnqueueBuffer(pCAStream->audioQueue, pBuf, 0, NULL); + } + } + else if (pCAStream->pCfg->enmDir == PDMAUDIODIR_OUT) + { + int rc2 = coreAudioOutputQueueProcBuffer(pCAStream, pBuf); + if ( RT_SUCCESS(rc2) + && pBuf->mAudioDataByteSize) + { + AudioQueueEnqueueBuffer(pCAStream->audioQueue, pBuf, 0, NULL); + } + + if (RT_SUCCESS(rc)) + rc = rc2; + } + else + AssertFailed(); + } + + return rc; +} + +/** + * Initializes a Core Audio stream's audio queue. + * + * @returns IPRT status code. + * @param pCAStream Core Audio stream to initialize audio queue for. + * @param pCfgReq Requested stream configuration. + * @param pCfgAcq Acquired stream configuration on success. + */ +static int coreAudioStreamInitQueue(PCOREAUDIOSTREAM pCAStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + RT_NOREF(pCfgAcq); + + LogFunc(("pCAStream=%p, pCfgReq=%p, pCfgAcq=%p\n", pCAStream, pCfgReq, pCfgAcq)); + + /* No device assigned? Bail out early. */ + if (pCAStream->Unit.pDevice == NULL) + return VERR_NOT_AVAILABLE; + + const bool fIn = pCfgReq->enmDir == PDMAUDIODIR_IN; + + int rc = VINF_SUCCESS; + + /* Create the recording device's out format based on our required audio settings. */ + Assert(pCAStream->pCfg == NULL); + pCAStream->pCfg = DrvAudioHlpStreamCfgDup(pCfgReq); + if (!pCAStream->pCfg) + rc = VERR_NO_MEMORY; + + coreAudioPCMPropsToASBD(&pCfgReq->Props, &pCAStream->asbdStream); + /** @todo Do some validation? */ + + coreAudioPrintASBD( fIn + ? "Capturing queue format" + : "Playback queue format", &pCAStream->asbdStream); + + if (RT_FAILURE(rc)) + { + LogRel(("CoreAudio: Failed to convert requested %s format to native format (%Rrc)\n", fIn ? "input" : "output", rc)); + return rc; + } + + rc = RTCircBufCreate(&pCAStream->pCircBuf, PDMAUDIOSTREAMCFG_F2B(pCfgReq, pCfgReq->Backend.cfBufferSize)); + if (RT_FAILURE(rc)) + return rc; + + /* + * Start the thread. + */ + rc = RTThreadCreate(&pCAStream->hThread, coreAudioQueueThread, + pCAStream /* pvUser */, 0 /* Default stack size */, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "CAQUEUE"); + if (RT_SUCCESS(rc)) + rc = RTThreadUserWait(pCAStream->hThread, 10 * 1000 /* 10s timeout */); + + LogFunc(("Returning %Rrc\n", rc)); + return rc; +} + +/** + * Unitializes a Core Audio stream's audio queue. + * + * @returns IPRT status code. + * @param pCAStream Core Audio stream to unitialize audio queue for. + */ +static int coreAudioStreamUninitQueue(PCOREAUDIOSTREAM pCAStream) +{ + LogFunc(("pCAStream=%p\n", pCAStream)); + + if (pCAStream->hThread != NIL_RTTHREAD) + { + LogFunc(("Waiting for thread ...\n")); + + ASMAtomicXchgBool(&pCAStream->fShutdown, true); + + int rcThread; + int rc = RTThreadWait(pCAStream->hThread, 30 * 1000, &rcThread); + if (RT_FAILURE(rc)) + return rc; + + RT_NOREF(rcThread); + LogFunc(("Thread stopped with %Rrc\n", rcThread)); + + pCAStream->hThread = NIL_RTTHREAD; + } + + if (pCAStream->pCfg) + { + DrvAudioHlpStreamCfgFree(pCAStream->pCfg); + pCAStream->pCfg = NULL; + } + + if (pCAStream->pCircBuf) + { + RTCircBufDestroy(pCAStream->pCircBuf); + pCAStream->pCircBuf = NULL; + } + + LogFunc(("Returning\n")); + return VINF_SUCCESS; +} + +/** + * Unitializes a Core Audio stream. + * + * @returns IPRT status code. + * @param pCAStream Core Audio stream to uninitialize. + */ +static int coreAudioStreamUninit(PCOREAUDIOSTREAM pCAStream) +{ + LogFunc(("pCAStream=%p\n", pCAStream)); + + int rc = coreAudioStreamUninitQueue(pCAStream); + if (RT_SUCCESS(rc)) + { + pCAStream->Unit.pDevice = NULL; + pCAStream->pDrv = NULL; + } + + return rc; +} + +/** + * Registers callbacks for a specific Core Audio device. + * + * @return IPRT status code. + * @param pThis Host audio driver instance. + * @param pDev Audio device to use for the registered callbacks. + */ +static int coreAudioDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev) +{ + RT_NOREF(pThis); + + AudioDeviceID deviceID = kAudioDeviceUnknown; + + PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData; + if (pData) + deviceID = pData->deviceID; + + if (deviceID != kAudioDeviceUnknown) + { + LogFunc(("deviceID=%RU32\n", deviceID)); + + /* + * Register device callbacks. + */ + AudioObjectPropertyAddress propAdr = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus err = AudioObjectAddPropertyListener(deviceID, &propAdr, + coreAudioDeviceStateChangedCb, pDev /* pvUser */); + if ( err != noErr + && err != kAudioHardwareIllegalOperationError) + { + LogRel(("CoreAudio: Failed to add the recording device state changed listener (%RI32)\n", err)); + } + + propAdr.mSelector = kAudioDeviceProcessorOverload; + propAdr.mScope = kAudioUnitScope_Global; + err = AudioObjectAddPropertyListener(deviceID, &propAdr, + coreAudioDevPropChgCb, pDev /* pvUser */); + if (err != noErr) + LogRel(("CoreAudio: Failed to register processor overload listener (%RI32)\n", err)); + + propAdr.mSelector = kAudioDevicePropertyNominalSampleRate; + propAdr.mScope = kAudioUnitScope_Global; + err = AudioObjectAddPropertyListener(deviceID, &propAdr, + coreAudioDevPropChgCb, pDev /* pvUser */); + if (err != noErr) + LogRel(("CoreAudio: Failed to register sample rate changed listener (%RI32)\n", err)); + } + + return VINF_SUCCESS; +} + +/** + * Unregisters all formerly registered callbacks of a Core Audio device again. + * + * @return IPRT status code. + * @param pThis Host audio driver instance. + * @param pDev Audio device to use for the registered callbacks. + */ +static int coreAudioDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev) +{ + RT_NOREF(pThis); + + AudioDeviceID deviceID = kAudioDeviceUnknown; + + if (pDev) + { + PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData; + if (pData) + deviceID = pData->deviceID; + } + + if (deviceID != kAudioDeviceUnknown) + { + LogFunc(("deviceID=%RU32\n", deviceID)); + + /* + * Unregister per-device callbacks. + */ + AudioObjectPropertyAddress propAdr = { kAudioDeviceProcessorOverload, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus err = AudioObjectRemovePropertyListener(deviceID, &propAdr, + coreAudioDevPropChgCb, pDev /* pvUser */); + if ( err != noErr + && err != kAudioHardwareBadObjectError) + { + LogRel(("CoreAudio: Failed to remove the recording processor overload listener (%RI32)\n", err)); + } + + propAdr.mSelector = kAudioDevicePropertyNominalSampleRate; + err = AudioObjectRemovePropertyListener(deviceID, &propAdr, + coreAudioDevPropChgCb, pDev /* pvUser */); + if ( err != noErr + && err != kAudioHardwareBadObjectError) + { + LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%RI32)\n", err)); + } + + propAdr.mSelector = kAudioDevicePropertyDeviceIsAlive; + err = AudioObjectRemovePropertyListener(deviceID, &propAdr, + coreAudioDeviceStateChangedCb, pDev /* pvUser */); + if ( err != noErr + && err != kAudioHardwareBadObjectError) + { + LogRel(("CoreAudio: Failed to remove the device alive listener (%RI32)\n", err)); + } + } + + return VINF_SUCCESS; +} + +/* Callback for getting notified when some of the properties of an audio device have changed. */ +static DECLCALLBACK(OSStatus) coreAudioDevPropChgCb(AudioObjectID propertyID, + UInt32 cAddresses, + const AudioObjectPropertyAddress properties[], + void *pvUser) +{ + RT_NOREF(cAddresses, properties, pvUser); + + PPDMAUDIODEVICE pDev = (PPDMAUDIODEVICE)pvUser; + AssertPtr(pDev); + + LogFlowFunc(("propertyID=%u, nAddresses=%u, pDev=%p\n", propertyID, cAddresses, pDev)); + + switch (propertyID) + { +#ifdef DEBUG + case kAudioDeviceProcessorOverload: + { + LogFunc(("Processor overload detected!\n")); + break; + } +#endif /* DEBUG */ + case kAudioDevicePropertyNominalSampleRate: + { +#ifndef VBOX_WITH_AUDIO_CALLBACKS + int rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_REINIT); + AssertRC(rc2); +#else + RT_NOREF(pDev); +#endif + break; + } + + default: + /* Just skip. */ + break; + } + + return noErr; +} + +/** + * Enumerates all available host audio devices internally. + * + * @returns IPRT status code. + * @param pThis Host audio driver instance. + */ +static int coreAudioEnumerateDevices(PDRVHOSTCOREAUDIO pThis) +{ + LogFlowFuncEnter(); + + /* + * Unregister old default devices, if any. + */ + if (pThis->pDefaultDevIn) + { + coreAudioDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevIn); + pThis->pDefaultDevIn = NULL; + } + + if (pThis->pDefaultDevOut) + { + coreAudioDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevOut); + pThis->pDefaultDevOut = NULL; + } + + /* Remove old / stale device entries. */ + DrvAudioHlpDeviceEnumFree(&pThis->Devices); + + /* Enumerate all devices internally. */ + int rc = coreAudioDevicesEnumerateAll(pThis, &pThis->Devices); + if (RT_SUCCESS(rc)) + { + /* + * Default input device. + */ + pThis->pDefaultDevIn = DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->Devices, PDMAUDIODIR_IN); + if (pThis->pDefaultDevIn) + { + LogRel2(("CoreAudio: Default capturing device is '%s'\n", pThis->pDefaultDevIn->szName)); + +#ifdef DEBUG + PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pThis->pDefaultDevIn->pvData; + AssertPtr(pDevData); + LogFunc(("pDefaultDevIn=%p, ID=%RU32\n", pThis->pDefaultDevIn, pDevData->deviceID)); +#endif + rc = coreAudioDeviceRegisterCallbacks(pThis, pThis->pDefaultDevIn); + } + else + LogRel2(("CoreAudio: No default capturing device found\n")); + + /* + * Default output device. + */ + pThis->pDefaultDevOut = DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->Devices, PDMAUDIODIR_OUT); + if (pThis->pDefaultDevOut) + { + LogRel2(("CoreAudio: Default playback device is '%s'\n", pThis->pDefaultDevOut->szName)); + +#ifdef DEBUG + PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pThis->pDefaultDevOut->pvData; + AssertPtr(pDevData); + LogFunc(("pDefaultDevOut=%p, ID=%RU32\n", pThis->pDefaultDevOut, pDevData->deviceID)); +#endif + rc = coreAudioDeviceRegisterCallbacks(pThis, pThis->pDefaultDevOut); + } + else + LogRel2(("CoreAudio: No default playback device found\n")); + } + + LogFunc(("Returning %Rrc\n", rc)); + return rc; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHostCoreAudioStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + /* pcxRead is optional. */ + + PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; + PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); + +#ifndef VBOX_WITH_AUDIO_CALLBACKS + /* Check if the audio device should be reinitialized. If so do it. */ + if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_REINIT) + { + /* For now re just re-initialize with the current input device. */ + if (pThis->pDefaultDevIn) + { + int rc2 = coreAudioStreamReinit(pThis, pCAStream, pThis->pDefaultDevIn); + if (RT_FAILURE(rc2)) + return VERR_NOT_AVAILABLE; + } + else + return VERR_NOT_AVAILABLE; + } +#else + RT_NOREF(pThis); +#endif + + if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT) + { + if (pcxRead) + *pcxRead = 0; + return VINF_SUCCESS; + } + + int rc = VINF_SUCCESS; + + uint32_t cbReadTotal = 0; + + rc = RTCritSectEnter(&pCAStream->CritSect); + AssertRC(rc); + + do + { + size_t cbToWrite = RT_MIN(cxBuf, RTCircBufUsed(pCAStream->pCircBuf)); + + uint8_t *pvChunk; + size_t cbChunk; + + Log3Func(("cbToWrite=%zu/%zu\n", cbToWrite, RTCircBufSize(pCAStream->pCircBuf))); + + while (cbToWrite) + { + /* Try to acquire the necessary block from the ring buffer. */ + RTCircBufAcquireReadBlock(pCAStream->pCircBuf, cbToWrite, (void **)&pvChunk, &cbChunk); + if (cbChunk) + memcpy((uint8_t *)pvBuf + cbReadTotal, pvChunk, cbChunk); + + /* Release the read buffer, so it could be used for new data. */ + RTCircBufReleaseReadBlock(pCAStream->pCircBuf, cbChunk); + + if (RT_FAILURE(rc)) + break; + + Assert(cbToWrite >= cbChunk); + cbToWrite -= cbChunk; + + cbReadTotal += cbChunk; + } + } + while (0); + + int rc2 = RTCritSectLeave(&pCAStream->CritSect); + AssertRC(rc2); + + if (RT_SUCCESS(rc)) + { + if (pcxRead) + *pcxRead = cbReadTotal; + } + + return rc; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHostCoreAudioStreamPlay(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf, + uint32_t *pcxWritten) +{ + PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); + PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; + +#ifndef VBOX_WITH_AUDIO_CALLBACKS + /* Check if the audio device should be reinitialized. If so do it. */ + if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_REINIT) + { + if (pThis->pDefaultDevOut) + { + /* For now re just re-initialize with the current output device. */ + int rc2 = coreAudioStreamReinit(pThis, pCAStream, pThis->pDefaultDevOut); + if (RT_FAILURE(rc2)) + return VERR_NOT_AVAILABLE; + } + else + return VERR_NOT_AVAILABLE; + } +#else + RT_NOREF(pThis); +#endif + + if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT) + { + if (pcxWritten) + *pcxWritten = 0; + return VINF_SUCCESS; + } + + uint32_t cbWrittenTotal = 0; + + int rc = VINF_SUCCESS; + + rc = RTCritSectEnter(&pCAStream->CritSect); + AssertRC(rc); + + size_t cbToWrite = RT_MIN(cxBuf, RTCircBufFree(pCAStream->pCircBuf)); + Log3Func(("cbToWrite=%zu\n", cbToWrite)); + + uint8_t *pvChunk; + size_t cbChunk; + + while (cbToWrite) + { + /* Try to acquire the necessary space from the ring buffer. */ + RTCircBufAcquireWriteBlock(pCAStream->pCircBuf, cbToWrite, (void **)&pvChunk, &cbChunk); + if (!cbChunk) + { + RTCircBufReleaseWriteBlock(pCAStream->pCircBuf, cbChunk); + break; + } + + Assert(cbChunk <= cbToWrite); + Assert(cbWrittenTotal + cbChunk <= cxBuf); + + memcpy(pvChunk, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk); + +#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA + RTFILE fh; + rc = RTFileOpen(&fh,VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm", + RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + RTFileWrite(fh, pvChunk, cbChunk, NULL); + RTFileClose(fh); + } + else + AssertFailed(); +#endif + + /* Release the ring buffer, so the read thread could start reading this data. */ + RTCircBufReleaseWriteBlock(pCAStream->pCircBuf, cbChunk); + + if (RT_FAILURE(rc)) + break; + + Assert(cbToWrite >= cbChunk); + cbToWrite -= cbChunk; + + cbWrittenTotal += cbChunk; + } + + if ( RT_SUCCESS(rc) + && pCAStream->fRun + && !pCAStream->fIsRunning) + { + rc = coreAudioStreamInvalidateQueue(pCAStream); + if (RT_SUCCESS(rc)) + { + AudioQueueStart(pCAStream->audioQueue, NULL); + pCAStream->fRun = false; + pCAStream->fIsRunning = true; + } + } + + int rc2 = RTCritSectLeave(&pCAStream->CritSect); + AssertRC(rc2); + + if (RT_SUCCESS(rc)) + { + if (pcxWritten) + *pcxWritten = cbWrittenTotal; + } + + return rc; +} + +static DECLCALLBACK(int) coreAudioStreamControl(PDRVHOSTCOREAUDIO pThis, + PCOREAUDIOSTREAM pCAStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + RT_NOREF(pThis); + + uint32_t enmStatus = ASMAtomicReadU32(&pCAStream->enmStatus); + + LogFlowFunc(("enmStreamCmd=%RU32, enmStatus=%RU32\n", enmStreamCmd, enmStatus)); + + if (!( enmStatus == COREAUDIOSTATUS_INIT +#ifndef VBOX_WITH_AUDIO_CALLBACKS + || enmStatus == COREAUDIOSTATUS_REINIT +#endif + )) + { + return VINF_SUCCESS; + } + + if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc = VINF_SUCCESS; + + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + case PDMAUDIOSTREAMCMD_RESUME: + { + LogFunc(("Queue enable\n")); + if (pCAStream->pCfg->enmDir == PDMAUDIODIR_IN) + { + rc = coreAudioStreamInvalidateQueue(pCAStream); + if (RT_SUCCESS(rc)) + { + /* Start the audio queue immediately. */ + AudioQueueStart(pCAStream->audioQueue, NULL); + } + } + else if (pCAStream->pCfg->enmDir == PDMAUDIODIR_OUT) + { + /* Touch the run flag to start the audio queue as soon as + * we have anough data to actually play something. */ + ASMAtomicXchgBool(&pCAStream->fRun, true); + } + break; + } + + case PDMAUDIOSTREAMCMD_DISABLE: + { + LogFunc(("Queue disable\n")); + AudioQueueStop(pCAStream->audioQueue, 1 /* Immediately */); + ASMAtomicXchgBool(&pCAStream->fRun, false); + ASMAtomicXchgBool(&pCAStream->fIsRunning, false); + break; + } + case PDMAUDIOSTREAMCMD_PAUSE: + { + LogFunc(("Queue pause\n")); + AudioQueuePause(pCAStream->audioQueue); + ASMAtomicXchgBool(&pCAStream->fIsRunning, false); + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHostCoreAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); + + RT_BZERO(pBackendCfg, sizeof(PDMAUDIOBACKENDCFG)); + + RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio driver"); + + pBackendCfg->cbStreamIn = sizeof(COREAUDIOSTREAM); + pBackendCfg->cbStreamOut = sizeof(COREAUDIOSTREAM); + + /* For Core Audio we provide one stream per device for now. */ + pBackendCfg->cMaxStreamsIn = DrvAudioHlpDeviceEnumGetDeviceCount(&pThis->Devices, PDMAUDIODIR_IN); + pBackendCfg->cMaxStreamsOut = DrvAudioHlpDeviceEnumGetDeviceCount(&pThis->Devices, PDMAUDIODIR_OUT); + + LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS)); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices} + */ +static DECLCALLBACK(int) drvHostCoreAudioGetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIODEVICEENUM pDeviceEnum) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER); + + PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rc = coreAudioEnumerateDevices(pThis); + if (RT_SUCCESS(rc)) + { + if (pDeviceEnum) + { + rc = DrvAudioHlpDeviceEnumInit(pDeviceEnum); + if (RT_SUCCESS(rc)) + rc = DrvAudioHlpDeviceEnumCopy(pDeviceEnum, &pThis->Devices); + + if (RT_FAILURE(rc)) + DrvAudioHlpDeviceEnumFree(pDeviceEnum); + } + } + + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + } + + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} + + +#ifdef VBOX_WITH_AUDIO_CALLBACKS +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnSetCallback} + */ +static DECLCALLBACK(int) drvHostCoreAudioSetCallback(PPDMIHOSTAUDIO pInterface, PFNPDMHOSTAUDIOCALLBACK pfnCallback) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + /* pfnCallback will be handled below. */ + + PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + LogFunc(("pfnCallback=%p\n", pfnCallback)); + + if (pfnCallback) /* Register. */ + { + Assert(pThis->pfnCallback == NULL); + pThis->pfnCallback = pfnCallback; + } + else /* Unregister. */ + { + if (pThis->pfnCallback) + pThis->pfnCallback = NULL; + } + + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + } + + return rc; +} +#endif + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostCoreAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(pInterface, enmDir); + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostCoreAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); + PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; + + int rc = RTCritSectInit(&pCAStream->CritSect); + if (RT_FAILURE(rc)) + return rc; + + pCAStream->hThread = NIL_RTTHREAD; + pCAStream->fRun = false; + pCAStream->fIsRunning = false; + pCAStream->fShutdown = false; + + /* Input or output device? */ + bool fIn = pCfgReq->enmDir == PDMAUDIODIR_IN; + + /* For now, just use the default device available. */ + PPDMAUDIODEVICE pDev = fIn ? pThis->pDefaultDevIn : pThis->pDefaultDevOut; + + LogFunc(("pStream=%p, pCfgReq=%p, pCfgAcq=%p, fIn=%RTbool, pDev=%p\n", pStream, pCfgReq, pCfgAcq, fIn, pDev)); + + if (pDev) /* (Default) device available? */ + { + /* Sanity. */ + AssertPtr(pDev->pvData); + Assert(pDev->cbData); + + /* Init the Core Audio stream. */ + rc = coreAudioStreamInit(pCAStream, pThis, pDev); + if (RT_SUCCESS(rc)) + { + ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_INIT); + + rc = coreAudioStreamInitQueue(pCAStream, pCfgReq, pCfgAcq); + if (RT_SUCCESS(rc)) + { + ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_INIT); + } + else + { + ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_UNINIT); + + int rc2 = coreAudioStreamUninit(pCAStream); + AssertRC(rc2); + + ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_UNINIT); + } + } + } + else + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + + LogFunc(("Returning %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHostCoreAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); + + PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; + + uint32_t status = ASMAtomicReadU32(&pCAStream->enmStatus); + if (!( status == COREAUDIOSTATUS_INIT +#ifndef VBOX_WITH_AUDIO_CALLBACKS + || status == COREAUDIOSTATUS_REINIT +#endif + )) + { + return VINF_SUCCESS; + } + + if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc = coreAudioStreamControl(pThis, pCAStream, PDMAUDIOSTREAMCMD_DISABLE); + if (RT_SUCCESS(rc)) + { + ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_UNINIT); + + rc = coreAudioStreamUninit(pCAStream); + + if (RT_SUCCESS(rc)) + ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_UNINIT); + } + + if (RT_SUCCESS(rc)) + { + if (RTCritSectIsInitialized(&pCAStream->CritSect)) + RTCritSectDelete(&pCAStream->CritSect); + } + + LogFunc(("rc=%Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} + */ +static DECLCALLBACK(int) drvHostCoreAudioStreamControl(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); + PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; + + return coreAudioStreamControl(pThis, pCAStream, enmStreamCmd); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostCoreAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; + + if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT) + return 0; + + AssertPtr(pCAStream->pCfg); + AssertPtr(pCAStream->pCircBuf); + + switch (pCAStream->pCfg->enmDir) + { + case PDMAUDIODIR_IN: + return (uint32_t)RTCircBufUsed(pCAStream->pCircBuf); + + case PDMAUDIODIR_OUT: + default: + AssertFailed(); + break; + } + + return 0; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostCoreAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; + + uint32_t cbWritable = 0; + + if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_INIT) + { + AssertPtr(pCAStream->pCfg); + AssertPtr(pCAStream->pCircBuf); + + switch (pCAStream->pCfg->enmDir) + { + case PDMAUDIODIR_OUT: + cbWritable = (uint32_t)RTCircBufFree(pCAStream->pCircBuf); + break; + + default: + break; + } + } + + LogFlowFunc(("cbWritable=%RU32\n", cbWritable)); + return cbWritable; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} + */ +static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostCoreAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream; + + PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_NONE; + + if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */ + return strmSts; + + if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_INIT) + strmSts |= PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED; + + return strmSts; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} + */ +static DECLCALLBACK(int) drvHostCoreAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + RT_NOREF(pInterface, pStream); + + /* Nothing to do here for Core Audio. */ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} + */ +static DECLCALLBACK(int) drvHostCoreAudioInit(PPDMIHOSTAUDIO pInterface) +{ + PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); + + int rc = DrvAudioHlpDeviceEnumInit(&pThis->Devices); + if (RT_SUCCESS(rc)) + { + /* Do the first (initial) internal device enumeration. */ + rc = coreAudioEnumerateDevices(pThis); + } + + if (RT_SUCCESS(rc)) + { + /* Register system callbacks. */ + AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + OSStatus err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAdr, + coreAudioDefaultDeviceChangedCb, pThis /* pvUser */); + if ( err != noErr + && err != kAudioHardwareIllegalOperationError) + { + LogRel(("CoreAudio: Failed to add the input default device changed listener (%RI32)\n", err)); + } + + propAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAdr, + coreAudioDefaultDeviceChangedCb, pThis /* pvUser */); + if ( err != noErr + && err != kAudioHardwareIllegalOperationError) + { + LogRel(("CoreAudio: Failed to add the output default device changed listener (%RI32)\n", err)); + } + } + + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} + */ +static DECLCALLBACK(void) drvHostCoreAudioShutdown(PPDMIHOSTAUDIO pInterface) +{ + PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface); + + /* + * Unregister system callbacks. + */ + AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + OSStatus err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAdr, + coreAudioDefaultDeviceChangedCb, pThis /* pvUser */); + if ( err != noErr + && err != kAudioHardwareBadObjectError) + { + LogRel(("CoreAudio: Failed to remove the default input device changed listener (%RI32)\n", err)); + } + + propAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAdr, + coreAudioDefaultDeviceChangedCb, pThis /* pvUser */); + if ( err != noErr + && err != kAudioHardwareBadObjectError) + { + LogRel(("CoreAudio: Failed to remove the default output device changed listener (%RI32)\n", err)); + } + + LogFlowFuncEnter(); +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostCoreAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + + return NULL; +} + + +/** + * @callback_method_impl{FNPDMDRVCONSTRUCT, + * Construct a Core Audio driver instance.} + */ +static DECLCALLBACK(int) drvHostCoreAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); + LogRel(("Audio: Initializing Core Audio driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostCoreAudioQueryInterface; + /* IHostAudio */ + PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostCoreAudio); + + /* This backend supports device enumeration. */ + pThis->IHostAudio.pfnGetDevices = drvHostCoreAudioGetDevices; + +#ifdef VBOX_WITH_AUDIO_CALLBACKS + /* This backend supports host audio callbacks. */ + pThis->IHostAudio.pfnSetCallback = drvHostCoreAudioSetCallback; + pThis->pfnCallback = NULL; +#endif + + int rc = RTCritSectInit(&pThis->CritSect); + +#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA + RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm"); + RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm"); +#endif + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * @callback_method_impl{FNPDMDRVDESTRUCT} + */ +static DECLCALLBACK(void) drvHostCoreAudioDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); + + int rc2 = RTCritSectDelete(&pThis->CritSect); + AssertRC(rc2); + + LogFlowFuncLeaveRC(rc2); +} + + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvHostCoreAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "CoreAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Core Audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTCOREAUDIO), + /* pfnConstruct */ + drvHostCoreAudioConstruct, + /* pfnDestruct */ + drvHostCoreAudioDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff --git a/src/VBox/Devices/Audio/DrvHostDSound.cpp b/src/VBox/Devices/Audio/DrvHostDSound.cpp new file mode 100644 index 00000000..a98fd7a0 --- /dev/null +++ b/src/VBox/Devices/Audio/DrvHostDSound.cpp @@ -0,0 +1,2659 @@ +/* $Id: DrvHostDSound.cpp $ */ +/** @file + * Windows host backend driver using DirectSound. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <VBox/log.h> +#include <iprt/win/windows.h> +#include <dsound.h> + +#include <iprt/alloc.h> +#include <iprt/system.h> +#include <iprt/uuid.h> +#include <iprt/utf16.h> + +#include "DrvAudio.h" +#include "VBoxDD.h" +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT +# include <new> /* For bad_alloc. */ +# include "VBoxMMNotificationClient.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* + * IDirectSound* interface uses HRESULT status codes and the driver callbacks use + * the IPRT status codes. To minimize HRESULT->IPRT conversion most internal functions + * in the driver return HRESULT and conversion is done in the driver callbacks. + * + * Naming convention: + * 'dsound*' functions return IPRT status code; + * 'directSound*' - return HRESULT. + */ + +/* + * Optional release logging, which a user can turn on with the + * 'VBoxManage debugvm' command. + * Debug logging still uses the common Log* macros from IPRT. + * Messages which always should go to the release log use LogRel. + */ +/* General code behavior. */ +#define DSLOG(a) do { LogRel2(a); } while(0) +/* Something which produce a lot of logging during playback/recording. */ +#define DSLOGF(a) do { LogRel3(a); } while(0) +/* Important messages like errors. Limited in the default release log to avoid log flood. */ +#define DSLOGREL(a) \ + do { \ + static int8_t s_cLogged = 0; \ + if (s_cLogged < 8) { \ + ++s_cLogged; \ + LogRel(a); \ + } else DSLOG(a); \ + } while (0) + +/** Maximum number of attempts to restore the sound buffer before giving up. */ +#define DRV_DSOUND_RESTORE_ATTEMPTS_MAX 3 +/** Default input latency (in ms). */ +#define DRV_DSOUND_DEFAULT_LATENCY_MS_IN 50 +/** Default output latency (in ms). */ +#define DRV_DSOUND_DEFAULT_LATENCY_MS_OUT 50 + +/** Makes DRVHOSTDSOUND out of PDMIHOSTAUDIO. */ +#define PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface) \ + ( (PDRVHOSTDSOUND)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTDSOUND, IHostAudio)) ) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/* Dynamically load dsound.dll. */ +typedef HRESULT WINAPI FNDIRECTSOUNDENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext); +typedef FNDIRECTSOUNDENUMERATEW *PFNDIRECTSOUNDENUMERATEW; +typedef HRESULT WINAPI FNDIRECTSOUNDCAPTUREENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext); +typedef FNDIRECTSOUNDCAPTUREENUMERATEW *PFNDIRECTSOUNDCAPTUREENUMERATEW; +typedef HRESULT WINAPI FNDIRECTSOUNDCAPTURECREATE8(LPCGUID lpcGUID, LPDIRECTSOUNDCAPTURE8 *lplpDSC, LPUNKNOWN pUnkOuter); +typedef FNDIRECTSOUNDCAPTURECREATE8 *PFNDIRECTSOUNDCAPTURECREATE8; + +#define VBOX_DSOUND_MAX_EVENTS 3 + +typedef enum DSOUNDEVENT +{ + DSOUNDEVENT_NOTIFY = 0, + DSOUNDEVENT_INPUT, + DSOUNDEVENT_OUTPUT, +} DSOUNDEVENT; + +typedef struct DSOUNDHOSTCFG +{ + RTUUID uuidPlay; + LPCGUID pGuidPlay; + RTUUID uuidCapture; + LPCGUID pGuidCapture; +} DSOUNDHOSTCFG, *PDSOUNDHOSTCFG; + +typedef struct DSOUNDSTREAM +{ + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; + /** Buffer alignment. */ + uint8_t uAlign; + /** Whether this stream is in an enable state on the DirectSound side. */ + bool fEnabled; + /** The stream's critical section for synchronizing access. */ + RTCRITSECT CritSect; + /** The internal playback / capturing buffer. */ + PRTCIRCBUF pCircBuf; + /** Size (in bytes) of the DirectSound buffer. + * Note: This in *not* the size of the circular buffer above! */ + DWORD cbBufSize; + union + { + struct + { + /** The actual DirectSound Buffer (DSB) used for the capturing. + * This is a secondary buffer and is used as a streaming buffer. */ + LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB; + /** Current read offset (in bytes) within the DSB. */ + DWORD offReadPos; + /** Number of buffer overruns happened. Used for logging. */ + uint8_t cOverruns; + } In; + struct + { + /** The actual DirectSound Buffer (DSB) used for playback. + * This is a secondary buffer and is used as a streaming buffer. */ + LPDIRECTSOUNDBUFFER8 pDSB; + /** Current write offset (in bytes) within the DSB. */ + DWORD offWritePos; + /** Offset of last play cursor within the DSB when checked for pending. */ + DWORD offPlayCursorLastPending; + /** Offset of last play cursor within the DSB when last played. */ + DWORD offPlayCursorLastPlayed; + /** Total amount (in bytes) written to our internal ring buffer. */ + uint64_t cbWritten; + /** Total amount (in bytes) played (to the DirectSound buffer). */ + uint64_t cbTransferred; + /** Flag indicating whether playback was just (re)started. */ + bool fFirstTransfer; + /** Flag indicating whether this stream is in draining mode, e.g. no new + * data is being written to it but DirectSound still needs to be able to + * play its remaining (buffered) data. */ + bool fDrain; + /** How much (in bytes) the last transfer from the internal buffer + * to the DirectSound buffer was. */ + uint32_t cbLastTransferred; + /** Timestamp (in ms) of the last transfer from the internal buffer + * to the DirectSound buffer. */ + uint64_t tsLastTransferredMs; + /** Number of buffer underruns happened. Used for logging. */ + uint8_t cUnderruns; + } Out; + }; +#ifdef LOG_ENABLED + struct + { + uint64_t tsLastTransferredMs; + } Dbg; +#endif +} DSOUNDSTREAM, *PDSOUNDSTREAM; + +typedef struct DRVHOSTDSOUND +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Our audio host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Critical section to serialize access. */ + RTCRITSECT CritSect; + /** List of found host input devices. */ + RTLISTANCHOR lstDevInput; + /** List of found host output devices. */ + RTLISTANCHOR lstDevOutput; + /** DirectSound configuration options. */ + DSOUNDHOSTCFG Cfg; + /** Whether this backend supports any audio input. */ + bool fEnabledIn; + /** Whether this backend supports any audio output. */ + bool fEnabledOut; + /** The Direct Sound playback interface. */ + LPDIRECTSOUND8 pDS; + /** The Direct Sound capturing interface. */ + LPDIRECTSOUNDCAPTURE8 pDSC; +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + VBoxMMNotificationClient *m_pNotificationClient; +#endif +#ifdef VBOX_WITH_AUDIO_CALLBACKS + /** Callback function to the upper driver. + * Can be NULL if not being used / registered. */ + PFNPDMHOSTAUDIOCALLBACK pfnCallback; +#endif + /** Pointer to the input stream. */ + PDSOUNDSTREAM pDSStrmIn; + /** Pointer to the output stream. */ + PDSOUNDSTREAM pDSStrmOut; +} DRVHOSTDSOUND, *PDRVHOSTDSOUND; + +/** No flags specified. */ +#define DSOUNDENUMCBFLAGS_NONE 0 +/** (Release) log found devices. */ +#define DSOUNDENUMCBFLAGS_LOG RT_BIT(0) + +/** + * Callback context for enumeration callbacks + */ +typedef struct DSOUNDENUMCBCTX +{ + /** Pointer to host backend driver. */ + PDRVHOSTDSOUND pDrv; + /** Enumeration flags. */ + uint32_t fFlags; + /** Number of found input devices. */ + uint8_t cDevIn; + /** Number of found output devices. */ + uint8_t cDevOut; +} DSOUNDENUMCBCTX, *PDSOUNDENUMCBCTX; + +typedef struct DSOUNDDEV +{ + RTLISTNODE Node; + char *pszName; + GUID Guid; +} DSOUNDDEV, *PDSOUNDDEV; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB); +static HRESULT directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS); +static HRESULT directSoundPlayStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush); +static HRESULT directSoundCaptureStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush); + +static void dsoundDeviceRemove(PDSOUNDDEV pDev); +static int dsoundDevicesEnumerate(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDCFG pCfg); + +static int dsoundStreamEnable(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fEnable); +static void dsoundStreamReset(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS); +static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis); +static void dsoundUpdateStatusInternalEx(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum); + + +static DWORD dsoundRingDistance(DWORD offEnd, DWORD offBegin, DWORD cSize) +{ + AssertReturn(offEnd <= cSize, 0); + AssertReturn(offBegin <= cSize, 0); + + return offEnd >= offBegin ? offEnd - offBegin : cSize - offBegin + offEnd; +} + +static int dsoundWaveFmtFromCfg(PPDMAUDIOSTREAMCFG pCfg, PWAVEFORMATEX pFmt) +{ + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + AssertPtrReturn(pFmt, VERR_INVALID_POINTER); + + RT_BZERO(pFmt, sizeof(WAVEFORMATEX)); + + pFmt->wFormatTag = WAVE_FORMAT_PCM; + pFmt->nChannels = pCfg->Props.cChannels; + pFmt->wBitsPerSample = pCfg->Props.cBytes * 8; + pFmt->nSamplesPerSec = pCfg->Props.uHz; + pFmt->nBlockAlign = pFmt->nChannels * pFmt->wBitsPerSample / 8; + pFmt->nAvgBytesPerSec = pFmt->nSamplesPerSec * pFmt->nBlockAlign; + pFmt->cbSize = 0; /* No extra data specified. */ + + return VINF_SUCCESS; +} + +/** + * Retrieves the number of free bytes available for writing to a DirectSound output stream. + * + * @return IPRT status code. VERR_NOT_AVAILABLE if unable to determine or the buffer was not recoverable. + * @param pThis Host audio driver instance. + * @param pStreamDS DirectSound output stream to retrieve number for. + * @param pdwFree Where to return the free amount on success. + */ +static int dsoundGetFreeOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, DWORD *pdwFree) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER); + AssertPtrReturn(pdwFree, VERR_INVALID_POINTER); + + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); /* Paranoia. */ + + LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB; + if (!pDSB) + { + AssertPtr(pDSB); + return VERR_INVALID_POINTER; + } + + HRESULT hr = S_OK; + + /* Get the current play position which is used for calculating the free space in the buffer. */ + for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) + { + DWORD cbPlayCursor, cbWriteCursor; + hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &cbPlayCursor, &cbWriteCursor); + if (SUCCEEDED(hr)) + { + int32_t cbDiff = cbWriteCursor - cbPlayCursor; + if (cbDiff < 0) + cbDiff += pStreamDS->cbBufSize; + + int32_t cbFree = cbPlayCursor - pStreamDS->Out.offWritePos; + if (cbFree < 0) + cbFree += pStreamDS->cbBufSize; + + if (cbFree > (int32_t)pStreamDS->cbBufSize - cbDiff) + { + pStreamDS->Out.offWritePos = cbWriteCursor; + cbFree = pStreamDS->cbBufSize - cbDiff; + } + + /* When starting to use a DirectSound buffer, cbPlayCursor and cbWriteCursor + * both point at position 0, so we won't be able to detect how many bytes + * are writable that way. + * + * So use our per-stream written indicator to see if we just started a stream. */ + if (pStreamDS->Out.cbWritten == 0) + cbFree = pStreamDS->cbBufSize; + + DSLOGREL(("DSound: cbPlayCursor=%RU32, cbWriteCursor=%RU32, offWritePos=%RU32 -> cbFree=%RI32\n", + cbPlayCursor, cbWriteCursor, pStreamDS->Out.offWritePos, cbFree)); + + *pdwFree = cbFree; + + return VINF_SUCCESS; + } + + if (hr != DSERR_BUFFERLOST) /** @todo MSDN doesn't state this error for GetCurrentPosition(). */ + break; + + LogFunc(("Getting playing position failed due to lost buffer, restoring ...\n")); + + directSoundPlayRestore(pThis, pDSB); + } + + if (hr != DSERR_BUFFERLOST) /* Avoid log flooding if the error is still there. */ + DSLOGREL(("DSound: Getting current playback position failed with %Rhrc\n", hr)); + + LogFunc(("Failed with %Rhrc\n", hr)); + + return VERR_NOT_AVAILABLE; +} + +static char *dsoundGUIDToUtf8StrA(LPCGUID pGUID) +{ + if (pGUID) + { + LPOLESTR lpOLEStr; + HRESULT hr = StringFromCLSID(*pGUID, &lpOLEStr); + if (SUCCEEDED(hr)) + { + char *pszGUID; + int rc = RTUtf16ToUtf8(lpOLEStr, &pszGUID); + CoTaskMemFree(lpOLEStr); + + return RT_SUCCESS(rc) ? pszGUID : NULL; + } + } + + return RTStrDup("{Default device}"); +} + +/** + * Clears the list of the host's playback + capturing devices. + * + * @param pThis Host audio driver instance. + */ +static void dsoundDevicesClear(PDRVHOSTDSOUND pThis) +{ + AssertPtrReturnVoid(pThis); + + PDSOUNDDEV pDev, pDevNext; + RTListForEachSafe(&pThis->lstDevInput, pDev, pDevNext, DSOUNDDEV, Node) + dsoundDeviceRemove(pDev); + + Assert(RTListIsEmpty(&pThis->lstDevInput)); + + RTListForEachSafe(&pThis->lstDevOutput, pDev, pDevNext, DSOUNDDEV, Node) + dsoundDeviceRemove(pDev); + + Assert(RTListIsEmpty(&pThis->lstDevOutput)); +} + +static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB) +{ + RT_NOREF(pThis); + HRESULT hr = IDirectSoundBuffer8_Restore(pDSB); + if (FAILED(hr)) + DSLOG(("DSound: Restoring playback buffer\n")); + else + DSLOGREL(("DSound: Restoring playback buffer failed with %Rhrc\n", hr)); + + return hr; +} + +static HRESULT directSoundPlayUnlock(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB, + PVOID pv1, PVOID pv2, + DWORD cb1, DWORD cb2) +{ + RT_NOREF(pThis); + HRESULT hr = IDirectSoundBuffer8_Unlock(pDSB, pv1, cb1, pv2, cb2); + if (FAILED(hr)) + DSLOGREL(("DSound: Unlocking playback buffer failed with %Rhrc\n", hr)); + return hr; +} + +static HRESULT directSoundCaptureUnlock(LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB, + PVOID pv1, PVOID pv2, + DWORD cb1, DWORD cb2) +{ + HRESULT hr = IDirectSoundCaptureBuffer8_Unlock(pDSCB, pv1, cb1, pv2, cb2); + if (FAILED(hr)) + DSLOGREL(("DSound: Unlocking capture buffer failed with %Rhrc\n", hr)); + return hr; +} + +static HRESULT directSoundPlayLock(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, + DWORD dwOffset, DWORD dwBytes, + PVOID *ppv1, PVOID *ppv2, + DWORD *pcb1, DWORD *pcb2, + DWORD dwFlags) +{ + AssertReturn(dwBytes, VERR_INVALID_PARAMETER); + + HRESULT hr = E_FAIL; + AssertCompile(DRV_DSOUND_RESTORE_ATTEMPTS_MAX > 0); + for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) + { + PVOID pv1, pv2; + DWORD cb1, cb2; + hr = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, dwOffset, dwBytes, &pv1, &cb1, &pv2, &cb2, dwFlags); + if (SUCCEEDED(hr)) + { + if ( (!pv1 || !(cb1 & pStreamDS->uAlign)) + && (!pv2 || !(cb2 & pStreamDS->uAlign))) + { + if (ppv1) + *ppv1 = pv1; + if (ppv2) + *ppv2 = pv2; + if (pcb1) + *pcb1 = cb1; + if (pcb2) + *pcb2 = cb2; + return S_OK; + } + DSLOGREL(("DSound: Locking playback buffer returned misaligned buffer: cb1=%#RX32, cb2=%#RX32 (alignment: %#RX32)\n", + *pcb1, *pcb2, pStreamDS->uAlign)); + directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2); + return E_FAIL; + } + + if (hr != DSERR_BUFFERLOST) + break; + + LogFlowFunc(("Locking failed due to lost buffer, restoring ...\n")); + directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + } + + DSLOGREL(("DSound: Locking playback buffer failed with %Rhrc (dwOff=%ld, dwBytes=%ld)\n", hr, dwOffset, dwBytes)); + return hr; +} + +static HRESULT directSoundCaptureLock(PDSOUNDSTREAM pStreamDS, + DWORD dwOffset, DWORD dwBytes, + PVOID *ppv1, PVOID *ppv2, + DWORD *pcb1, DWORD *pcb2, + DWORD dwFlags) +{ + PVOID pv1 = NULL; + PVOID pv2 = NULL; + DWORD cb1 = 0; + DWORD cb2 = 0; + + HRESULT hr = IDirectSoundCaptureBuffer8_Lock(pStreamDS->In.pDSCB, dwOffset, dwBytes, + &pv1, &cb1, &pv2, &cb2, dwFlags); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Locking capture buffer failed with %Rhrc\n", hr)); + return hr; + } + + if ( (pv1 && (cb1 & pStreamDS->uAlign)) + || (pv2 && (cb2 & pStreamDS->uAlign))) + { + DSLOGREL(("DSound: Locking capture buffer returned misaligned buffer: cb1=%RI32, cb2=%RI32 (alignment: %RU32)\n", + cb1, cb2, pStreamDS->uAlign)); + directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2); + return E_FAIL; + } + + *ppv1 = pv1; + *ppv2 = pv2; + *pcb1 = cb1; + *pcb2 = cb2; + + return S_OK; +} + +/* + * DirectSound playback + */ + +static void directSoundPlayInterfaceDestroy(PDRVHOSTDSOUND pThis) +{ + if (pThis->pDS) + { + LogFlowFuncEnter(); + + IDirectSound8_Release(pThis->pDS); + pThis->pDS = NULL; + } +} + +static HRESULT directSoundPlayInterfaceCreate(PDRVHOSTDSOUND pThis) +{ + if (pThis->pDS != NULL) + return S_OK; + + LogFlowFuncEnter(); + + HRESULT hr = CoCreateInstance(CLSID_DirectSound8, NULL, CLSCTX_ALL, + IID_IDirectSound8, (void **)&pThis->pDS); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Creating playback instance failed with %Rhrc\n", hr)); + } + else + { + hr = IDirectSound8_Initialize(pThis->pDS, pThis->Cfg.pGuidPlay); + if (SUCCEEDED(hr)) + { + HWND hWnd = GetDesktopWindow(); + hr = IDirectSound8_SetCooperativeLevel(pThis->pDS, hWnd, DSSCL_PRIORITY); + if (FAILED(hr)) + DSLOGREL(("DSound: Setting cooperative level for window %p failed with %Rhrc\n", hWnd, hr)); + } + + if (FAILED(hr)) + { + if (hr == DSERR_NODRIVER) /* Usually means that no playback devices are attached. */ + DSLOGREL(("DSound: DirectSound playback is currently unavailable\n")); + else + DSLOGREL(("DSound: DirectSound playback initialization failed with %Rhrc\n", hr)); + + directSoundPlayInterfaceDestroy(pThis); + } + } + + LogFlowFunc(("Returning %Rhrc\n", hr)); + return hr; +} + +static HRESULT directSoundPlayClose(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + AssertPtrReturn(pThis, E_POINTER); + AssertPtrReturn(pStreamDS, E_POINTER); + + LogFlowFuncEnter(); + + HRESULT hr = directSoundPlayStop(pThis, pStreamDS, true /* fFlush */); + if (FAILED(hr)) + return hr; + + DSLOG(("DSound: Closing playback stream\n")); + + if (pStreamDS->pCircBuf) + Assert(RTCircBufUsed(pStreamDS->pCircBuf) == 0); + + if (SUCCEEDED(hr)) + { + RTCritSectEnter(&pThis->CritSect); + + if (pStreamDS->pCircBuf) + { + RTCircBufDestroy(pStreamDS->pCircBuf); + pStreamDS->pCircBuf = NULL; + } + + if (pStreamDS->Out.pDSB) + { + IDirectSoundBuffer8_Release(pStreamDS->Out.pDSB); + pStreamDS->Out.pDSB = NULL; + } + + pThis->pDSStrmOut = NULL; + + RTCritSectLeave(&pThis->CritSect); + } + + if (FAILED(hr)) + DSLOGREL(("DSound: Stopping playback stream %p failed with %Rhrc\n", pStreamDS, hr)); + + return hr; +} + +static HRESULT directSoundPlayOpen(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pThis, E_POINTER); + AssertPtrReturn(pStreamDS, E_POINTER); + AssertPtrReturn(pCfgReq, E_POINTER); + AssertPtrReturn(pCfgAcq, E_POINTER); + + LogFlowFuncEnter(); + + Assert(pStreamDS->Out.pDSB == NULL); + + DSLOG(("DSound: Opening playback stream (uHz=%RU32, cChannels=%RU8, cBits=%RU8, fSigned=%RTbool)\n", + pCfgReq->Props.uHz, + pCfgReq->Props.cChannels, + pCfgReq->Props.cBytes * 8, + pCfgReq->Props.fSigned)); + + WAVEFORMATEX wfx; + int rc = dsoundWaveFmtFromCfg(pCfgReq, &wfx); + if (RT_FAILURE(rc)) + return E_INVALIDARG; + + DSLOG(("DSound: Requested playback format:\n" + " wFormatTag = %RI16\n" + " nChannels = %RI16\n" + " nSamplesPerSec = %RU32\n" + " nAvgBytesPerSec = %RU32\n" + " nBlockAlign = %RI16\n" + " wBitsPerSample = %RI16\n" + " cbSize = %RI16\n", + wfx.wFormatTag, + wfx.nChannels, + wfx.nSamplesPerSec, + wfx.nAvgBytesPerSec, + wfx.nBlockAlign, + wfx.wBitsPerSample, + wfx.cbSize)); + + dsoundUpdateStatusInternal(pThis); + + HRESULT hr = directSoundPlayInterfaceCreate(pThis); + if (FAILED(hr)) + return hr; + + do /* To use breaks. */ + { + LPDIRECTSOUNDBUFFER pDSB = NULL; + + DSBUFFERDESC bd; + RT_ZERO(bd); + bd.dwSize = sizeof(bd); + bd.lpwfxFormat = &wfx; + + /* + * As we reuse our (secondary) buffer for playing out data as it comes in, + * we're using this buffer as a so-called streaming buffer. + * + * See https://msdn.microsoft.com/en-us/library/windows/desktop/ee419014(v=vs.85).aspx + * + * However, as we do not want to use memory on the sound device directly + * (as most modern audio hardware on the host doesn't have this anyway), + * we're *not* going to use DSBCAPS_STATIC for that. + * + * Instead we're specifying DSBCAPS_LOCSOFTWARE, as this fits the bill + * of copying own buffer data to our secondary's Direct Sound buffer. + */ + bd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE; + bd.dwBufferBytes = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props); + + DSLOG(("DSound: Requested playback buffer is %RU64ms (%ld bytes)\n", + DrvAudioHlpBytesToMilli(bd.dwBufferBytes, &pCfgReq->Props), bd.dwBufferBytes)); + + hr = IDirectSound8_CreateSoundBuffer(pThis->pDS, &bd, &pDSB, NULL); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Creating playback sound buffer failed with %Rhrc\n", hr)); + break; + } + + /* "Upgrade" to IDirectSoundBuffer8 interface. */ + hr = IDirectSoundBuffer_QueryInterface(pDSB, IID_IDirectSoundBuffer8, (PVOID *)&pStreamDS->Out.pDSB); + IDirectSoundBuffer_Release(pDSB); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Querying playback sound buffer interface failed with %Rhrc\n", hr)); + break; + } + + /* + * Query the actual parameters set for this stream. + * Those might be different than the initially requested parameters. + */ + RT_ZERO(wfx); + hr = IDirectSoundBuffer8_GetFormat(pStreamDS->Out.pDSB, &wfx, sizeof(wfx), NULL); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Getting playback format failed with %Rhrc\n", hr)); + break; + } + + DSBCAPS bc; + RT_ZERO(bc); + bc.dwSize = sizeof(bc); + + hr = IDirectSoundBuffer8_GetCaps(pStreamDS->Out.pDSB, &bc); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Getting playback capabilities failed with %Rhrc\n", hr)); + break; + } + + DSLOG(("DSound: Acquired playback buffer is %RU64ms (%ld bytes)\n", + DrvAudioHlpBytesToMilli(bc.dwBufferBytes, &pCfgReq->Props), bc.dwBufferBytes)); + + DSLOG(("DSound: Acquired playback format:\n" + " dwBufferBytes = %RI32\n" + " dwFlags = 0x%x\n" + " wFormatTag = %RI16\n" + " nChannels = %RI16\n" + " nSamplesPerSec = %RU32\n" + " nAvgBytesPerSec = %RU32\n" + " nBlockAlign = %RI16\n" + " wBitsPerSample = %RI16\n" + " cbSize = %RI16\n", + bc.dwBufferBytes, + bc.dwFlags, + wfx.wFormatTag, + wfx.nChannels, + wfx.nSamplesPerSec, + wfx.nAvgBytesPerSec, + wfx.nBlockAlign, + wfx.wBitsPerSample, + wfx.cbSize)); + + if (bc.dwBufferBytes & pStreamDS->uAlign) + DSLOGREL(("DSound: Playback capabilities returned misaligned buffer: size %RU32, alignment %RU32\n", + bc.dwBufferBytes, pStreamDS->uAlign + 1)); + + /* + * Initial state. + * dsoundPlayStart initializes part of it to make sure that Stop/Start continues with a correct + * playback buffer position. + */ + pStreamDS->cbBufSize = bc.dwBufferBytes; + + rc = RTCircBufCreate(&pStreamDS->pCircBuf, pStreamDS->cbBufSize) * 2; /* Use "double buffering" */ + AssertRC(rc); + + pThis->pDSStrmOut = pStreamDS; + + const uint32_t cfBufSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamDS->cbBufSize); + + pCfgAcq->Backend.cfBufferSize = cfBufSize; + pCfgAcq->Backend.cfPeriod = cfBufSize / 4; + pCfgAcq->Backend.cfPreBuf = pCfgAcq->Backend.cfPeriod * 2; + + } while (0); + + if (FAILED(hr)) + directSoundPlayClose(pThis, pStreamDS); + + return hr; +} + +static void dsoundPlayClearBuffer(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + AssertPtrReturnVoid(pStreamDS); + + PPDMAUDIOPCMPROPS pProps = &pStreamDS->Cfg.Props; + + HRESULT hr = IDirectSoundBuffer_SetCurrentPosition(pStreamDS->Out.pDSB, 0 /* Position */); + if (FAILED(hr)) + DSLOGREL(("DSound: Setting current position to 0 when clearing buffer failed with %Rhrc\n", hr)); + + PVOID pv1; + hr = directSoundPlayLock(pThis, pStreamDS, + 0 /* dwOffset */, pStreamDS->cbBufSize, + &pv1, NULL, 0, 0, DSBLOCK_ENTIREBUFFER); + if (SUCCEEDED(hr)) + { + DrvAudioHlpClearBuf(pProps, pv1, pStreamDS->cbBufSize, PDMAUDIOPCMPROPS_B2F(pProps, pStreamDS->cbBufSize)); + + directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, NULL, 0, 0); + + /* Make sure to get the last playback position and current write position from DirectSound again. + * Those positions in theory could have changed, re-fetch them to be sure. */ + hr = IDirectSoundBuffer_GetCurrentPosition(pStreamDS->Out.pDSB, + &pStreamDS->Out.offPlayCursorLastPlayed, &pStreamDS->Out.offWritePos); + if (FAILED(hr)) + DSLOGREL(("DSound: Re-fetching current position when clearing buffer failed with %Rhrc\n", hr)); + } +} + +/** + * Transfers audio data from the internal buffer to the DirectSound playback instance. + * Due to internal accounting and querying DirectSound, this function knows how much it can transfer at once. + * + * @return IPRT status code. + * @param pThis Host audio driver instance. + * @param pStreamDS Stream to transfer playback data for. + */ +static int dsoundPlayTransfer(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + if (!pStreamDS->fEnabled) + { + Log2Func(("Stream disabled, skipping\n")); + return VINF_SUCCESS; + } + + uint32_t cbTransferred = 0; + + PRTCIRCBUF pCircBuf = pStreamDS->pCircBuf; + AssertPtr(pCircBuf); + + LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB; + AssertPtr(pDSB); + + int rc = VINF_SUCCESS; + + DWORD offPlayCursor, offWriteCursor; + HRESULT hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &offPlayCursor, &offWriteCursor); + if (FAILED(hr)) + { + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + return rc; + } + + DWORD cbFree, cbRemaining; + if (pStreamDS->Out.fFirstTransfer) + { + cbRemaining = 0; + cbFree = pStreamDS->cbBufSize; + } + else + { + cbFree = dsoundRingDistance(offPlayCursor, pStreamDS->Out.offWritePos, pStreamDS->cbBufSize); + cbRemaining = dsoundRingDistance(pStreamDS->Out.offWritePos, offPlayCursor, pStreamDS->cbBufSize); + } + + uint32_t cbAvail = (uint32_t)RTCircBufUsed(pCircBuf); + uint32_t cbToTransfer = RT_MIN(cbFree, cbAvail); + +#ifdef LOG_ENABLED + if (!pStreamDS->Dbg.tsLastTransferredMs) + pStreamDS->Dbg.tsLastTransferredMs = RTTimeMilliTS(); + Log3Func(("offPlay=%RU32, offWrite=%RU32, tsLastTransferredMs=%RU64ms, cbAvail=%RU32, cbFree=%RU32 -> cbToTransfer=%RU32 " + "(fFirst=%RTbool, fDrain=%RTbool)\n", + offPlayCursor, offWriteCursor, RTTimeMilliTS() - pStreamDS->Dbg.tsLastTransferredMs, cbAvail, cbFree, cbToTransfer, + pStreamDS->Out.fFirstTransfer, pStreamDS->Out.fDrain)); + pStreamDS->Dbg.tsLastTransferredMs = RTTimeMilliTS(); +#endif + + while (cbToTransfer) + { + DWORD cb1 = 0; + DWORD cb2 = 0; + + void *pvBuf; + size_t cbBuf; + RTCircBufAcquireReadBlock(pCircBuf, cbToTransfer, &pvBuf, &cbBuf); + + if (cbBuf) + { + PVOID pv1, pv2; + hr = directSoundPlayLock(pThis, pStreamDS, pStreamDS->Out.offWritePos, (DWORD)cbBuf, + &pv1, &pv2, &cb1, &cb2, 0 /* dwFlags */); + if (FAILED(hr)) + { + rc = VERR_ACCESS_DENIED; + break; + } + + AssertPtr(pv1); + Assert(cb1); + + memcpy(pv1, pvBuf, cb1); + + if (pv2 && cb2) /* Buffer wrap-around? Write second part. */ + memcpy(pv2, (uint8_t *)pvBuf + cb1, cb2); + + directSoundPlayUnlock(pThis, pDSB, pv1, pv2, cb1, cb2); + + pStreamDS->Out.offWritePos = (pStreamDS->Out.offWritePos + cb1 + cb2) % pStreamDS->cbBufSize; + + Assert(cbToTransfer >= cbBuf); + cbToTransfer -= (uint32_t)cbBuf; + + cbTransferred += cb1 + cb2; + } + + RTCircBufReleaseReadBlock(pCircBuf, cb1 + cb2); + } + + pStreamDS->Out.cbTransferred += cbTransferred; + + if ( pStreamDS->Out.fFirstTransfer + && pStreamDS->Out.cbTransferred >= DrvAudioHlpFramesToBytes(pStreamDS->Cfg.Backend.cfPreBuf, &pStreamDS->Cfg.Props)) + { + hr = directSoundPlayStart(pThis, pStreamDS); + if (SUCCEEDED(hr)) + { + pStreamDS->Out.fFirstTransfer = false; + } + else + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + + cbAvail = (uint32_t)RTCircBufUsed(pCircBuf); + if ( !cbAvail + && cbTransferred) + { + pStreamDS->Out.cbLastTransferred = cbTransferred; + pStreamDS->Out.tsLastTransferredMs = RTTimeMilliTS(); + + LogFlowFunc(("cbLastTransferred=%RU32, tsLastTransferredMs=%RU64\n", + pStreamDS->Out.cbLastTransferred, pStreamDS->Out.tsLastTransferredMs)); + } + + LogFlowFunc(("cbTransferred=%RU32, cbAvail=%RU32, rc=%Rrc\n", cbTransferred, cbAvail, rc)); + return rc; +} + +static HRESULT directSoundPlayGetStatus(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB, DWORD *pdwStatus) +{ + AssertPtrReturn(pThis, E_POINTER); + AssertPtrReturn(pDSB, E_POINTER); + + AssertPtrNull(pdwStatus); + + DWORD dwStatus = 0; + HRESULT hr = E_FAIL; + for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) + { + hr = IDirectSoundBuffer8_GetStatus(pDSB, &dwStatus); + if ( hr == DSERR_BUFFERLOST + || ( SUCCEEDED(hr) + && (dwStatus & DSBSTATUS_BUFFERLOST))) + { + LogFlowFunc(("Getting status failed due to lost buffer, restoring ...\n")); + directSoundPlayRestore(pThis, pDSB); + } + else + break; + } + + if (SUCCEEDED(hr)) + { + if (pdwStatus) + *pdwStatus = dwStatus; + } + else + DSLOGREL(("DSound: Retrieving playback status failed with %Rhrc\n", hr)); + + return hr; +} + +static HRESULT directSoundPlayStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush) +{ + AssertPtrReturn(pThis, E_POINTER); + AssertPtrReturn(pStreamDS, E_POINTER); + + HRESULT hr = S_OK; + + if (pStreamDS->Out.pDSB) + { + DSLOG(("DSound: Stopping playback\n")); + hr = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); + if (FAILED(hr)) + { + hr = directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + if (FAILED(hr)) + hr = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); + } + } + + if (SUCCEEDED(hr)) + { + if (fFlush) + dsoundStreamReset(pThis, pStreamDS); + } + + if (FAILED(hr)) + DSLOGREL(("DSound: %s playback failed with %Rhrc\n", fFlush ? "Stopping" : "Pausing", hr)); + + return hr; +} + +/** + * Enables or disables a stream. + * + * @return IPRT status code. + * @param pThis Host audio driver instance. + * @param pStreamDS Stream to enable / disable. + * @param fEnable Whether to enable or disable the stream. + */ +static int dsoundStreamEnable(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fEnable) +{ + RT_NOREF(pThis); + + LogFunc(("%s %s\n", + fEnable ? "Enabling" : "Disabling", + pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN ? "capture" : "playback")); + + if (fEnable) + dsoundStreamReset(pThis, pStreamDS); + + pStreamDS->fEnabled = fEnable; + + return VINF_SUCCESS; +} + + +/** + * Resets the state of a DirectSound stream. + * + * @param pThis Host audio driver instance. + * @param pStreamDS Stream to reset state for. + */ +static void dsoundStreamReset(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + RT_NOREF(pThis); + + LogFunc(("Resetting %s\n", + pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN ? "capture" : "playback")); + + if (pStreamDS->pCircBuf) + RTCircBufReset(pStreamDS->pCircBuf); + + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + { + pStreamDS->In.offReadPos = 0; + pStreamDS->In.cOverruns = 0; + + /* Also reset the DirectSound Capture Buffer (DSCB) by clearing all data to make sure + * not stale audio data is left. */ + if (pStreamDS->In.pDSCB) + { + PVOID pv1; PVOID pv2; DWORD cb1; DWORD cb2; + HRESULT hr = directSoundCaptureLock(pStreamDS, 0 /* Offset */, pStreamDS->cbBufSize, &pv1, &pv2, &cb1, &cb2, + 0 /* Flags */); + if (SUCCEEDED(hr)) + { + DrvAudioHlpClearBuf(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1)); + if (pv2 && cb2) + DrvAudioHlpClearBuf(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2)); + directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2); + } + } + } + else if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT) + { + pStreamDS->Out.fFirstTransfer = true; + pStreamDS->Out.fDrain = false; + pStreamDS->Out.cUnderruns = 0; + + pStreamDS->Out.cbLastTransferred = 0; + pStreamDS->Out.tsLastTransferredMs = 0; + + pStreamDS->Out.cbTransferred = 0; + pStreamDS->Out.cbWritten = 0; + + pStreamDS->Out.offWritePos = 0; + pStreamDS->Out.offPlayCursorLastPending = 0; + pStreamDS->Out.offPlayCursorLastPlayed = 0; + + /* Also reset the DirectSound Buffer (DSB) by setting the position to 0 and clear all data to make sure + * not stale audio data is left. */ + if (pStreamDS->Out.pDSB) + { + HRESULT hr = IDirectSoundBuffer8_SetCurrentPosition(pStreamDS->Out.pDSB, 0); + if (SUCCEEDED(hr)) + { + PVOID pv1; PVOID pv2; DWORD cb1; DWORD cb2; + hr = directSoundPlayLock(pThis, pStreamDS, 0 /* Offset */, pStreamDS->cbBufSize, &pv1, &pv2, &cb1, &cb2, + 0 /* Flags */); + if (SUCCEEDED(hr)) + { + DrvAudioHlpClearBuf(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1)); + if (pv2 && cb2) + DrvAudioHlpClearBuf(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2)); + directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2); + } + } + } + } + +#ifdef LOG_ENABLED + pStreamDS->Dbg.tsLastTransferredMs = 0; +#endif +} + + +/** + * Starts playing a DirectSound stream. + * + * @return HRESULT + * @param pThis Host audio driver instance. + * @param pStreamDS Stream to start playing. + */ +static HRESULT directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + HRESULT hr = S_OK; + + DWORD fFlags = DSCBSTART_LOOPING; + + for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) + { + DSLOG(("DSound: Starting playback\n")); + hr = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, fFlags); + if ( SUCCEEDED(hr) + || hr != DSERR_BUFFERLOST) + break; + else + { + LogFunc(("Restarting playback failed due to lost buffer, restoring ...\n")); + directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + } + } + + return hr; +} + + +/* + * DirectSoundCapture + */ + +static LPCGUID dsoundCaptureSelectDevice(PDRVHOSTDSOUND pThis, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, NULL); + AssertPtrReturn(pCfg, NULL); + + int rc = VINF_SUCCESS; + + LPCGUID pGUID = pThis->Cfg.pGuidCapture; + if (!pGUID) + { + PDSOUNDDEV pDev = NULL; + + switch (pCfg->DestSource.Source) + { + case PDMAUDIORECSOURCE_LINE: + /* + * At the moment we're only supporting line-in in the HDA emulation, + * and line-in + mic-in in the AC'97 emulation both are expected + * to use the host's mic-in as well. + * + * So the fall through here is intentional for now. + */ + case PDMAUDIORECSOURCE_MIC: + { + RTListForEach(&pThis->lstDevInput, pDev, DSOUNDDEV, Node) + { + if (RTStrIStr(pDev->pszName, "Mic")) /** @todo What is with non en_us windows versions? */ + break; + } + + if (RTListNodeIsDummy(&pThis->lstDevInput, pDev, DSOUNDDEV, Node)) + pDev = NULL; /* Found nothing. */ + + break; + } + + default: + AssertFailedStmt(rc = VERR_NOT_SUPPORTED); + break; + } + + if ( RT_SUCCESS(rc) + && pDev) + { + DSLOG(("DSound: Guest source '%s' is using host recording device '%s'\n", + DrvAudioHlpRecSrcToStr(pCfg->DestSource.Source), pDev->pszName)); + + pGUID = &pDev->Guid; + } + } + + if (RT_FAILURE(rc)) + { + LogRel(("DSound: Selecting recording device failed with %Rrc\n", rc)); + return NULL; + } + + char *pszGUID = dsoundGUIDToUtf8StrA(pGUID); + + /* This always has to be in the release log. */ + LogRel(("DSound: Guest source '%s' is using host recording device with GUID '%s'\n", + DrvAudioHlpRecSrcToStr(pCfg->DestSource.Source), pszGUID ? pszGUID: "{?}")); + + if (pszGUID) + { + RTStrFree(pszGUID); + pszGUID = NULL; + } + + return pGUID; +} + +/** + * Transfers audio data from the DirectSound capture instance to the internal buffer. + * Due to internal accounting and querying DirectSound, this function knows how much it can transfer at once. + * + * @return IPRT status code. + * @param pThis Host audio driver instance. + * @param pStreamDS Stream to capture audio data for. + */ +static int dsoundCaptureTransfer(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + RT_NOREF(pThis); + + LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB = pStreamDS->In.pDSCB; + AssertPtr(pDSCB); + + DWORD offCaptureCursor; + HRESULT hr = IDirectSoundCaptureBuffer_GetCurrentPosition(pDSCB, NULL, &offCaptureCursor); + if (FAILED(hr)) + { + AssertFailed(); + return VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + + DWORD cbUsed = dsoundRingDistance(offCaptureCursor, pStreamDS->In.offReadPos, pStreamDS->cbBufSize); + + PRTCIRCBUF pCircBuf = pStreamDS->pCircBuf; + AssertPtr(pCircBuf); + + uint32_t cbFree = (uint32_t)RTCircBufFree(pCircBuf); + if ( !cbFree + || pStreamDS->In.cOverruns < 32) /** @todo Make this configurable. */ + { + DSLOG(("DSound: Warning: Capture buffer full, skipping to record data (%RU32 bytes)\n", cbUsed)); + pStreamDS->In.cOverruns++; + } + + DWORD cbToCapture = RT_MIN(cbUsed, cbFree); + + Log3Func(("cbUsed=%ld, cbToCapture=%ld\n", cbUsed, cbToCapture)); + + while (cbToCapture) + { + void *pvBuf; + size_t cbBuf; + RTCircBufAcquireWriteBlock(pCircBuf, cbToCapture, &pvBuf, &cbBuf); + + if (cbBuf) + { + PVOID pv1, pv2; + DWORD cb1, cb2; + hr = directSoundCaptureLock(pStreamDS, pStreamDS->In.offReadPos, (DWORD)cbBuf, + &pv1, &pv2, &cb1, &cb2, 0 /* dwFlags */); + if (FAILED(hr)) + break; + + AssertPtr(pv1); + Assert(cb1); + + memcpy(pvBuf, pv1, cb1); + + if (pv2 && cb2) /* Buffer wrap-around? Write second part. */ + memcpy((uint8_t *)pvBuf + cb1, pv2, cb2); + + directSoundCaptureUnlock(pDSCB, pv1, pv2, cb1, cb2); + + pStreamDS->In.offReadPos = (pStreamDS->In.offReadPos + cb1 + cb2) % pStreamDS->cbBufSize; + + Assert(cbToCapture >= cbBuf); + cbToCapture -= (uint32_t)cbBuf; + } + +#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA + if (cbBuf) + { + RTFILE fh; + int rc2 = RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "dsoundCapture.pcm", + RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc2)) + { + RTFileWrite(fh, pvBuf, cbBuf, NULL); + RTFileClose(fh); + } + } +#endif + RTCircBufReleaseWriteBlock(pCircBuf, cbBuf); + } + + return VINF_SUCCESS; +} + +/** + * Destroys the DirectSound capturing interface. + * + * @return IPRT status code. + * @param pThis Driver instance to destroy capturing interface for. + */ +static void directSoundCaptureInterfaceDestroy(PDRVHOSTDSOUND pThis) +{ + if (pThis->pDSC) + { + LogFlowFuncEnter(); + + IDirectSoundCapture_Release(pThis->pDSC); + pThis->pDSC = NULL; + } +} + +/** + * Creates the DirectSound capturing interface. + * + * @return IPRT status code. + * @param pThis Driver instance to create the capturing interface for. + * @param pCfg Audio stream to use for creating the capturing interface. + */ +static HRESULT directSoundCaptureInterfaceCreate(PDRVHOSTDSOUND pThis, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, E_POINTER); + AssertPtrReturn(pCfg, E_POINTER); + + if (pThis->pDSC) + return S_OK; + + LogFlowFuncEnter(); + + HRESULT hr = CoCreateInstance(CLSID_DirectSoundCapture8, NULL, CLSCTX_ALL, + IID_IDirectSoundCapture8, (void **)&pThis->pDSC); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Creating capture instance failed with %Rhrc\n", hr)); + } + else + { + LPCGUID pGUID = dsoundCaptureSelectDevice(pThis, pCfg); + /* pGUID can be NULL when using the default device. */ + + hr = IDirectSoundCapture_Initialize(pThis->pDSC, pGUID); + if (FAILED(hr)) + { + if (hr == DSERR_NODRIVER) /* Usually means that no capture devices are attached. */ + DSLOGREL(("DSound: Capture device currently is unavailable\n")); + else + DSLOGREL(("DSound: Initializing capturing device failed with %Rhrc\n", hr)); + + directSoundCaptureInterfaceDestroy(pThis); + } + } + + LogFlowFunc(("Returning %Rhrc\n", hr)); + return hr; +} + +static HRESULT directSoundCaptureClose(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + AssertPtrReturn(pThis, E_POINTER); + AssertPtrReturn(pStreamDS, E_POINTER); + + LogFlowFuncEnter(); + + HRESULT hr = directSoundCaptureStop(pThis, pStreamDS, true /* fFlush */); + if (FAILED(hr)) + return hr; + + if ( pStreamDS + && pStreamDS->In.pDSCB) + { + DSLOG(("DSound: Closing capturing stream\n")); + + IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB); + pStreamDS->In.pDSCB = NULL; + } + + LogFlowFunc(("Returning %Rhrc\n", hr)); + return hr; +} + +static HRESULT directSoundCaptureOpen(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pThis, E_POINTER); + AssertPtrReturn(pStreamDS, E_POINTER); + AssertPtrReturn(pCfgReq, E_POINTER); + AssertPtrReturn(pCfgAcq, E_POINTER); + + LogFlowFuncEnter(); + + Assert(pStreamDS->In.pDSCB == NULL); + + DSLOG(("DSound: Opening capturing stream (uHz=%RU32, cChannels=%RU8, cBits=%RU8, fSigned=%RTbool)\n", + pCfgReq->Props.uHz, + pCfgReq->Props.cChannels, + pCfgReq->Props.cBytes * 8, + pCfgReq->Props.fSigned)); + + WAVEFORMATEX wfx; + int rc = dsoundWaveFmtFromCfg(pCfgReq, &wfx); + if (RT_FAILURE(rc)) + return E_INVALIDARG; + + dsoundUpdateStatusInternalEx(pThis, NULL /* Cfg */, DSOUNDENUMCBFLAGS_LOG /* fEnum */); + + HRESULT hr = directSoundCaptureInterfaceCreate(pThis, pCfgReq); + if (FAILED(hr)) + return hr; + + do /* To use breaks. */ + { + DSCBUFFERDESC bd; + RT_ZERO(bd); + + bd.dwSize = sizeof(bd); + bd.lpwfxFormat = &wfx; + bd.dwBufferBytes = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props); + + DSLOG(("DSound: Requested capture buffer is %RU64ms (%ld bytes)\n", + DrvAudioHlpBytesToMilli(bd.dwBufferBytes, &pCfgReq->Props), bd.dwBufferBytes)); + + LPDIRECTSOUNDCAPTUREBUFFER pDSCB; + hr = IDirectSoundCapture_CreateCaptureBuffer(pThis->pDSC, &bd, &pDSCB, NULL); + if (FAILED(hr)) + { + if (hr == E_ACCESSDENIED) + { + DSLOGREL(("DSound: Capturing input from host not possible, access denied\n")); + } + else + DSLOGREL(("DSound: Creating capture buffer failed with %Rhrc\n", hr)); + break; + } + + hr = IDirectSoundCaptureBuffer_QueryInterface(pDSCB, IID_IDirectSoundCaptureBuffer8, (void **)&pStreamDS->In.pDSCB); + IDirectSoundCaptureBuffer_Release(pDSCB); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Querying interface for capture buffer failed with %Rhrc\n", hr)); + break; + } + + /* + * Query the actual parameters. + */ + DWORD offByteReadPos = 0; + hr = IDirectSoundCaptureBuffer8_GetCurrentPosition(pStreamDS->In.pDSCB, NULL, &offByteReadPos); + if (FAILED(hr)) + { + offByteReadPos = 0; + DSLOGREL(("DSound: Getting capture position failed with %Rhrc\n", hr)); + } + + RT_ZERO(wfx); + hr = IDirectSoundCaptureBuffer8_GetFormat(pStreamDS->In.pDSCB, &wfx, sizeof(wfx), NULL); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Getting capture format failed with %Rhrc\n", hr)); + break; + } + + DSCBCAPS bc; + RT_ZERO(bc); + bc.dwSize = sizeof(bc); + hr = IDirectSoundCaptureBuffer8_GetCaps(pStreamDS->In.pDSCB, &bc); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Getting capture capabilities failed with %Rhrc\n", hr)); + break; + } + + DSLOG(("DSound: Acquired capture buffer is %RU64ms (%ld bytes)\n", + DrvAudioHlpBytesToMilli(bc.dwBufferBytes, &pCfgReq->Props), bc.dwBufferBytes)); + + DSLOG(("DSound: Capture format:\n" + " dwBufferBytes = %RI32\n" + " dwFlags = 0x%x\n" + " wFormatTag = %RI16\n" + " nChannels = %RI16\n" + " nSamplesPerSec = %RU32\n" + " nAvgBytesPerSec = %RU32\n" + " nBlockAlign = %RI16\n" + " wBitsPerSample = %RI16\n" + " cbSize = %RI16\n", + bc.dwBufferBytes, + bc.dwFlags, + wfx.wFormatTag, + wfx.nChannels, + wfx.nSamplesPerSec, + wfx.nAvgBytesPerSec, + wfx.nBlockAlign, + wfx.wBitsPerSample, + wfx.cbSize)); + + if (bc.dwBufferBytes & pStreamDS->uAlign) + DSLOGREL(("DSound: Capture GetCaps returned misaligned buffer: size %RU32, alignment %RU32\n", + bc.dwBufferBytes, pStreamDS->uAlign + 1)); + + /* Initial state: reading at the initial capture position, no error. */ + pStreamDS->In.offReadPos = 0; + pStreamDS->cbBufSize = bc.dwBufferBytes; + + rc = RTCircBufCreate(&pStreamDS->pCircBuf, pStreamDS->cbBufSize) * 2; /* Use "double buffering". */ + AssertRC(rc); + + pThis->pDSStrmIn = pStreamDS; + + pCfgAcq->Backend.cfBufferSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamDS->cbBufSize); + + } while (0); + + if (FAILED(hr)) + directSoundCaptureClose(pThis, pStreamDS); + + LogFlowFunc(("Returning %Rhrc\n", hr)); + return hr; +} + +static HRESULT directSoundCaptureStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush) +{ + AssertPtrReturn(pThis, E_POINTER); + AssertPtrReturn(pStreamDS, E_POINTER); + + RT_NOREF(pThis); + + HRESULT hr = S_OK; + + if (pStreamDS->In.pDSCB) + { + DSLOG(("DSound: Stopping capture\n")); + hr = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB); + } + + if (SUCCEEDED(hr)) + { + if (fFlush) + dsoundStreamReset(pThis, pStreamDS); + } + + if (FAILED(hr)) + DSLOGREL(("DSound: Stopping capture buffer failed with %Rhrc\n", hr)); + + return hr; +} + +static HRESULT directSoundCaptureStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + AssertPtrReturn(pThis, E_POINTER); + AssertPtrReturn(pStreamDS, E_POINTER); + + HRESULT hr; + if (pStreamDS->In.pDSCB) + { + DWORD dwStatus; + hr = IDirectSoundCaptureBuffer8_GetStatus(pStreamDS->In.pDSCB, &dwStatus); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Retrieving capture status failed with %Rhrc\n", hr)); + } + else + { + if (dwStatus & DSCBSTATUS_CAPTURING) + { + DSLOG(("DSound: Already capturing\n")); + } + else + { + const DWORD fFlags = DSCBSTART_LOOPING; + + DSLOG(("DSound: Starting to capture\n")); + hr = IDirectSoundCaptureBuffer8_Start(pStreamDS->In.pDSCB, fFlags); + if (FAILED(hr)) + DSLOGREL(("DSound: Starting to capture failed with %Rhrc\n", hr)); + } + } + } + else + hr = E_UNEXPECTED; + + LogFlowFunc(("Returning %Rhrc\n", hr)); + return hr; +} + +static int dsoundDevAdd(PRTLISTANCHOR pList, LPGUID pGUID, LPCWSTR pwszDescription, PDSOUNDDEV *ppDev) +{ + AssertPtrReturn(pList, VERR_INVALID_POINTER); + AssertPtrReturn(pGUID, VERR_INVALID_POINTER); + AssertPtrReturn(pwszDescription, VERR_INVALID_POINTER); + + PDSOUNDDEV pDev = (PDSOUNDDEV)RTMemAlloc(sizeof(DSOUNDDEV)); + if (!pDev) + return VERR_NO_MEMORY; + + int rc = RTUtf16ToUtf8(pwszDescription, &pDev->pszName); + if ( RT_SUCCESS(rc) + && pGUID) + { + memcpy(&pDev->Guid, pGUID, sizeof(GUID)); + } + + if (RT_SUCCESS(rc)) + RTListAppend(pList, &pDev->Node); + + if (ppDev) + *ppDev = pDev; + + return rc; +} + +static void dsoundDeviceRemove(PDSOUNDDEV pDev) +{ + if (pDev) + { + if (pDev->pszName) + { + RTStrFree(pDev->pszName); + pDev->pszName = NULL; + } + + RTListNodeRemove(&pDev->Node); + + RTMemFree(pDev); + pDev = NULL; + } +} + +static void dsoundLogDevice(const char *pszType, LPGUID pGUID, LPCWSTR pwszDescription, LPCWSTR pwszModule) +{ + char *pszGUID = dsoundGUIDToUtf8StrA(pGUID); + /* This always has to be in the release log. + * Only print this when we're running in verbose (audio debug) mode, as this can generate a lot of content. */ + LogRel2(("DSound: %s: GUID: %s [%ls] (Module: %ls)\n", pszType, pszGUID ? pszGUID : "{?}", pwszDescription, pwszModule)); + RTStrFree(pszGUID); +} + +static BOOL CALLBACK dsoundDevicesEnumCbPlayback(LPGUID pGUID, LPCWSTR pwszDescription, LPCWSTR pwszModule, PVOID lpContext) +{ + PDSOUNDENUMCBCTX pCtx = (PDSOUNDENUMCBCTX)lpContext; + AssertPtrReturn(pCtx, FALSE); + AssertPtrReturn(pCtx->pDrv, FALSE); + + if (!pGUID) + return TRUE; + + AssertPtrReturn(pwszDescription, FALSE); + /* Do not care about pwszModule. */ + + if (pCtx->fFlags & DSOUNDENUMCBFLAGS_LOG) + dsoundLogDevice("Output", pGUID, pwszDescription, pwszModule); + + int rc = dsoundDevAdd(&pCtx->pDrv->lstDevOutput, + pGUID, pwszDescription, NULL /* ppDev */); + if (RT_FAILURE(rc)) + return FALSE; /* Abort enumeration. */ + + pCtx->cDevOut++; + + return TRUE; +} + +static BOOL CALLBACK dsoundDevicesEnumCbCapture(LPGUID pGUID, LPCWSTR pwszDescription, LPCWSTR pwszModule, PVOID lpContext) +{ + PDSOUNDENUMCBCTX pCtx = (PDSOUNDENUMCBCTX)lpContext; + AssertPtrReturn(pCtx, FALSE); + AssertPtrReturn(pCtx->pDrv, FALSE); + + if (!pGUID) + return TRUE; + + if (pCtx->fFlags & DSOUNDENUMCBFLAGS_LOG) + dsoundLogDevice("Input", pGUID, pwszDescription, pwszModule); + + int rc = dsoundDevAdd(&pCtx->pDrv->lstDevInput, + pGUID, pwszDescription, NULL /* ppDev */); + if (RT_FAILURE(rc)) + return FALSE; /* Abort enumeration. */ + + pCtx->cDevIn++; + + return TRUE; +} + +/** + * Does a (Re-)enumeration of the host's playback + capturing devices. + * + * @return IPRT status code. + * @param pThis Host audio driver instance. + * @param pEnmCtx Enumeration context to use. + * @param fEnum Enumeration flags. + */ +static int dsoundDevicesEnumerate(PDRVHOSTDSOUND pThis, PDSOUNDENUMCBCTX pEnmCtx, uint32_t fEnum) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pEnmCtx, VERR_INVALID_POINTER); + + dsoundDevicesClear(pThis); + + RTLDRMOD hDSound = NULL; + int rc = RTLdrLoadSystem("dsound.dll", true /*fNoUnload*/, &hDSound); + if (RT_SUCCESS(rc)) + { + PFNDIRECTSOUNDENUMERATEW pfnDirectSoundEnumerateW = NULL; + PFNDIRECTSOUNDCAPTUREENUMERATEW pfnDirectSoundCaptureEnumerateW = NULL; + + rc = RTLdrGetSymbol(hDSound, "DirectSoundEnumerateW", (void**)&pfnDirectSoundEnumerateW); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hDSound, "DirectSoundCaptureEnumerateW", (void**)&pfnDirectSoundCaptureEnumerateW); + + if (RT_SUCCESS(rc)) + { + HRESULT hr = pfnDirectSoundEnumerateW(&dsoundDevicesEnumCbPlayback, pEnmCtx); + if (FAILED(hr)) + LogRel2(("DSound: Error enumerating host playback devices: %Rhrc\n", hr)); + + hr = pfnDirectSoundCaptureEnumerateW(&dsoundDevicesEnumCbCapture, pEnmCtx); + if (FAILED(hr)) + LogRel2(("DSound: Error enumerating host capturing devices: %Rhrc\n", hr)); + + if (fEnum & DSOUNDENUMCBFLAGS_LOG) + { + LogRel2(("DSound: Found %RU8 host playback devices\n", pEnmCtx->cDevOut)); + LogRel2(("DSound: Found %RU8 host capturing devices\n", pEnmCtx->cDevIn)); + } + } + + RTLdrClose(hDSound); + } + else + { + /* No dsound.dll on this system. */ + LogRel2(("DSound: Could not load dsound.dll: %Rrc\n", rc)); + } + + return rc; +} + +/** + * Updates this host driver's internal status, according to the global, overall input/output + * state and all connected (native) audio streams. + * + * @param pThis Host audio driver instance. + * @param pCfg Where to store the backend configuration. Optional. + * @param fEnum Enumeration flags. + */ +static void dsoundUpdateStatusInternalEx(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum) +{ + AssertPtrReturnVoid(pThis); + /* pCfg is optional. */ + + PDMAUDIOBACKENDCFG Cfg; + RT_ZERO(Cfg); + + Cfg.cbStreamOut = sizeof(DSOUNDSTREAM); + Cfg.cbStreamIn = sizeof(DSOUNDSTREAM); + + DSOUNDENUMCBCTX cbCtx = { pThis, fEnum, 0, 0 }; + + int rc = dsoundDevicesEnumerate(pThis, &cbCtx, fEnum); + if (RT_SUCCESS(rc)) + { +#if 0 + if ( pThis->fEnabledOut != RT_BOOL(cbCtx.cDevOut) + || pThis->fEnabledIn != RT_BOOL(cbCtx.cDevIn)) + { + /** @todo Use a registered callback to the audio connector (e.g "OnConfigurationChanged") to + * let the connector know that something has changed within the host backend. */ + } +#endif + pThis->fEnabledOut = RT_BOOL(cbCtx.cDevOut); + pThis->fEnabledIn = RT_BOOL(cbCtx.cDevIn); + + RTStrPrintf2(Cfg.szName, sizeof(Cfg.szName), "DirectSound audio driver"); + + Cfg.cMaxStreamsIn = UINT32_MAX; + Cfg.cMaxStreamsOut = UINT32_MAX; + + if (pCfg) + memcpy(pCfg, &Cfg, sizeof(PDMAUDIOBACKENDCFG)); + } + + LogFlowFuncLeaveRC(rc); +} + +static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis) +{ + dsoundUpdateStatusInternalEx(pThis, NULL /* pCfg */, 0 /* fEnum */); +} + +static int dsoundCreateStreamOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + LogFlowFunc(("pStreamDS=%p, pCfgReq=%p\n", pStreamDS, pCfgReq)); + + int rc = VINF_SUCCESS; + + /* Try to open playback in case the device is already there. */ + HRESULT hr = directSoundPlayOpen(pThis, pStreamDS, pCfgReq, pCfgAcq); + if (SUCCEEDED(hr)) + { + rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, pCfgAcq); + if (RT_SUCCESS(rc)) + dsoundStreamReset(pThis, pStreamDS); + } + else + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static int dsoundControlStreamOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + LogFlowFunc(("pStreamDS=%p, cmd=%d\n", pStreamDS, enmStreamCmd)); + + int rc = VINF_SUCCESS; + + HRESULT hr; + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + { + dsoundStreamEnable(pThis, pStreamDS, true /* fEnable */); + break; + } + + case PDMAUDIOSTREAMCMD_RESUME: + { + hr = directSoundPlayStart(pThis, pStreamDS); + if (FAILED(hr)) + rc = VERR_NOT_SUPPORTED; /** @todo Fix this. */ + break; + } + + case PDMAUDIOSTREAMCMD_DISABLE: + { + dsoundStreamEnable(pThis, pStreamDS, false /* fEnable */); + hr = directSoundPlayStop(pThis, pStreamDS, true /* fFlush */); + if (FAILED(hr)) + rc = VERR_NOT_SUPPORTED; + break; + } + + case PDMAUDIOSTREAMCMD_PAUSE: + { + hr = directSoundPlayStop(pThis, pStreamDS, false /* fFlush */); + if (FAILED(hr)) + rc = VERR_NOT_SUPPORTED; + break; + } + + case PDMAUDIOSTREAMCMD_DRAIN: + { + /* Make sure we transferred everything. */ + pStreamDS->fEnabled = true; + pStreamDS->Out.fDrain = true; + rc = dsoundPlayTransfer(pThis, pStreamDS); + if ( RT_SUCCESS(rc) + && pStreamDS->Out.fFirstTransfer) + { + /* If this was the first transfer ever for this stream, make sure to also play the (short) audio data. */ + DSLOG(("DSound: Started playing output (short sound)\n")); + + pStreamDS->Out.fFirstTransfer = false; + pStreamDS->Out.cbLastTransferred = pStreamDS->Out.cbTransferred; /* All transferred audio data must be played. */ + pStreamDS->Out.tsLastTransferredMs = RTTimeMilliTS(); + + hr = directSoundPlayStart(pThis, pStreamDS); + if (FAILED(hr)) + rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */ + } + break; + } + + case PDMAUDIOSTREAMCMD_DROP: + { + pStreamDS->Out.cbLastTransferred = 0; + pStreamDS->Out.tsLastTransferredMs = 0; + RTCircBufReset(pStreamDS->pCircBuf); + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +int drvHostDSoundStreamPlay(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf, uint32_t *pcxWritten) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cxBuf, VERR_INVALID_PARAMETER); + /* pcxWritten is optional. */ + + PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + + int rc = VINF_SUCCESS; + + uint32_t cbWrittenTotal = 0; + + uint8_t *pbBuf = (uint8_t *)pvBuf; + PRTCIRCBUF pCircBuf = pStreamDS->pCircBuf; + + uint32_t cbToPlay = RT_MIN(cxBuf, (uint32_t)RTCircBufFree(pCircBuf)); + while (cbToPlay) + { + void *pvChunk; + size_t cbChunk; + RTCircBufAcquireWriteBlock(pCircBuf, cbToPlay, &pvChunk, &cbChunk); + + if (cbChunk) + { + memcpy(pvChunk, pbBuf, cbChunk); + + pbBuf += cbChunk; + Assert(cbToPlay >= cbChunk); + cbToPlay -= (uint32_t)cbChunk; + + cbWrittenTotal += (uint32_t)cbChunk; + } + + RTCircBufReleaseWriteBlock(pCircBuf, cbChunk); + } + + Assert(cbWrittenTotal <= cxBuf); + Assert(cbWrittenTotal == cxBuf); + + pStreamDS->Out.cbWritten += cbWrittenTotal; + + if (RT_SUCCESS(rc)) + { + if (pcxWritten) + *pcxWritten = cbWrittenTotal; + } + else + dsoundUpdateStatusInternal(pThis); + + return rc; +} + +static int dsoundDestroyStreamOut(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + + LogFlowFuncEnter(); + + HRESULT hr = directSoundPlayStop(pThis, pStreamDS, true /* fFlush */); + if (SUCCEEDED(hr)) + { + hr = directSoundPlayClose(pThis, pStreamDS); + if (FAILED(hr)) + return VERR_GENERAL_FAILURE; /** @todo Fix. */ + } + + return VINF_SUCCESS; +} + +static int dsoundCreateStreamIn(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + LogFunc(("pStreamDS=%p, pCfgReq=%p, enmRecSource=%s\n", + pStreamDS, pCfgReq, DrvAudioHlpRecSrcToStr(pCfgReq->DestSource.Source))); + + int rc = VINF_SUCCESS; + + /* Try to open capture in case the device is already there. */ + HRESULT hr = directSoundCaptureOpen(pThis, pStreamDS, pCfgReq, pCfgAcq); + if (SUCCEEDED(hr)) + { + rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, pCfgAcq); + if (RT_SUCCESS(rc)) + dsoundStreamReset(pThis, pStreamDS); + } + else + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + + return rc; +} + +static int dsoundControlStreamIn(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + LogFlowFunc(("pStreamDS=%p, enmStreamCmd=%ld\n", pStreamDS, enmStreamCmd)); + + int rc = VINF_SUCCESS; + + HRESULT hr; + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + dsoundStreamEnable(pThis, pStreamDS, true /* fEnable */); + RT_FALL_THROUGH(); + case PDMAUDIOSTREAMCMD_RESUME: + { + /* Try to start capture. If it fails, then reopen and try again. */ + hr = directSoundCaptureStart(pThis, pStreamDS); + if (FAILED(hr)) + { + hr = directSoundCaptureClose(pThis, pStreamDS); + if (SUCCEEDED(hr)) + { + PDMAUDIOSTREAMCFG CfgAcq; + hr = directSoundCaptureOpen(pThis, pStreamDS, &pStreamDS->Cfg /* pCfgReq */, &CfgAcq); + if (SUCCEEDED(hr)) + { + rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, &CfgAcq); + if (RT_FAILURE(rc)) + break; + + /** @todo What to do if the format has changed? */ + + hr = directSoundCaptureStart(pThis, pStreamDS); + } + } + } + + if (FAILED(hr)) + rc = VERR_NOT_SUPPORTED; + break; + } + + case PDMAUDIOSTREAMCMD_DISABLE: + dsoundStreamEnable(pThis, pStreamDS, false /* fEnable */); + RT_FALL_THROUGH(); + case PDMAUDIOSTREAMCMD_PAUSE: + { + directSoundCaptureStop(pThis, pStreamDS, + enmStreamCmd == PDMAUDIOSTREAMCMD_DISABLE /* fFlush */); + + /* Return success in any case, as stopping the capture can fail if + * the capture buffer is not around anymore. + * + * This can happen if the host's capturing device has been changed suddenly. */ + rc = VINF_SUCCESS; + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + return rc; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +int drvHostDSoundStreamCapture(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead) +{ + + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cxBuf, VERR_INVALID_PARAMETER); + + PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + + int rc = VINF_SUCCESS; + + uint32_t cbReadTotal = 0; + + uint32_t cbToRead = RT_MIN((uint32_t)RTCircBufUsed(pStreamDS->pCircBuf), cxBuf); + while (cbToRead) + { + void *pvChunk; + size_t cbChunk; + RTCircBufAcquireReadBlock(pStreamDS->pCircBuf, cbToRead, &pvChunk, &cbChunk); + + if (cbChunk) + { + memcpy((uint8_t *)pvBuf + cbReadTotal, pvChunk, cbChunk); + cbReadTotal += (uint32_t)cbChunk; + Assert(cbToRead >= cbChunk); + cbToRead -= (uint32_t)cbChunk; + } + + RTCircBufReleaseReadBlock(pStreamDS->pCircBuf, cbChunk); + } + + if (RT_SUCCESS(rc)) + { + if (pcxRead) + *pcxRead = cbReadTotal; + } + else + dsoundUpdateStatusInternal(pThis); + + return rc; +} + +static int dsoundDestroyStreamIn(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + LogFlowFuncEnter(); + + directSoundCaptureClose(pThis, pStreamDS); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +int drvHostDSoundGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); + + dsoundUpdateStatusInternalEx(pThis, pBackendCfg, 0 /* fEnum */); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} + */ +void drvHostDSoundShutdown(PPDMIHOSTAUDIO pInterface) +{ + PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); + + LogFlowFuncEnter(); + + RT_NOREF(pThis); + + LogFlowFuncLeave(); +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} + */ +static DECLCALLBACK(int) drvHostDSoundInit(PPDMIHOSTAUDIO pInterface) +{ + PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); + LogFlowFuncEnter(); + + int rc; + + /* Verify that IDirectSound is available. */ + LPDIRECTSOUND pDirectSound = NULL; + HRESULT hr = CoCreateInstance(CLSID_DirectSound, NULL, CLSCTX_ALL, IID_IDirectSound, (void **)&pDirectSound); + if (SUCCEEDED(hr)) + { + IDirectSound_Release(pDirectSound); + + rc = VINF_SUCCESS; + + dsoundUpdateStatusInternalEx(pThis, NULL /* pCfg */, DSOUNDENUMCBFLAGS_LOG /* fEnum */); + } + else + { + DSLOGREL(("DSound: DirectSound not available: %Rhrc\n", hr)); + rc = VERR_NOT_SUPPORTED; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static LPCGUID dsoundConfigQueryGUID(PCFGMNODE pCfg, const char *pszName, RTUUID *pUuid) +{ + LPCGUID pGuid = NULL; + + char *pszGuid = NULL; + int rc = CFGMR3QueryStringAlloc(pCfg, pszName, &pszGuid); + if (RT_SUCCESS(rc)) + { + rc = RTUuidFromStr(pUuid, pszGuid); + if (RT_SUCCESS(rc)) + pGuid = (LPCGUID)&pUuid; + else + DSLOGREL(("DSound: Error parsing device GUID for device '%s': %Rrc\n", pszName, rc)); + + RTStrFree(pszGuid); + } + + return pGuid; +} + +static int dsoundConfigInit(PDRVHOSTDSOUND pThis, PCFGMNODE pCfg) +{ + pThis->Cfg.pGuidPlay = dsoundConfigQueryGUID(pCfg, "DeviceGuidOut", &pThis->Cfg.uuidPlay); + pThis->Cfg.pGuidCapture = dsoundConfigQueryGUID(pCfg, "DeviceGuidIn", &pThis->Cfg.uuidCapture); + + DSLOG(("DSound: Configuration: DeviceGuidOut {%RTuuid}, DeviceGuidIn {%RTuuid}\n", + &pThis->Cfg.uuidPlay, + &pThis->Cfg.uuidCapture)); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostDSoundGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(enmDir); + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostDSoundStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + + int rc; + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + rc = dsoundCreateStreamIn(pThis, pStreamDS, pCfgReq, pCfgAcq); + else + rc = dsoundCreateStreamOut(pThis, pStreamDS, pCfgReq, pCfgAcq); + + if (RT_SUCCESS(rc)) + { + rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, pCfgAcq); + if (RT_SUCCESS(rc)) + rc = RTCritSectInit(&pStreamDS->CritSect); + } + + return rc; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHostDSoundStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + + int rc; + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + rc = dsoundDestroyStreamIn(pThis, pStreamDS); + else + rc = dsoundDestroyStreamOut(pThis, pStreamDS); + + if (RT_SUCCESS(rc)) + { + if (RTCritSectIsInitialized(&pStreamDS->CritSect)) + rc = RTCritSectDelete(&pStreamDS->CritSect); + } + + return rc; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} + */ +static DECLCALLBACK(int) drvHostDSoundStreamControl(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + + int rc; + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + rc = dsoundControlStreamIn(pThis, pStreamDS, enmStreamCmd); + else + rc = dsoundControlStreamOut(pThis, pStreamDS, enmStreamCmd); + + return rc; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostDSoundStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, PDMAUDIOSTREAMSTS_FLAG_NONE); + + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + + if ( pStreamDS->fEnabled + && pStreamDS->pCircBuf) + { + return (uint32_t)RTCircBufUsed(pStreamDS->pCircBuf); + } + + return 0; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostDSoundStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, PDMAUDIOSTREAMSTS_FLAG_NONE); + AssertPtrReturn(pStream, PDMAUDIOSTREAMSTS_FLAG_NONE); + + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + + if (pStreamDS->fEnabled) + return (uint32_t)RTCircBufFree(pStreamDS->pCircBuf); + + return 0; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending} + */ +static DECLCALLBACK(uint32_t) drvHostDSoundStreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, 0); + + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT) + { + uint32_t cbPending = 0; + + /* Any uncommitted data left? */ + if (pStreamDS->pCircBuf) + cbPending = (uint32_t)RTCircBufUsed(pStreamDS->pCircBuf); + + /* Check if we have committed data which still needs to be played by + * by DirectSound's streaming buffer. */ + if (!cbPending) + { + const uint64_t diffLastTransferredMs = RTTimeMilliTS() - pStreamDS->Out.tsLastTransferredMs; + const uint64_t uLastTranserredChunkMs = DrvAudioHlpBytesToMilli(pStreamDS->Out.cbLastTransferred, &pStreamDS->Cfg.Props); + if ( uLastTranserredChunkMs + && diffLastTransferredMs < uLastTranserredChunkMs) + cbPending = 1; + + Log3Func(("diffLastTransferredMs=%RU64ms, uLastTranserredChunkMs=%RU64ms (%RU32 bytes) -> cbPending=%RU32\n", + diffLastTransferredMs, uLastTranserredChunkMs, pStreamDS->Out.cbLastTransferred, cbPending)); + } + else + Log3Func(("cbPending=%RU32\n", cbPending)); + + return cbPending; + } + /* Note: For input streams we never have pending data left. */ + + return 0; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} + */ +static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostDSoundStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, PDMAUDIOSTREAMSTS_FLAG_NONE); + + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + + PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED; + + if (pStreamDS->fEnabled) + strmSts |= PDMAUDIOSTREAMSTS_FLAG_ENABLED; + + return strmSts; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} + */ +static DECLCALLBACK(int) drvHostDSoundStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + { + return dsoundCaptureTransfer(pThis, pStreamDS); + } + else if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT) + { + return dsoundPlayTransfer(pThis, pStreamDS); + } + + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_AUDIO_CALLBACKS +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnSetCallback} + */ +static DECLCALLBACK(int) drvHostDSoundSetCallback(PPDMIHOSTAUDIO pInterface, PFNPDMHOSTAUDIOCALLBACK pfnCallback) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + /* pfnCallback will be handled below. */ + + PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + LogFunc(("pfnCallback=%p\n", pfnCallback)); + + if (pfnCallback) /* Register. */ + { + Assert(pThis->pfnCallback == NULL); + pThis->pfnCallback = pfnCallback; + +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + if (pThis->m_pNotificationClient) + pThis->m_pNotificationClient->RegisterCallback(pThis->pDrvIns, pfnCallback); +#endif + } + else /* Unregister. */ + { + if (pThis->pfnCallback) + pThis->pfnCallback = NULL; + +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + if (pThis->m_pNotificationClient) + pThis->m_pNotificationClient->UnregisterCallback(); +#endif + } + + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + } + + return rc; +} +#endif + + +/********************************************************************************************************************************* +* PDMDRVINS::IBase Interface * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostDSoundQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/********************************************************************************************************************************* +* PDMDRVREG Interface * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct} + */ +static DECLCALLBACK(void) drvHostDSoundDestruct(PPDMDRVINS pDrvIns) +{ + PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND); + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + + LogFlowFuncEnter(); + +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + if (pThis->m_pNotificationClient) + { + pThis->m_pNotificationClient->Dispose(); + pThis->m_pNotificationClient->Release(); + + pThis->m_pNotificationClient = NULL; + } +#endif + + if (pThis->pDrvIns) + CoUninitialize(); + + int rc2 = RTCritSectDelete(&pThis->CritSect); + AssertRC(rc2); + + LogFlowFuncLeave(); +} + +/** + * @callback_method_impl{FNPDMDRVCONSTRUCT, + * Construct a DirectSound Audio driver instance.} + */ +static DECLCALLBACK(int) drvHostDSoundConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(fFlags); + PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND); + + LogRel(("Audio: Initializing DirectSound audio driver\n")); + + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (FAILED(hr)) + { + DSLOGREL(("DSound: CoInitializeEx failed with %Rhrc\n", hr)); + return VERR_NOT_SUPPORTED; + } + + /* + * Init basic data members and interfaces. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostDSoundQueryInterface; + /* IHostAudio */ + PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostDSound); + pThis->IHostAudio.pfnStreamGetPending = drvHostDSoundStreamGetPending; + +#ifdef VBOX_WITH_AUDIO_CALLBACKS + /* This backend supports host audio callbacks. */ + pThis->IHostAudio.pfnSetCallback = drvHostDSoundSetCallback; + pThis->pfnCallback = NULL; +#endif + + /* + * Init the static parts. + */ + RTListInit(&pThis->lstDevInput); + RTListInit(&pThis->lstDevOutput); + + pThis->fEnabledIn = false; + pThis->fEnabledOut = false; + + int rc = VINF_SUCCESS; + +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + bool fUseNotificationClient = false; + + char szOSVersion[32]; + rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOSVersion, sizeof(szOSVersion)); + if (RT_SUCCESS(rc)) + { + /* IMMNotificationClient is available starting at Windows Vista. */ + if (RTStrVersionCompare(szOSVersion, "6.0") >= 0) + fUseNotificationClient = true; + } + + if (fUseNotificationClient) + { + try + { + pThis->m_pNotificationClient = new VBoxMMNotificationClient(); + + HRESULT hr = pThis->m_pNotificationClient->Initialize(); + if (FAILED(hr)) + rc = VERR_AUDIO_BACKEND_INIT_FAILED; + } + catch (std::bad_alloc &ex) + { + NOREF(ex); + rc = VERR_NO_MEMORY; + } + } + + LogRel2(("DSound: Notification client is %s\n", fUseNotificationClient ? "enabled" : "disabled")); +#endif + + if (RT_SUCCESS(rc)) + { + /* + * Initialize configuration values. + */ + rc = dsoundConfigInit(pThis, pCfg); + if (RT_SUCCESS(rc)) + rc = RTCritSectInit(&pThis->CritSect); + } + + return rc; +} + + +/** + * PDM driver registration. + */ +const PDMDRVREG g_DrvHostDSound = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "DSoundAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "DirectSound Audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTDSOUND), + /* pfnConstruct */ + drvHostDSoundConstruct, + /* pfnDestruct */ + drvHostDSoundDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff --git a/src/VBox/Devices/Audio/DrvHostDebugAudio.cpp b/src/VBox/Devices/Audio/DrvHostDebugAudio.cpp new file mode 100644 index 00000000..04af08d4 --- /dev/null +++ b/src/VBox/Devices/Audio/DrvHostDebugAudio.cpp @@ -0,0 +1,428 @@ +/* $Id: DrvHostDebugAudio.cpp $ */ +/** @file + * Debug audio driver. + * + * Host backend for dumping and injecting audio data from/to the device emulation. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <iprt/alloc.h> +#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */ + +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <VBox/log.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include "DrvAudio.h" +#include "VBoxDD.h" + + +/** + * Structure for keeping a debug input/output stream. + */ +typedef struct DEBUGAUDIOSTREAM +{ + /** The stream's acquired configuration. */ + PPDMAUDIOSTREAMCFG pCfg; + /** Audio file to dump output to or read input from. */ + PPDMAUDIOFILE pFile; + union + { + struct + { + /** Timestamp of last captured samples. */ + uint64_t tsLastCaptured; + } In; + }; +} DEBUGAUDIOSTREAM, *PDEBUGAUDIOSTREAM; + +/** + * Debug audio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHOSTDEBUGAUDIO +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; +} DRVHOSTDEBUGAUDIO, *PDRVHOSTDEBUGAUDIO; + +/*******************************************PDM_AUDIO_DRIVER******************************/ + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHostDebugAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Debug audio driver"); + + pBackendCfg->cbStreamOut = sizeof(DEBUGAUDIOSTREAM); + pBackendCfg->cbStreamIn = sizeof(DEBUGAUDIOSTREAM); + + pBackendCfg->cMaxStreamsOut = 1; /* Output; writing to a file. */ + pBackendCfg->cMaxStreamsIn = 0; /** @todo Right now we don't support any input (capturing, injecting from a file). */ + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} + */ +static DECLCALLBACK(int) drvHostDebugAudioInit(PPDMIHOSTAUDIO pInterface) +{ + RT_NOREF(pInterface); + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} + */ +static DECLCALLBACK(void) drvHostDebugAudioShutdown(PPDMIHOSTAUDIO pInterface) +{ + RT_NOREF(pInterface); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostDebugAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(enmDir); + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +static int debugCreateStreamIn(PDRVHOSTDEBUGAUDIO pDrv, PDEBUGAUDIOSTREAM pStreamDbg, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + RT_NOREF(pDrv, pStreamDbg, pCfgReq, pCfgAcq); + + return VINF_SUCCESS; +} + + +static int debugCreateStreamOut(PDRVHOSTDEBUGAUDIO pDrv, PDEBUGAUDIOSTREAM pStreamDbg, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + RT_NOREF(pDrv, pCfgAcq); + + char szTemp[RTPATH_MAX]; + int rc = RTPathTemp(szTemp, sizeof(szTemp)); + if (RT_SUCCESS(rc)) + { + char szFile[RTPATH_MAX]; + rc = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), szTemp, "DebugAudioOut", + pDrv->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE); + if (RT_SUCCESS(rc)) + { + rc = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE, &pStreamDbg->pFile); + if (RT_SUCCESS(rc)) + { + rc = DrvAudioHlpFileOpen(pStreamDbg->pFile, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE, + &pCfgReq->Props); + } + + if (RT_FAILURE(rc)) + LogRel(("DebugAudio: Creating output file '%s' failed with %Rrc\n", szFile, rc)); + } + else + LogRel(("DebugAudio: Unable to build file name for temp dir '%s': %Rrc\n", szTemp, rc)); + } + else + LogRel(("DebugAudio: Unable to retrieve temp dir: %Rrc\n", rc)); + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostDebugAudioStreamCreate(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + PDRVHOSTDEBUGAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTDEBUGAUDIO, IHostAudio); + PDEBUGAUDIOSTREAM pStreamDbg = (PDEBUGAUDIOSTREAM)pStream; + + int rc; + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + rc = debugCreateStreamIn( pDrv, pStreamDbg, pCfgReq, pCfgAcq); + else + rc = debugCreateStreamOut(pDrv, pStreamDbg, pCfgReq, pCfgAcq); + + if (RT_SUCCESS(rc)) + { + pStreamDbg->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); + if (!pStreamDbg->pCfg) + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHostDebugAudioStreamPlay(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf, + uint32_t *pcxWritten) +{ + RT_NOREF(pInterface); + PDEBUGAUDIOSTREAM pStreamDbg = (PDEBUGAUDIOSTREAM)pStream; + + int rc = DrvAudioHlpFileWrite(pStreamDbg->pFile, pvBuf, cxBuf, 0 /* fFlags */); + if (RT_FAILURE(rc)) + { + LogRel(("DebugAudio: Writing output failed with %Rrc\n", rc)); + return rc; + } + + if (pcxWritten) + *pcxWritten = cxBuf; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHostDebugAudioStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead) +{ + RT_NOREF(pInterface, pStream, pvBuf, cxBuf); + + /* Never capture anything. */ + if (pcxRead) + *pcxRead = 0; + + return VINF_SUCCESS; +} + + +static int debugDestroyStreamIn(PDRVHOSTDEBUGAUDIO pDrv, PDEBUGAUDIOSTREAM pStreamDbg) +{ + RT_NOREF(pDrv, pStreamDbg); + return VINF_SUCCESS; +} + + +static int debugDestroyStreamOut(PDRVHOSTDEBUGAUDIO pDrv, PDEBUGAUDIOSTREAM pStreamDbg) +{ + RT_NOREF(pDrv); + + DrvAudioHlpFileDestroy(pStreamDbg->pFile); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHostDebugAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + + PDRVHOSTDEBUGAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTDEBUGAUDIO, IHostAudio); + PDEBUGAUDIOSTREAM pStreamDbg = (PDEBUGAUDIOSTREAM)pStream; + + if (!pStreamDbg->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc; + if (pStreamDbg->pCfg->enmDir == PDMAUDIODIR_IN) + rc = debugDestroyStreamIn (pDrv, pStreamDbg); + else + rc = debugDestroyStreamOut(pDrv, pStreamDbg); + + if (RT_SUCCESS(rc)) + { + DrvAudioHlpStreamCfgFree(pStreamDbg->pCfg); + pStreamDbg->pCfg = NULL; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} + */ +static DECLCALLBACK(int) drvHostDebugAudioStreamControl(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + RT_NOREF(enmStreamCmd); + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostDebugAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return 0; /* Never capture anything. */ +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostDebugAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return UINT32_MAX; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostDebugAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return (PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} + */ +static DECLCALLBACK(int) drvHostDebugAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostDebugAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTDEBUGAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDEBUGAUDIO); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/** + * Constructs a Null audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvHostDebugAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHOSTDEBUGAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDEBUGAUDIO); + LogRel(("Audio: Initializing DEBUG driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostDebugAudioQueryInterface; + /* IHostAudio */ + PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostDebugAudio); + +#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA + RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "AudioDebugOutput.pcm"); +#endif + + return VINF_SUCCESS; +} + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvHostDebugAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "DebugAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Debug audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTDEBUGAUDIO), + /* pfnConstruct */ + drvHostDebugAudioConstruct, + /* pfnDestruct */ + NULL, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff --git a/src/VBox/Devices/Audio/DrvHostNullAudio.cpp b/src/VBox/Devices/Audio/DrvHostNullAudio.cpp new file mode 100644 index 00000000..aec4e022 --- /dev/null +++ b/src/VBox/Devices/Audio/DrvHostNullAudio.cpp @@ -0,0 +1,415 @@ +/* $Id: DrvHostNullAudio.cpp $ */ +/** @file + * NULL audio driver. + * + * This also acts as a fallback if no other backend is available. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * -------------------------------------------------------------------- + * + * This code is based on: noaudio.c QEMU based code. + * + * QEMU Timer based audio emulation + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/mem.h> +#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */ + +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <VBox/log.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include "DrvAudio.h" +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct NULLAUDIOSTREAM +{ + /** The stream's acquired configuration. */ + PPDMAUDIOSTREAMCFG pCfg; +} NULLAUDIOSTREAM, *PNULLAUDIOSTREAM; + +/** + * NULL audio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHOSTNULLAUDIO +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; +} DRVHOSTNULLAUDIO, *PDRVHOSTNULLAUDIO; + + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHostNullAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "NULL audio driver"); + + pBackendCfg->cbStreamOut = sizeof(NULLAUDIOSTREAM); + pBackendCfg->cbStreamIn = sizeof(NULLAUDIOSTREAM); + + pBackendCfg->cMaxStreamsOut = 1; /* Output */ + pBackendCfg->cMaxStreamsIn = 2; /* Line input + microphone input. */ + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} + */ +static DECLCALLBACK(int) drvHostNullAudioInit(PPDMIHOSTAUDIO pInterface) +{ + NOREF(pInterface); + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} + */ +static DECLCALLBACK(void) drvHostNullAudioShutdown(PPDMIHOSTAUDIO pInterface) +{ + RT_NOREF(pInterface); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostNullAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(enmDir); + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHostNullAudioStreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cxBuf, uint32_t *pcxWritten) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cxBuf, VERR_INVALID_PARAMETER); + + RT_NOREF(pInterface, pStream, pvBuf); + + /* Note: No copying of samples needed here, as this a NULL backend. */ + + if (pcxWritten) + *pcxWritten = cxBuf; /* Return all bytes as written. */ + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHostNullAudioStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead) +{ + RT_NOREF(pInterface, pStream); + + PNULLAUDIOSTREAM pStreamNull = (PNULLAUDIOSTREAM)pStream; + + /* Return silence. */ + Assert(pStreamNull->pCfg); + DrvAudioHlpClearBuf(&pStreamNull->pCfg->Props, pvBuf, cxBuf, PDMAUDIOPCMPROPS_B2F(&pStreamNull->pCfg->Props, cxBuf)); + + if (pcxRead) + *pcxRead = cxBuf; + + return VINF_SUCCESS; +} + + +static int nullCreateStreamIn(PNULLAUDIOSTREAM pStreamNull, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + RT_NOREF(pStreamNull, pCfgReq, pCfgAcq); + + return VINF_SUCCESS; +} + + +static int nullCreateStreamOut(PNULLAUDIOSTREAM pStreamNull, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + RT_NOREF(pStreamNull, pCfgReq, pCfgAcq); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostNullAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + PNULLAUDIOSTREAM pStreamNull = (PNULLAUDIOSTREAM)pStream; + + int rc; + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + rc = nullCreateStreamIn( pStreamNull, pCfgReq, pCfgAcq); + else + rc = nullCreateStreamOut(pStreamNull, pCfgReq, pCfgAcq); + + if (RT_SUCCESS(rc)) + { + pStreamNull->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); + if (!pStreamNull->pCfg) + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +static int nullDestroyStreamIn(void) +{ + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + + +static int nullDestroyStreamOut(PNULLAUDIOSTREAM pStreamNull) +{ + RT_NOREF(pStreamNull); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHostNullAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PNULLAUDIOSTREAM pStreamNull = (PNULLAUDIOSTREAM)pStream; + + if (!pStreamNull->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc; + if (pStreamNull->pCfg->enmDir == PDMAUDIODIR_IN) + rc = nullDestroyStreamIn(); + else + rc = nullDestroyStreamOut(pStreamNull); + + if (RT_SUCCESS(rc)) + { + DrvAudioHlpStreamCfgFree(pStreamNull->pCfg); + pStreamNull->pCfg = NULL; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} + */ +static DECLCALLBACK(int) drvHostNullAudioStreamControl(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + RT_NOREF(pInterface, pStream, enmStreamCmd); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostNullAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return UINT32_MAX; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostNullAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return UINT32_MAX; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} + */ +static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostNullAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} + */ +static DECLCALLBACK(int) drvHostNullAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + NOREF(pInterface); + NOREF(pStream); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostNullAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTNULLAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTNULLAUDIO); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/** + * Constructs a Null audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvHostNullAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER); + /* pCfg is optional. */ + + PDRVHOSTNULLAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTNULLAUDIO); + LogRel(("Audio: Initializing NULL driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostNullAudioQueryInterface; + /* IHostAudio */ + PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostNullAudio); + + return VINF_SUCCESS; +} + + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvHostNullAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "NullAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "NULL audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTNULLAUDIO), + /* pfnConstruct */ + drvHostNullAudioConstruct, + /* pfnDestruct */ + NULL, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff --git a/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp b/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp new file mode 100644 index 00000000..4b1d7905 --- /dev/null +++ b/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp @@ -0,0 +1,1169 @@ +/* $Id: DrvHostOSSAudio.cpp $ */ +/** @file + * OSS (Open Sound System) host audio backend. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <errno.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/soundcard.h> +#include <unistd.h> + +#include <iprt/alloc.h> +#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */ + +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <VBox/log.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include "DrvAudio.h" +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ + +#if ((SOUND_VERSION > 360) && (defined(OSS_SYSINFO))) +/* OSS > 3.6 has a new syscall available for querying a bit more detailed information + * about OSS' audio capabilities. This is handy for e.g. Solaris. */ +# define VBOX_WITH_AUDIO_OSS_SYSINFO 1 +#endif + +/** Makes DRVHOSTOSSAUDIO out of PDMIHOSTAUDIO. */ +#define PDMIHOSTAUDIO_2_DRVHOSTOSSAUDIO(pInterface) \ + ( (PDRVHOSTOSSAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTOSSAUDIO, IHostAudio)) ) + + +/********************************************************************************************************************************* +* Structures * +*********************************************************************************************************************************/ + +/** + * OSS host audio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHOSTOSSAUDIO +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Error count for not flooding the release log. + * UINT32_MAX for unlimited logging. */ + uint32_t cLogErrors; +} DRVHOSTOSSAUDIO, *PDRVHOSTOSSAUDIO; + +typedef struct OSSAUDIOSTREAMCFG +{ + PDMAUDIOPCMPROPS Props; + uint16_t cFragments; + uint32_t cbFragmentSize; +} OSSAUDIOSTREAMCFG, *POSSAUDIOSTREAMCFG; + +typedef struct OSSAUDIOSTREAM +{ + /** The stream's acquired configuration. */ + PPDMAUDIOSTREAMCFG pCfg; + /** Buffer alignment. */ + uint8_t uAlign; + union + { + struct + { + + } In; + struct + { +#ifndef RT_OS_L4 + /** Whether we use a memory mapped file instead of our + * own allocated PCM buffer below. */ + /** @todo The memory mapped code seems to be utterly broken. + * Needs investigation! */ + bool fMMIO; +#endif + } Out; + }; + int hFile; + int cFragments; + int cbFragmentSize; + /** Own PCM buffer. */ + void *pvBuf; + /** Size (in bytes) of own PCM buffer. */ + size_t cbBuf; + int old_optr; +} OSSAUDIOSTREAM, *POSSAUDIOSTREAM; + +typedef struct OSSAUDIOCFG +{ +#ifndef RT_OS_L4 + bool try_mmap; +#endif + int nfrags; + int fragsize; + const char *devpath_out; + const char *devpath_in; + int debug; +} OSSAUDIOCFG, *POSSAUDIOCFG; + +static OSSAUDIOCFG s_OSSConf = +{ +#ifndef RT_OS_L4 + false, +#endif + 4, + 4096, + "/dev/dsp", + "/dev/dsp", + 0 +}; + + +/* http://www.df.lth.se/~john_e/gems/gem002d.html */ +static uint32_t popcount(uint32_t u) +{ + u = ((u&0x55555555) + ((u>>1)&0x55555555)); + u = ((u&0x33333333) + ((u>>2)&0x33333333)); + u = ((u&0x0f0f0f0f) + ((u>>4)&0x0f0f0f0f)); + u = ((u&0x00ff00ff) + ((u>>8)&0x00ff00ff)); + u = ( u&0x0000ffff) + (u>>16); + return u; +} + + +static uint32_t lsbindex(uint32_t u) +{ + return popcount ((u&-u)-1); +} + + +static int ossOSSToAudioProps(int fmt, PPDMAUDIOPCMPROPS pProps) +{ + RT_BZERO(pProps, sizeof(PDMAUDIOPCMPROPS)); + + switch (fmt) + { + case AFMT_S8: + pProps->cBytes = 1; + pProps->fSigned = true; + break; + + case AFMT_U8: + pProps->cBytes = 1; + pProps->fSigned = false; + break; + + case AFMT_S16_LE: + pProps->cBytes = 2; + pProps->fSigned = true; + break; + + case AFMT_U16_LE: + pProps->cBytes = 2; + pProps->fSigned = false; + break; + + case AFMT_S16_BE: + pProps->cBytes = 2; + pProps->fSigned = true; +#ifdef RT_LITTLE_ENDIAN + pProps->fSwapEndian = true; +#endif + break; + + case AFMT_U16_BE: + pProps->cBytes = 2; + pProps->fSigned = false; +#ifdef RT_LITTLE_ENDIAN + pProps->fSwapEndian = true; +#endif + break; + + default: + AssertMsgFailed(("Format %ld not supported\n", fmt)); + return VERR_NOT_SUPPORTED; + } + + return VINF_SUCCESS; +} + + +static int ossStreamClose(int *phFile) +{ + if (!phFile || !*phFile || *phFile == -1) + return VINF_SUCCESS; + + int rc; + if (close(*phFile)) + { + LogRel(("OSS: Closing stream failed: %s\n", strerror(errno))); + rc = VERR_GENERAL_FAILURE; /** @todo */ + } + else + { + *phFile = -1; + rc = VINF_SUCCESS; + } + + return rc; +} + + +static int ossStreamOpen(const char *pszDev, int fOpen, POSSAUDIOSTREAMCFG pOSSReq, POSSAUDIOSTREAMCFG pOSSAcq, int *phFile) +{ + int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + + int hFile = -1; + do + { + hFile = open(pszDev, fOpen); + if (hFile == -1) + { + LogRel(("OSS: Failed to open %s: %s (%d)\n", pszDev, strerror(errno), errno)); + break; + } + + int iFormat; + switch (pOSSReq->Props.cBytes) + { + case 1: + iFormat = pOSSReq->Props.fSigned ? AFMT_S8 : AFMT_U8; + break; + + case 2: + iFormat = pOSSReq->Props.fSigned ? AFMT_S16_LE : AFMT_U16_LE; + break; + + default: + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + break; + } + + if (RT_FAILURE(rc)) + break; + + if (ioctl(hFile, SNDCTL_DSP_SAMPLESIZE, &iFormat)) + { + LogRel(("OSS: Failed to set audio format to %ld: %s (%d)\n", iFormat, strerror(errno), errno)); + break; + } + + int cChannels = pOSSReq->Props.cChannels; + if (ioctl(hFile, SNDCTL_DSP_CHANNELS, &cChannels)) + { + LogRel(("OSS: Failed to set number of audio channels (%RU8): %s (%d)\n", + pOSSReq->Props.cChannels, strerror(errno), errno)); + break; + } + + int freq = pOSSReq->Props.uHz; + if (ioctl(hFile, SNDCTL_DSP_SPEED, &freq)) + { + LogRel(("OSS: Failed to set audio frequency (%dHZ): %s (%d)\n", pOSSReq->Props.uHz, strerror(errno), errno)); + break; + } + + /* Obsolete on Solaris (using O_NONBLOCK is sufficient). */ +#if !(defined(VBOX) && defined(RT_OS_SOLARIS)) + if (ioctl(hFile, SNDCTL_DSP_NONBLOCK)) + { + LogRel(("OSS: Failed to set non-blocking mode: %s (%d)\n", strerror(errno), errno)); + break; + } +#endif + + /* Check access mode (input or output). */ + bool fIn = ((fOpen & O_ACCMODE) == O_RDONLY); + + LogRel2(("OSS: Requested %RU16 %s fragments, %RU32 bytes each\n", + pOSSReq->cFragments, fIn ? "input" : "output", pOSSReq->cbFragmentSize)); + + int mmmmssss = (pOSSReq->cFragments << 16) | lsbindex(pOSSReq->cbFragmentSize); + if (ioctl(hFile, SNDCTL_DSP_SETFRAGMENT, &mmmmssss)) + { + LogRel(("OSS: Failed to set %RU16 fragments to %RU32 bytes each: %s (%d)\n", + pOSSReq->cFragments, pOSSReq->cbFragmentSize, strerror(errno), errno)); + break; + } + + audio_buf_info abinfo; + if (ioctl(hFile, fIn ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &abinfo)) + { + LogRel(("OSS: Failed to retrieve %s buffer length: %s (%d)\n", fIn ? "input" : "output", strerror(errno), errno)); + break; + } + + rc = ossOSSToAudioProps(iFormat, &pOSSAcq->Props); + if (RT_SUCCESS(rc)) + { + pOSSAcq->Props.cChannels = cChannels; + pOSSAcq->Props.uHz = freq; + pOSSAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pOSSAcq->Props.cBytes, pOSSAcq->Props.cChannels); + + pOSSAcq->cFragments = abinfo.fragstotal; + pOSSAcq->cbFragmentSize = abinfo.fragsize; + + LogRel2(("OSS: Got %RU16 %s fragments, %RU32 bytes each\n", + pOSSAcq->cFragments, fIn ? "input" : "output", pOSSAcq->cbFragmentSize)); + + *phFile = hFile; + } + } + while (0); + + if (RT_FAILURE(rc)) + ossStreamClose(&hFile); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +static int ossControlStreamIn(/*PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd*/ void) +{ + /** @todo Nothing to do here right now!? */ + + return VINF_SUCCESS; +} + + +static int ossControlStreamOut(PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; + + int rc = VINF_SUCCESS; + + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + case PDMAUDIOSTREAMCMD_RESUME: + { + DrvAudioHlpClearBuf(&pStreamOSS->pCfg->Props, pStreamOSS->pvBuf, pStreamOSS->cbBuf, + PDMAUDIOPCMPROPS_B2F(&pStreamOSS->pCfg->Props, pStreamOSS->cbBuf)); + + int mask = PCM_ENABLE_OUTPUT; + if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0) + { + LogRel(("OSS: Failed to enable output stream: %s\n", strerror(errno))); + rc = RTErrConvertFromErrno(errno); + } + + break; + } + + case PDMAUDIOSTREAMCMD_DISABLE: + case PDMAUDIOSTREAMCMD_PAUSE: + { + int mask = 0; + if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0) + { + LogRel(("OSS: Failed to disable output stream: %s\n", strerror(errno))); + rc = RTErrConvertFromErrno(errno); + } + + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} + */ +static DECLCALLBACK(int) drvHostOSSAudioInit(PPDMIHOSTAUDIO pInterface) +{ + RT_NOREF(pInterface); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHostOSSAudioStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; + + int rc = VINF_SUCCESS; + + size_t cbToRead = RT_MIN(pStreamOSS->cbBuf, cxBuf); + + LogFlowFunc(("cbToRead=%zi\n", cbToRead)); + + uint32_t cbReadTotal = 0; + uint32_t cbTemp; + ssize_t cbRead; + size_t offWrite = 0; + + while (cbToRead) + { + cbTemp = RT_MIN(cbToRead, pStreamOSS->cbBuf); + AssertBreakStmt(cbTemp, rc = VERR_NO_DATA); + cbRead = read(pStreamOSS->hFile, (uint8_t *)pStreamOSS->pvBuf, cbTemp); + + LogFlowFunc(("cbRead=%zi, cbTemp=%RU32, cbToRead=%zu\n", cbRead, cbTemp, cbToRead)); + + if (cbRead < 0) + { + switch (errno) + { + case 0: + { + LogFunc(("Failed to read %z frames\n", cbRead)); + rc = VERR_ACCESS_DENIED; + break; + } + + case EINTR: + case EAGAIN: + rc = VERR_NO_DATA; + break; + + default: + LogFlowFunc(("Failed to read %zu input frames, rc=%Rrc\n", cbTemp, rc)); + rc = VERR_GENERAL_FAILURE; /** @todo Fix this. */ + break; + } + + if (RT_FAILURE(rc)) + break; + } + else if (cbRead) + { + memcpy((uint8_t *)pvBuf + offWrite, pStreamOSS->pvBuf, cbRead); + + Assert((ssize_t)cbToRead >= cbRead); + cbToRead -= cbRead; + offWrite += cbRead; + cbReadTotal += cbRead; + } + else /* No more data, try next round. */ + break; + } + + if (rc == VERR_NO_DATA) + rc = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + { + if (pcxRead) + *pcxRead = cbReadTotal; + } + + return rc; +} + + +static int ossDestroyStreamIn(PPDMAUDIOBACKENDSTREAM pStream) +{ + POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; + + LogFlowFuncEnter(); + + if (pStreamOSS->pvBuf) + { + Assert(pStreamOSS->cbBuf); + + RTMemFree(pStreamOSS->pvBuf); + pStreamOSS->pvBuf = NULL; + } + + pStreamOSS->cbBuf = 0; + + ossStreamClose(&pStreamOSS->hFile); + + return VINF_SUCCESS; +} + + +static int ossDestroyStreamOut(PPDMAUDIOBACKENDSTREAM pStream) +{ + POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; + +#ifndef RT_OS_L4 + if (pStreamOSS->Out.fMMIO) + { + if (pStreamOSS->pvBuf) + { + Assert(pStreamOSS->cbBuf); + + int rc2 = munmap(pStreamOSS->pvBuf, pStreamOSS->cbBuf); + if (rc2 == 0) + { + pStreamOSS->pvBuf = NULL; + pStreamOSS->cbBuf = 0; + + pStreamOSS->Out.fMMIO = false; + } + else + LogRel(("OSS: Failed to memory unmap playback buffer on close: %s\n", strerror(errno))); + } + } + else + { +#endif + if (pStreamOSS->pvBuf) + { + Assert(pStreamOSS->cbBuf); + + RTMemFree(pStreamOSS->pvBuf); + pStreamOSS->pvBuf = NULL; + } + + pStreamOSS->cbBuf = 0; +#ifndef RT_OS_L4 + } +#endif + + ossStreamClose(&pStreamOSS->hFile); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHostOSSAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + + RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "OSS audio driver"); + + pBackendCfg->cbStreamIn = sizeof(OSSAUDIOSTREAM); + pBackendCfg->cbStreamOut = sizeof(OSSAUDIOSTREAM); + + int hFile = open("/dev/dsp", O_WRONLY | O_NONBLOCK, 0); + if (hFile == -1) + { + /* Try opening the mixing device instead. */ + hFile = open("/dev/mixer", O_RDONLY | O_NONBLOCK, 0); + } + + int ossVer = -1; + +#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO + oss_sysinfo ossInfo; + RT_ZERO(ossInfo); +#endif + + if (hFile != -1) + { + int err = ioctl(hFile, OSS_GETVERSION, &ossVer); + if (err == 0) + { + LogRel2(("OSS: Using version: %d\n", ossVer)); +#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO + err = ioctl(hFile, OSS_SYSINFO, &ossInfo); + if (err == 0) + { + LogRel2(("OSS: Number of DSPs: %d\n", ossInfo.numaudios)); + LogRel2(("OSS: Number of mixers: %d\n", ossInfo.nummixers)); + + int cDev = ossInfo.nummixers; + if (!cDev) + cDev = ossInfo.numaudios; + + pBackendCfg->cMaxStreamsIn = UINT32_MAX; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; + } + else + { +#endif + /* Since we cannot query anything, assume that we have at least + * one input and one output if we found "/dev/dsp" or "/dev/mixer". */ + + pBackendCfg->cMaxStreamsIn = UINT32_MAX; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; +#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO + } +#endif + } + else + LogRel(("OSS: Unable to determine installed version: %s (%d)\n", strerror(err), err)); + } + else + LogRel(("OSS: No devices found, audio is not available\n")); + + if (hFile != -1) + close(hFile); + + return VINF_SUCCESS; +} + + +static int ossCreateStreamIn(POSSAUDIOSTREAM pStreamOSS, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + int rc; + + int hFile = -1; + + do + { + OSSAUDIOSTREAMCFG ossReq; + memcpy(&ossReq.Props, &pCfgReq->Props, sizeof(PDMAUDIOPCMPROPS)); + + ossReq.cFragments = s_OSSConf.nfrags; + ossReq.cbFragmentSize = s_OSSConf.fragsize; + + OSSAUDIOSTREAMCFG ossAcq; + RT_ZERO(ossAcq); + + rc = ossStreamOpen(s_OSSConf.devpath_in, O_RDONLY | O_NONBLOCK, &ossReq, &ossAcq, &hFile); + if (RT_SUCCESS(rc)) + { + memcpy(&pCfgAcq->Props, &ossAcq.Props, sizeof(PDMAUDIOPCMPROPS)); + + if (ossAcq.cFragments * ossAcq.cbFragmentSize & pStreamOSS->uAlign) + { + LogRel(("OSS: Warning: Misaligned capturing buffer: Size = %zu, Alignment = %u\n", + ossAcq.cFragments * ossAcq.cbFragmentSize, pStreamOSS->uAlign + 1)); + } + + if (RT_SUCCESS(rc)) + { + size_t cbBuf = PDMAUDIOSTREAMCFG_F2B(pCfgAcq, ossAcq.cFragments * ossAcq.cbFragmentSize); + void *pvBuf = RTMemAlloc(cbBuf); + if (!pvBuf) + { + LogRel(("OSS: Failed allocating capturing buffer with (%zu bytes)\n", cbBuf)); + rc = VERR_NO_MEMORY; + } + + pStreamOSS->hFile = hFile; + pStreamOSS->pvBuf = pvBuf; + pStreamOSS->cbBuf = cbBuf; + + pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, ossAcq.cbFragmentSize); + pCfgAcq->Backend.cfBufferSize = pCfgAcq->Backend.cfPeriod * 2; /* Use "double buffering". */ + /** @todo Pre-buffering required? */ + } + } + + } while (0); + + if (RT_FAILURE(rc)) + ossStreamClose(&hFile); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +static int ossCreateStreamOut(POSSAUDIOSTREAM pStreamOSS, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + int rc; + int hFile = -1; + + do + { + OSSAUDIOSTREAMCFG reqStream; + RT_ZERO(reqStream); + + OSSAUDIOSTREAMCFG obtStream; + RT_ZERO(obtStream); + + memcpy(&reqStream.Props, &pCfgReq->Props, sizeof(PDMAUDIOPCMPROPS)); + + reqStream.cFragments = s_OSSConf.nfrags; + reqStream.cbFragmentSize = s_OSSConf.fragsize; + + rc = ossStreamOpen(s_OSSConf.devpath_out, O_WRONLY, &reqStream, &obtStream, &hFile); + if (RT_SUCCESS(rc)) + { + memcpy(&pCfgAcq->Props, &obtStream.Props, sizeof(PDMAUDIOPCMPROPS)); + + if (obtStream.cFragments * obtStream.cbFragmentSize & pStreamOSS->uAlign) + { + LogRel(("OSS: Warning: Misaligned playback buffer: Size = %zu, Alignment = %u\n", + obtStream.cFragments * obtStream.cbFragmentSize, pStreamOSS->uAlign + 1)); + } + } + + if (RT_SUCCESS(rc)) + { + pStreamOSS->Out.fMMIO = false; + + size_t cbBuf = PDMAUDIOSTREAMCFG_F2B(pCfgAcq, obtStream.cFragments * obtStream.cbFragmentSize); + Assert(cbBuf); + +#ifndef RT_OS_L4 + if (s_OSSConf.try_mmap) + { + pStreamOSS->pvBuf = mmap(0, cbBuf, PROT_READ | PROT_WRITE, MAP_SHARED, hFile, 0); + if (pStreamOSS->pvBuf == MAP_FAILED) + { + LogRel(("OSS: Failed to memory map %zu bytes of playback buffer: %s\n", cbBuf, strerror(errno))); + rc = RTErrConvertFromErrno(errno); + break; + } + else + { + int mask = 0; + if (ioctl(hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0) + { + LogRel(("OSS: Failed to retrieve initial trigger mask for playback buffer: %s\n", strerror(errno))); + rc = RTErrConvertFromErrno(errno); + /* Note: No break here, need to unmap file first! */ + } + else + { + mask = PCM_ENABLE_OUTPUT; + if (ioctl (hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0) + { + LogRel(("OSS: Failed to retrieve PCM_ENABLE_OUTPUT mask: %s\n", strerror(errno))); + rc = RTErrConvertFromErrno(errno); + /* Note: No break here, need to unmap file first! */ + } + else + { + pStreamOSS->Out.fMMIO = true; + LogRel(("OSS: Using MMIO\n")); + } + } + + if (RT_FAILURE(rc)) + { + int rc2 = munmap(pStreamOSS->pvBuf, cbBuf); + if (rc2) + LogRel(("OSS: Failed to memory unmap playback buffer: %s\n", strerror(errno))); + break; + } + } + } +#endif /* !RT_OS_L4 */ + + /* Memory mapping failed above? Try allocating an own buffer. */ +#ifndef RT_OS_L4 + if (!pStreamOSS->Out.fMMIO) + { +#endif + void *pvBuf = RTMemAlloc(cbBuf); + if (!pvBuf) + { + LogRel(("OSS: Failed allocating playback buffer with %zu bytes\n", cbBuf)); + rc = VERR_NO_MEMORY; + break; + } + + pStreamOSS->hFile = hFile; + pStreamOSS->pvBuf = pvBuf; + pStreamOSS->cbBuf = cbBuf; +#ifndef RT_OS_L4 + } +#endif + pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, obtStream.cbFragmentSize); + pCfgAcq->Backend.cfBufferSize = pCfgAcq->Backend.cfPeriod * 2; /* Use "double buffering" */ + } + + } while (0); + + if (RT_FAILURE(rc)) + ossStreamClose(&hFile); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHostOSSAudioStreamPlay(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf, + uint32_t *pcxWritten) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; + + int rc = VINF_SUCCESS; + uint32_t cbWrittenTotal = 0; + +#ifndef RT_OS_L4 + count_info cntinfo; +#endif + + do + { + uint32_t cbAvail = cxBuf; + uint32_t cbToWrite; + +#ifndef RT_OS_L4 + if (pStreamOSS->Out.fMMIO) + { + /* Get current playback pointer. */ + int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOPTR, &cntinfo); + if (!rc2) + { + LogRel(("OSS: Failed to retrieve current playback pointer: %s\n", strerror(errno))); + rc = RTErrConvertFromErrno(errno); + break; + } + + /* Nothing to play? */ + if (cntinfo.ptr == pStreamOSS->old_optr) + break; + + int cbData; + if (cntinfo.ptr > pStreamOSS->old_optr) + cbData = cntinfo.ptr - pStreamOSS->old_optr; + else + cbData = cxBuf + cntinfo.ptr - pStreamOSS->old_optr; + Assert(cbData >= 0); + + cbToWrite = RT_MIN((unsigned)cbData, cbAvail); + } + else + { +#endif + audio_buf_info abinfo; + int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &abinfo); + if (rc2 < 0) + { + LogRel(("OSS: Failed to retrieve current playback buffer: %s\n", strerror(errno))); + rc = RTErrConvertFromErrno(errno); + break; + } + + if ((size_t)abinfo.bytes > cxBuf) + { + LogRel2(("OSS: Warning: Too big output size (%d > %RU32), limiting to %RU32\n", abinfo.bytes, cxBuf, cxBuf)); + abinfo.bytes = cxBuf; + /* Keep going. */ + } + + if (abinfo.bytes < 0) + { + LogRel2(("OSS: Warning: Invalid available size (%d vs. %RU32)\n", abinfo.bytes, cxBuf)); + rc = VERR_INVALID_PARAMETER; + break; + } + + cbToWrite = RT_MIN(unsigned(abinfo.fragments * abinfo.fragsize), cbAvail); +#ifndef RT_OS_L4 + } +#endif + cbToWrite = RT_MIN(cbToWrite, pStreamOSS->cbBuf); + + while (cbToWrite) + { + uint32_t cbWritten = cbToWrite; + + memcpy(pStreamOSS->pvBuf, pvBuf, cbWritten); + + uint32_t cbChunk = cbWritten; + uint32_t cbChunkOff = 0; + while (cbChunk) + { + ssize_t cbChunkWritten = write(pStreamOSS->hFile, (uint8_t *)pStreamOSS->pvBuf + cbChunkOff, + RT_MIN(cbChunk, (unsigned)s_OSSConf.fragsize)); + if (cbChunkWritten < 0) + { + LogRel(("OSS: Failed writing output data: %s\n", strerror(errno))); + rc = RTErrConvertFromErrno(errno); + break; + } + + if (cbChunkWritten & pStreamOSS->uAlign) + { + LogRel(("OSS: Misaligned write (written %z, expected %RU32)\n", cbChunkWritten, cbChunk)); + break; + } + + cbChunkOff += (uint32_t)cbChunkWritten; + Assert(cbChunkOff <= cbWritten); + Assert(cbChunk >= (uint32_t)cbChunkWritten); + cbChunk -= (uint32_t)cbChunkWritten; + } + + Assert(cbToWrite >= cbWritten); + cbToWrite -= cbWritten; + cbWrittenTotal += cbWritten; + } + +#ifndef RT_OS_L4 + /* Update read pointer. */ + if (pStreamOSS->Out.fMMIO) + pStreamOSS->old_optr = cntinfo.ptr; +#endif + + } while(0); + + if (RT_SUCCESS(rc)) + { + if (pcxWritten) + *pcxWritten = cbWrittenTotal; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} + */ +static DECLCALLBACK(void) drvHostOSSAudioShutdown(PPDMIHOSTAUDIO pInterface) +{ + RT_NOREF(pInterface); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostOSSAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + RT_NOREF(enmDir); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostOSSAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; + + int rc; + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + rc = ossCreateStreamIn (pStreamOSS, pCfgReq, pCfgAcq); + else + rc = ossCreateStreamOut(pStreamOSS, pCfgReq, pCfgAcq); + + if (RT_SUCCESS(rc)) + { + pStreamOSS->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); + if (!pStreamOSS->pCfg) + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHostOSSAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; + + if (!pStreamOSS->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc; + if (pStreamOSS->pCfg->enmDir == PDMAUDIODIR_IN) + rc = ossDestroyStreamIn(pStream); + else + rc = ossDestroyStreamOut(pStream); + + if (RT_SUCCESS(rc)) + { + DrvAudioHlpStreamCfgFree(pStreamOSS->pCfg); + pStreamOSS->pCfg = NULL; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} + */ +static DECLCALLBACK(int) drvHostOSSAudioStreamControl(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PDMAUDIOSTREAMCMD enmStreamCmd) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream; + + if (!pStreamOSS->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc; + if (pStreamOSS->pCfg->enmDir == PDMAUDIODIR_IN) + rc = ossControlStreamIn(/*pInterface, pStream, enmStreamCmd*/); + else + rc = ossControlStreamOut(pStreamOSS, enmStreamCmd); + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} + */ +static DECLCALLBACK(int) drvHostOSSAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + /* Nothing to do here for OSS. */ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostOSSAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return UINT32_MAX; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostOSSAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return UINT32_MAX; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} + */ +static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostOSSAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED + | PDMAUDIOSTREAMSTS_FLAG_ENABLED; + return strmSts; +} + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostOSSAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + + return NULL; +} + +/** + * Constructs an OSS audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvHostOSSAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO); + LogRel(("Audio: Initializing OSS driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostOSSAudioQueryInterface; + /* IHostAudio */ + PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostOSSAudio); + + return VINF_SUCCESS; +} + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvHostOSSAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "OSSAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "OSS audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTOSSAUDIO), + /* pfnConstruct */ + drvHostOSSAudioConstruct, + /* pfnDestruct */ + NULL, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff --git a/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp b/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp new file mode 100644 index 00000000..82de1ffb --- /dev/null +++ b/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp @@ -0,0 +1,1744 @@ +/* $Id: DrvHostPulseAudio.cpp $ */ +/** @file + * VBox audio devices: Pulse Audio audio driver. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <VBox/log.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include <stdio.h> + +#include <iprt/alloc.h> +#include <iprt/mem.h> +#include <iprt/uuid.h> + +RT_C_DECLS_BEGIN + #include "pulse_mangling.h" + #include "pulse_stubs.h" +RT_C_DECLS_END + +#include <pulse/pulseaudio.h> + +#include "DrvAudio.h" +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ +#define VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS 32 /** @todo Make this configurable thru driver options. */ + +#ifndef PA_STREAM_NOFLAGS +# define PA_STREAM_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */ +#endif + +#ifndef PA_CONTEXT_NOFLAGS +# define PA_CONTEXT_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */ +#endif + +/** No flags specified. */ +#define PULSEAUDIOENUMCBFLAGS_NONE 0 +/** (Release) log found devices. */ +#define PULSEAUDIOENUMCBFLAGS_LOG RT_BIT(0) + +/** Makes DRVHOSTPULSEAUDIO out of PDMIHOSTAUDIO. */ +#define PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface) \ + ( (PDRVHOSTPULSEAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTPULSEAUDIO, IHostAudio)) ) + + +/********************************************************************************************************************************* +* Structures * +*********************************************************************************************************************************/ + +/** + * Host Pulse audio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHOSTPULSEAUDIO +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to PulseAudio's threaded main loop. */ + pa_threaded_mainloop *pMainLoop; + /** + * Pointer to our PulseAudio context. + * Note: We use a pMainLoop in a separate thread (pContext). + * So either use callback functions or protect these functions + * by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock(). + */ + pa_context *pContext; + /** Shutdown indicator. */ + volatile bool fAbortLoop; + /** Enumeration operation successful? */ + volatile bool fEnumOpSuccess; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Error count for not flooding the release log. + * Specify UINT32_MAX for unlimited logging. */ + uint32_t cLogErrors; + /** The stream (base) name; needed for distinguishing + * streams in the PulseAudio mixer controls if multiple + * VMs are running at the same time. */ + char szStreamName[64]; +} DRVHOSTPULSEAUDIO, *PDRVHOSTPULSEAUDIO; + +typedef struct PULSEAUDIOSTREAM +{ + /** The stream's acquired configuration. */ + PPDMAUDIOSTREAMCFG pCfg; + /** Pointer to driver instance. */ + PDRVHOSTPULSEAUDIO pDrv; + /** Pointer to opaque PulseAudio stream. */ + pa_stream *pStream; + /** Pulse sample format and attribute specification. */ + pa_sample_spec SampleSpec; + /** Pulse playback and buffer metrics. */ + pa_buffer_attr BufAttr; + int fOpSuccess; + /** Pointer to Pulse sample peeking buffer. */ + const uint8_t *pu8PeekBuf; + /** Current size (in bytes) of peeking data in + * buffer. */ + size_t cbPeekBuf; + /** Our offset (in bytes) in peeking buffer. */ + size_t offPeekBuf; + pa_operation *pDrainOp; + /** Number of occurred audio data underflows. */ + uint32_t cUnderflows; + /** Current latency (in us). */ + uint64_t curLatencyUs; +#ifdef LOG_ENABLED + /** Start time stamp (in us) of stream playback / recording. */ + pa_usec_t tsStartUs; + /** Time stamp (in us) when last read from / written to the stream. */ + pa_usec_t tsLastReadWrittenUs; +#endif +} PULSEAUDIOSTREAM, *PPULSEAUDIOSTREAM; + +/** + * Callback context for server enumeration callbacks. + */ +typedef struct PULSEAUDIOENUMCBCTX +{ + /** Pointer to host backend driver. */ + PDRVHOSTPULSEAUDIO pDrv; + /** Enumeration flags. */ + uint32_t fFlags; + /** Number of found input devices. */ + uint8_t cDevIn; + /** Number of found output devices. */ + uint8_t cDevOut; + /** Name of default sink being used. Must be free'd using RTStrFree(). */ + char *pszDefaultSink; + /** Name of default source being used. Must be free'd using RTStrFree(). */ + char *pszDefaultSource; +} PULSEAUDIOENUMCBCTX, *PPULSEAUDIOENUMCBCTX; + +#ifndef PA_CONTEXT_IS_GOOD /* To allow running on systems with PulseAudio < 0.9.11. */ +static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) { + return + x == PA_CONTEXT_CONNECTING || + x == PA_CONTEXT_AUTHORIZING || + x == PA_CONTEXT_SETTING_NAME || + x == PA_CONTEXT_READY; +} +#endif /* !PA_CONTEXT_IS_GOOD */ + +#ifndef PA_STREAM_IS_GOOD /* To allow running on systems with PulseAudio < 0.9.11. */ +static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) { + return + x == PA_STREAM_CREATING || + x == PA_STREAM_READY; +} +#endif /* !PA_STREAM_IS_GOOD */ + + +/********************************************************************************************************************************* +* Prototypes * +*********************************************************************************************************************************/ + +static int paEnumerate(PDRVHOSTPULSEAUDIO pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum); +static int paError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg); +#ifdef DEBUG +static void paStreamCbUnderflow(pa_stream *pStream, void *pvContext); +static void paStreamCbReqWrite(pa_stream *pStream, size_t cbLen, void *pvContext); +#endif +static void paStreamCbSuccess(pa_stream *pStream, int fSuccess, void *pvContext); + + +/** + * Signal the main loop to abort. Just signalling isn't sufficient as the + * mainloop might not have been entered yet. + */ +static void paSignalWaiter(PDRVHOSTPULSEAUDIO pThis) +{ + if (!pThis) + return; + + pThis->fAbortLoop = true; + pa_threaded_mainloop_signal(pThis->pMainLoop, 0); +} + + +static pa_sample_format_t paAudioPropsToPulse(PPDMAUDIOPCMPROPS pProps) +{ + switch (pProps->cBytes) + { + case 1: + if (!pProps->fSigned) + return PA_SAMPLE_U8; + break; + + case 2: + if (pProps->fSigned) + return PA_SAMPLE_S16LE; + break; + +#ifdef PA_SAMPLE_S32LE + case 4: + if (pProps->fSigned) + return PA_SAMPLE_S32LE; + break; +#endif + + default: + break; + } + + AssertMsgFailed(("%RU8%s not supported\n", pProps->cBytes, pProps->fSigned ? "S" : "U")); + return PA_SAMPLE_INVALID; +} + + +static int paPulseToAudioProps(pa_sample_format_t pulsefmt, PPDMAUDIOPCMPROPS pProps) +{ + switch (pulsefmt) + { + case PA_SAMPLE_U8: + pProps->cBytes = 1; + pProps->fSigned = false; + break; + + case PA_SAMPLE_S16LE: + pProps->cBytes = 2; + pProps->fSigned = true; + break; + + case PA_SAMPLE_S16BE: + pProps->cBytes = 2; + pProps->fSigned = true; + /** @todo Handle Endianess. */ + break; + +#ifdef PA_SAMPLE_S32LE + case PA_SAMPLE_S32LE: + pProps->cBytes = 4; + pProps->fSigned = true; + break; +#endif + +#ifdef PA_SAMPLE_S32BE + case PA_SAMPLE_S32BE: + pProps->cBytes = 4; + pProps->fSigned = true; + /** @todo Handle Endianess. */ + break; +#endif + + default: + AssertLogRelMsgFailed(("PulseAudio: Format (%ld) not supported\n", pulsefmt)); + return VERR_NOT_SUPPORTED; + } + + return VINF_SUCCESS; +} + + +/** + * Synchronously wait until an operation completed. + */ +static int paWaitForEx(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP, RTMSINTERVAL cMsTimeout) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pOP, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + uint64_t u64StartMs = RTTimeMilliTS(); + while (pa_operation_get_state(pOP) == PA_OPERATION_RUNNING) + { + if (!pThis->fAbortLoop) + { + AssertPtr(pThis->pMainLoop); + pa_threaded_mainloop_wait(pThis->pMainLoop); + if ( !pThis->pContext + || pa_context_get_state(pThis->pContext) != PA_CONTEXT_READY) + { + LogRel(("PulseAudio: pa_context_get_state context not ready\n")); + break; + } + } + pThis->fAbortLoop = false; + + uint64_t u64ElapsedMs = RTTimeMilliTS() - u64StartMs; + if (u64ElapsedMs >= cMsTimeout) + { + rc = VERR_TIMEOUT; + break; + } + } + + pa_operation_unref(pOP); + + return rc; +} + + +static int paWaitFor(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP) +{ + return paWaitForEx(pThis, pOP, 10 * 1000 /* 10s timeout */); +} + + +/** + * Context status changed. + */ +static void paContextCbStateChanged(pa_context *pCtx, void *pvUser) +{ + AssertPtrReturnVoid(pCtx); + + PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser; + AssertPtrReturnVoid(pThis); + + switch (pa_context_get_state(pCtx)) + { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + paSignalWaiter(pThis); + break; + + default: + break; + } +} + + +/** + * Callback called when our pa_stream_drain operation was completed. + */ +static void paStreamCbDrain(pa_stream *pStream, int fSuccess, void *pvUser) +{ + AssertPtrReturnVoid(pStream); + + PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pvUser; + AssertPtrReturnVoid(pStreamPA); + + pStreamPA->fOpSuccess = fSuccess; + if (fSuccess) + { + pa_operation_unref(pa_stream_cork(pStream, 1, + paStreamCbSuccess, pvUser)); + } + else + paError(pStreamPA->pDrv, "Failed to drain stream"); + + if (pStreamPA->pDrainOp) + { + pa_operation_unref(pStreamPA->pDrainOp); + pStreamPA->pDrainOp = NULL; + } +} + + +/** + * Stream status changed. + */ +static void paStreamCbStateChanged(pa_stream *pStream, void *pvUser) +{ + AssertPtrReturnVoid(pStream); + + PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser; + AssertPtrReturnVoid(pThis); + + switch (pa_stream_get_state(pStream)) + { + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + paSignalWaiter(pThis); + break; + + default: + break; + } +} + + +#ifdef DEBUG +static void paStreamCbReqWrite(pa_stream *pStream, size_t cbLen, void *pvContext) +{ + RT_NOREF(cbLen, pvContext); + + PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext; + AssertPtrReturnVoid(pStrm); + + pa_usec_t usec = 0; + int neg = 0; + pa_stream_get_latency(pStream, &usec, &neg); + + Log2Func(("Requested %zu bytes -- Current latency is %RU64ms\n", cbLen, usec / 1000)); +} + + +static void paStreamCbUnderflow(pa_stream *pStream, void *pvContext) +{ + PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext; + AssertPtrReturnVoid(pStrm); + + pStrm->cUnderflows++; + + LogRel2(("PulseAudio: Warning: Hit underflow #%RU32\n", pStrm->cUnderflows)); + + if ( pStrm->cUnderflows >= 6 /** @todo Make this check configurable. */ + && pStrm->curLatencyUs < 2000000 /* 2s */) + { + pStrm->curLatencyUs = (pStrm->curLatencyUs * 3) / 2; + + LogRel2(("PulseAudio: Output latency increased to %RU64ms\n", pStrm->curLatencyUs / 1000 /* ms */)); + + pStrm->BufAttr.maxlength = pa_usec_to_bytes(pStrm->curLatencyUs, &pStrm->SampleSpec); + pStrm->BufAttr.tlength = pa_usec_to_bytes(pStrm->curLatencyUs, &pStrm->SampleSpec); + + pa_stream_set_buffer_attr(pStream, &pStrm->BufAttr, NULL, NULL); + + pStrm->cUnderflows = 0; + } + + pa_usec_t curLatencyUs = 0; + pa_stream_get_latency(pStream, &curLatencyUs, NULL /* Neg */); + + LogRel2(("PulseAudio: Latency now is %RU64ms\n", curLatencyUs / 1000 /* ms */)); + +# ifdef LOG_ENABLED + const pa_timing_info *pTInfo = pa_stream_get_timing_info(pStream); + const pa_sample_spec *pSpec = pa_stream_get_sample_spec(pStream); + + pa_usec_t curPosWritesUs = pa_bytes_to_usec(pTInfo->write_index, pSpec); + pa_usec_t curPosReadsUs = pa_bytes_to_usec(pTInfo->read_index, pSpec); + pa_usec_t curTsUs = pa_rtclock_now() - pStrm->tsStartUs; + + Log2Func(("curPosWrite=%RU64ms, curPosRead=%RU64ms, curTs=%RU64ms, curLatency=%RU64ms (%RU32Hz, %RU8 channels)\n", + curPosWritesUs / RT_US_1MS_64, curPosReadsUs / RT_US_1MS_64, + curTsUs / RT_US_1MS_64, curLatencyUs / RT_US_1MS_64, pSpec->rate, pSpec->channels)); +# endif +} + + +static void paStreamCbOverflow(pa_stream *pStream, void *pvContext) +{ + RT_NOREF(pStream, pvContext); + + Log2Func(("Warning: Hit overflow\n")); +} +#endif /* DEBUG */ + + +static void paStreamCbSuccess(pa_stream *pStream, int fSuccess, void *pvUser) +{ + AssertPtrReturnVoid(pStream); + + PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvUser; + AssertPtrReturnVoid(pStrm); + + pStrm->fOpSuccess = fSuccess; + + if (fSuccess) + paSignalWaiter(pStrm->pDrv); + else + paError(pStrm->pDrv, "Failed to finish stream operation"); +} + + +static int paStreamOpen(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, bool fIn, const char *pszName) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + + int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + + pa_stream *pStream = NULL; + uint32_t flags = PA_STREAM_NOFLAGS; + + pa_threaded_mainloop_lock(pThis->pMainLoop); + + do + { + pa_sample_spec *pSampleSpec = &pStreamPA->SampleSpec; + + LogFunc(("Opening '%s', rate=%dHz, channels=%d, format=%s\n", + pszName, pSampleSpec->rate, pSampleSpec->channels, + pa_sample_format_to_string(pSampleSpec->format))); + + if (!pa_sample_spec_valid(pSampleSpec)) + { + LogRel(("PulseAudio: Unsupported sample specification for stream '%s'\n", pszName)); + break; + } + + pa_buffer_attr *pBufAttr = &pStreamPA->BufAttr; + + /** @todo r=andy Use pa_stream_new_with_proplist instead. */ + if (!(pStream = pa_stream_new(pThis->pContext, pszName, pSampleSpec, NULL /* pa_channel_map */))) + { + LogRel(("PulseAudio: Could not create stream '%s'\n", pszName)); + rc = VERR_NO_MEMORY; + break; + } + +#ifdef DEBUG + pa_stream_set_write_callback (pStream, paStreamCbReqWrite, pStreamPA); + pa_stream_set_underflow_callback (pStream, paStreamCbUnderflow, pStreamPA); + if (!fIn) /* Only for output streams. */ + pa_stream_set_overflow_callback(pStream, paStreamCbOverflow, pStreamPA); +#endif + pa_stream_set_state_callback (pStream, paStreamCbStateChanged, pThis); + +#if PA_API_VERSION >= 12 + /* XXX */ + flags |= PA_STREAM_ADJUST_LATENCY; +#endif + /* For using pa_stream_get_latency() and pa_stream_get_time(). */ + flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE; + + /* No input/output right away after the stream was started. */ + flags |= PA_STREAM_START_CORKED; + + if (fIn) + { + LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n", + pBufAttr->maxlength, pBufAttr->fragsize)); + + if (pa_stream_connect_record(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags) < 0) + { + LogRel(("PulseAudio: Could not connect input stream '%s': %s\n", + pszName, pa_strerror(pa_context_errno(pThis->pContext)))); + break; + } + } + else + { + LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n", + pBufAttr->maxlength, pBufAttr->tlength, pBufAttr->prebuf, pBufAttr->minreq)); + + if (pa_stream_connect_playback(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags, + /*cvolume=*/NULL, /*sync_stream=*/NULL) < 0) + { + LogRel(("PulseAudio: Could not connect playback stream '%s': %s\n", + pszName, pa_strerror(pa_context_errno(pThis->pContext)))); + break; + } + } + + /* Wait until the stream is ready. */ + for (;;) + { + if (!pThis->fAbortLoop) + pa_threaded_mainloop_wait(pThis->pMainLoop); + pThis->fAbortLoop = false; + + pa_stream_state_t streamSt = pa_stream_get_state(pStream); + if (streamSt == PA_STREAM_READY) + break; + else if ( streamSt == PA_STREAM_FAILED + || streamSt == PA_STREAM_TERMINATED) + { + LogRel(("PulseAudio: Failed to initialize stream '%s' (state %ld)\n", pszName, streamSt)); + break; + } + } + +#ifdef LOG_ENABLED + pStreamPA->tsStartUs = pa_rtclock_now(); +#endif + const pa_buffer_attr *pBufAttrObtained = pa_stream_get_buffer_attr(pStream); + AssertPtr(pBufAttrObtained); + memcpy(pBufAttr, pBufAttrObtained, sizeof(pa_buffer_attr)); + + LogFunc(("Obtained %s buffer attributes: tLength=%RU32, maxLength=%RU32, minReq=%RU32, fragSize=%RU32, preBuf=%RU32\n", + fIn ? "capture" : "playback", + pBufAttr->tlength, pBufAttr->maxlength, pBufAttr->minreq, pBufAttr->fragsize, pBufAttr->prebuf)); + + pStreamPA->pStream = pStream; + + rc = VINF_SUCCESS; + + } while (0); + + if ( RT_FAILURE(rc) + && pStream) + pa_stream_disconnect(pStream); + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + if (RT_FAILURE(rc)) + { + if (pStream) + pa_stream_unref(pStream); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} + */ +static DECLCALLBACK(int) drvHostPulseAudioInit(PPDMIHOSTAUDIO pInterface) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + + PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); + + LogFlowFuncEnter(); + + int rc = audioLoadPulseLib(); + if (RT_FAILURE(rc)) + { + LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc)); + return rc; + } + + LogRel(("PulseAudio: Using v%s\n", pa_get_library_version())); + + pThis->fAbortLoop = false; + pThis->pMainLoop = NULL; + + bool fLocked = false; + + do + { + if (!(pThis->pMainLoop = pa_threaded_mainloop_new())) + { + LogRel(("PulseAudio: Failed to allocate main loop: %s\n", + pa_strerror(pa_context_errno(pThis->pContext)))); + rc = VERR_NO_MEMORY; + break; + } + + if (!(pThis->pContext = pa_context_new(pa_threaded_mainloop_get_api(pThis->pMainLoop), "VirtualBox"))) + { + LogRel(("PulseAudio: Failed to allocate context: %s\n", + pa_strerror(pa_context_errno(pThis->pContext)))); + rc = VERR_NO_MEMORY; + break; + } + + if (pa_threaded_mainloop_start(pThis->pMainLoop) < 0) + { + LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n", + pa_strerror(pa_context_errno(pThis->pContext)))); + break; + } + + /* Install a global callback to known if something happens to our acquired context. */ + pa_context_set_state_callback(pThis->pContext, paContextCbStateChanged, pThis /* pvUserData */); + + pa_threaded_mainloop_lock(pThis->pMainLoop); + fLocked = true; + + if (pa_context_connect(pThis->pContext, NULL /* pszServer */, + PA_CONTEXT_NOFLAGS, NULL) < 0) + { + LogRel(("PulseAudio: Failed to connect to server: %s\n", + pa_strerror(pa_context_errno(pThis->pContext)))); + break; + } + + /* Wait until the pThis->pContext is ready. */ + for (;;) + { + if (!pThis->fAbortLoop) + pa_threaded_mainloop_wait(pThis->pMainLoop); + pThis->fAbortLoop = false; + + pa_context_state_t cstate = pa_context_get_state(pThis->pContext); + if (cstate == PA_CONTEXT_READY) + break; + else if ( cstate == PA_CONTEXT_TERMINATED + || cstate == PA_CONTEXT_FAILED) + { + LogRel(("PulseAudio: Failed to initialize context (state %d)\n", cstate)); + break; + } + } + } + while (0); + + if (fLocked) + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + if (RT_FAILURE(rc)) + { + if (pThis->pMainLoop) + pa_threaded_mainloop_stop(pThis->pMainLoop); + + if (pThis->pContext) + { + pa_context_disconnect(pThis->pContext); + pa_context_unref(pThis->pContext); + pThis->pContext = NULL; + } + + if (pThis->pMainLoop) + { + pa_threaded_mainloop_free(pThis->pMainLoop); + pThis->pMainLoop = NULL; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +static int paCreateStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + pStreamPA->pDrainOp = NULL; + + pStreamPA->SampleSpec.format = paAudioPropsToPulse(&pCfgReq->Props); + pStreamPA->SampleSpec.rate = pCfgReq->Props.uHz; + pStreamPA->SampleSpec.channels = pCfgReq->Props.cChannels; + + pStreamPA->curLatencyUs = DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props) * RT_US_1MS; + + const uint32_t cbLatency = pa_usec_to_bytes(pStreamPA->curLatencyUs, &pStreamPA->SampleSpec); + + LogRel2(("PulseAudio: Initial output latency is %RU64ms (%RU32 bytes)\n", pStreamPA->curLatencyUs / RT_US_1MS, cbLatency)); + + pStreamPA->BufAttr.tlength = cbLatency; + pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */ + pStreamPA->BufAttr.prebuf = cbLatency; + pStreamPA->BufAttr.minreq = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfPeriod, &pCfgReq->Props); + + LogFunc(("Requested: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n", + pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq)); + + Assert(pCfgReq->enmDir == PDMAUDIODIR_OUT); + + char szName[256]; + RTStrPrintf2(szName, sizeof(szName), "VirtualBox %s [%s]", + DrvAudioHlpPlaybackDstToStr(pCfgReq->DestSource.Dest), pThis->szStreamName); + + /* Note that the struct BufAttr is updated to the obtained values after this call! */ + int rc = paStreamOpen(pThis, pStreamPA, false /* fIn */, szName); + if (RT_FAILURE(rc)) + return rc; + + rc = paPulseToAudioProps(pStreamPA->SampleSpec.format, &pCfgAcq->Props); + if (RT_FAILURE(rc)) + { + LogRel(("PulseAudio: Cannot find audio output format %ld\n", pStreamPA->SampleSpec.format)); + return rc; + } + + pCfgAcq->Props.uHz = pStreamPA->SampleSpec.rate; + pCfgAcq->Props.cChannels = pStreamPA->SampleSpec.channels; + pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cBytes, pCfgAcq->Props.cChannels); + + LogFunc(("Acquired: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n", + pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq)); + + pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.minreq); + pCfgAcq->Backend.cfBufferSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.tlength); + pCfgAcq->Backend.cfPreBuf = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.prebuf); + + pStreamPA->pDrv = pThis; + + return rc; +} + + +static int paCreateStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + pStreamPA->SampleSpec.format = paAudioPropsToPulse(&pCfgReq->Props); + pStreamPA->SampleSpec.rate = pCfgReq->Props.uHz; + pStreamPA->SampleSpec.channels = pCfgReq->Props.cChannels; + + pStreamPA->BufAttr.fragsize = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfPeriod, &pCfgReq->Props); + pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */ + + Assert(pCfgReq->enmDir == PDMAUDIODIR_IN); + + char szName[256]; + RTStrPrintf2(szName, sizeof(szName), "VirtualBox %s [%s]", + DrvAudioHlpRecSrcToStr(pCfgReq->DestSource.Source), pThis->szStreamName); + + /* Note: Other members of BufAttr are ignored for record streams. */ + int rc = paStreamOpen(pThis, pStreamPA, true /* fIn */, szName); + if (RT_FAILURE(rc)) + return rc; + + rc = paPulseToAudioProps(pStreamPA->SampleSpec.format, &pCfgAcq->Props); + if (RT_FAILURE(rc)) + { + LogRel(("PulseAudio: Cannot find audio capture format %ld\n", pStreamPA->SampleSpec.format)); + return rc; + } + + pStreamPA->pDrv = pThis; + pStreamPA->pu8PeekBuf = NULL; + + pCfgAcq->Props.uHz = pStreamPA->SampleSpec.rate; + pCfgAcq->Props.cChannels = pStreamPA->SampleSpec.channels; + + pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.fragsize); + pCfgAcq->Backend.cfBufferSize = pCfgAcq->Backend.cfBufferSize; + pCfgAcq->Backend.cfPreBuf = pCfgAcq->Backend.cfPeriod; + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHostPulseAudioStreamCapture(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead) +{ + RT_NOREF(pvBuf, cxBuf); + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cxBuf, VERR_INVALID_PARAMETER); + /* pcbRead is optional. */ + + PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); + PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream; + + /* We should only call pa_stream_readable_size() once and trust the first value. */ + pa_threaded_mainloop_lock(pThis->pMainLoop); + size_t cbAvail = pa_stream_readable_size(pStreamPA->pStream); + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + if (cbAvail == (size_t)-1) + return paError(pStreamPA->pDrv, "Failed to determine input data size"); + + /* If the buffer was not dropped last call, add what remains. */ + if (pStreamPA->pu8PeekBuf) + { + Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf); + cbAvail += (pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf); + } + + Log3Func(("cbAvail=%zu\n", cbAvail)); + + if (!cbAvail) /* No data? Bail out. */ + { + if (pcxRead) + *pcxRead = 0; + return VINF_SUCCESS; + } + + int rc = VINF_SUCCESS; + + size_t cbToRead = RT_MIN(cbAvail, cxBuf); + + Log3Func(("cbToRead=%zu, cbAvail=%zu, offPeekBuf=%zu, cbPeekBuf=%zu\n", + cbToRead, cbAvail, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf)); + + uint32_t cbReadTotal = 0; + + while (cbToRead) + { + /* If there is no data, do another peek. */ + if (!pStreamPA->pu8PeekBuf) + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + pa_stream_peek(pStreamPA->pStream, + (const void**)&pStreamPA->pu8PeekBuf, &pStreamPA->cbPeekBuf); + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + pStreamPA->offPeekBuf = 0; + + /* No data anymore? + * Note: If there's a data hole (cbPeekBuf then contains the length of the hole) + * we need to drop the stream lateron. */ + if ( !pStreamPA->pu8PeekBuf + && !pStreamPA->cbPeekBuf) + { + break; + } + } + + Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf); + size_t cbToWrite = RT_MIN(pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf, cbToRead); + + Log3Func(("cbToRead=%zu, cbToWrite=%zu, offPeekBuf=%zu, cbPeekBuf=%zu, pu8PeekBuf=%p\n", + cbToRead, cbToWrite, + pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->pu8PeekBuf)); + + if ( cbToWrite + /* Only copy data if it's not a data hole (see above). */ + && pStreamPA->pu8PeekBuf + && pStreamPA->cbPeekBuf) + { + memcpy((uint8_t *)pvBuf + cbReadTotal, pStreamPA->pu8PeekBuf + pStreamPA->offPeekBuf, cbToWrite); + + Assert(cbToRead >= cbToWrite); + cbToRead -= cbToWrite; + cbReadTotal += cbToWrite; + + pStreamPA->offPeekBuf += cbToWrite; + Assert(pStreamPA->offPeekBuf <= pStreamPA->cbPeekBuf); + } + + if (/* Nothing to write anymore? Drop the buffer. */ + !cbToWrite + /* Was there a hole in the peeking buffer? Drop it. */ + || !pStreamPA->pu8PeekBuf + /* If the buffer is done, drop it. */ + || pStreamPA->offPeekBuf == pStreamPA->cbPeekBuf) + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + pa_stream_drop(pStreamPA->pStream); + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + pStreamPA->pu8PeekBuf = NULL; + } + } + + if (RT_SUCCESS(rc)) + { + if (pcxRead) + *pcxRead = cbReadTotal; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHostPulseAudioStreamPlay(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf, + uint32_t *pcxWritten) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cxBuf, VERR_INVALID_PARAMETER); + /* pcxWritten is optional. */ + + PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); + PPULSEAUDIOSTREAM pPAStream = (PPULSEAUDIOSTREAM)pStream; + + int rc = VINF_SUCCESS; + + uint32_t cbWrittenTotal = 0; + + pa_threaded_mainloop_lock(pThis->pMainLoop); + +#ifdef LOG_ENABLED + const pa_usec_t tsNowUs = pa_rtclock_now(); + const pa_usec_t tsDeltaPlayedUs = tsNowUs - pPAStream->tsLastReadWrittenUs; + + Log3Func(("tsDeltaPlayedMs=%RU64\n", tsDeltaPlayedUs / 1000 /* ms */)); + + pPAStream->tsLastReadWrittenUs = tsNowUs; +#endif + + do + { + size_t cbWriteable = pa_stream_writable_size(pPAStream->pStream); + if (cbWriteable == (size_t)-1) + { + rc = paError(pPAStream->pDrv, "Failed to determine output data size"); + break; + } + + size_t cbLeft = RT_MIN(cbWriteable, cxBuf); + Assert(cbLeft); /* At this point we better have *something* to write. */ + + while (cbLeft) + { + uint32_t cbChunk = cbLeft; /* Write all at once for now. */ + + if (pa_stream_write(pPAStream->pStream, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk, NULL /* Cleanup callback */, + 0, PA_SEEK_RELATIVE) < 0) + { + rc = paError(pPAStream->pDrv, "Failed to write to output stream"); + break; + } + + Assert(cbLeft >= cbChunk); + cbLeft -= cbChunk; + cbWrittenTotal += cbChunk; + } + + } while (0); + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + if (RT_SUCCESS(rc)) + { + if (pcxWritten) + *pcxWritten = cbWrittenTotal; + } + + return rc; +} + + +/** @todo Implement va handling. */ +static int paError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(szMsg, VERR_INVALID_POINTER); + + if (pThis->cLogErrors++ < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS) + { + int rc2 = pa_context_errno(pThis->pContext); + LogRel2(("PulseAudio: %s: %s\n", szMsg, pa_strerror(rc2))); + } + + /** @todo Implement some PulseAudio -> IPRT mapping here. */ + return VERR_GENERAL_FAILURE; +} + + +static void paEnumSinkCb(pa_context *pCtx, const pa_sink_info *pInfo, int eol, void *pvUserData) +{ + if (eol > 0) + return; + + PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData; + AssertPtrReturnVoid(pCbCtx); + PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv; + AssertPtrReturnVoid(pThis); + if (eol < 0) + { + pThis->fEnumOpSuccess = false; + pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0); + return; + } + + AssertPtrReturnVoid(pCtx); + AssertPtrReturnVoid(pInfo); + + LogRel2(("PulseAudio: Using output sink '%s'\n", pInfo->name)); + + /** @todo Store sinks + channel mapping in callback context as soon as we have surround support. */ + pCbCtx->cDevOut++; + + pThis->fEnumOpSuccess = true; + pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0); +} + + +static void paEnumSourceCb(pa_context *pCtx, const pa_source_info *pInfo, int eol, void *pvUserData) +{ + if (eol > 0) + return; + + PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData; + AssertPtrReturnVoid(pCbCtx); + PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv; + AssertPtrReturnVoid(pThis); + if (eol < 0) + { + pThis->fEnumOpSuccess = false; + pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0); + return; + } + + AssertPtrReturnVoid(pCtx); + AssertPtrReturnVoid(pInfo); + + LogRel2(("PulseAudio: Using input source '%s'\n", pInfo->name)); + + /** @todo Store sources + channel mapping in callback context as soon as we have surround support. */ + pCbCtx->cDevIn++; + + pThis->fEnumOpSuccess = true; + pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0); +} + + +static void paEnumServerCb(pa_context *pCtx, const pa_server_info *pInfo, void *pvUserData) +{ + AssertPtrReturnVoid(pCtx); + PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData; + AssertPtrReturnVoid(pCbCtx); + PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv; + AssertPtrReturnVoid(pThis); + + if (!pInfo) + { + pThis->fEnumOpSuccess = false; + pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0); + return; + } + + if (pInfo->default_sink_name) + { + Assert(RTStrIsValidEncoding(pInfo->default_sink_name)); + pCbCtx->pszDefaultSink = RTStrDup(pInfo->default_sink_name); + } + + if (pInfo->default_sink_name) + { + Assert(RTStrIsValidEncoding(pInfo->default_source_name)); + pCbCtx->pszDefaultSource = RTStrDup(pInfo->default_source_name); + } + + pThis->fEnumOpSuccess = true; + pa_threaded_mainloop_signal(pThis->pMainLoop, 0); +} + + +static int paEnumerate(PDRVHOSTPULSEAUDIO pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + PDMAUDIOBACKENDCFG Cfg; + RT_ZERO(Cfg); + + RTStrPrintf2(Cfg.szName, sizeof(Cfg.szName), "PulseAudio driver"); + + Cfg.cbStreamOut = sizeof(PULSEAUDIOSTREAM); + Cfg.cbStreamIn = sizeof(PULSEAUDIOSTREAM); + Cfg.cMaxStreamsOut = UINT32_MAX; + Cfg.cMaxStreamsIn = UINT32_MAX; + + PULSEAUDIOENUMCBCTX CbCtx; + RT_ZERO(CbCtx); + + CbCtx.pDrv = pThis; + CbCtx.fFlags = fEnum; + + bool fLog = (fEnum & PULSEAUDIOENUMCBFLAGS_LOG); + + pa_threaded_mainloop_lock(pThis->pMainLoop); + + pThis->fEnumOpSuccess = false; + + LogRel(("PulseAudio: Retrieving server information ...\n")); + + /* Check if server information is available and bail out early if it isn't. */ + pa_operation *paOpServerInfo = pa_context_get_server_info(pThis->pContext, paEnumServerCb, &CbCtx); + if (!paOpServerInfo) + { + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + LogRel(("PulseAudio: Server information not available, skipping enumeration\n")); + return VINF_SUCCESS; + } + + int rc = paWaitFor(pThis, paOpServerInfo); + if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess) + rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */ + if (RT_SUCCESS(rc)) + { + if (CbCtx.pszDefaultSink) + { + if (fLog) + LogRel2(("PulseAudio: Default output sink is '%s'\n", CbCtx.pszDefaultSink)); + + pThis->fEnumOpSuccess = false; + rc = paWaitFor(pThis, pa_context_get_sink_info_by_name(pThis->pContext, CbCtx.pszDefaultSink, + paEnumSinkCb, &CbCtx)); + if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess) + rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */ + if ( RT_FAILURE(rc) + && fLog) + { + LogRel(("PulseAudio: Error enumerating properties for default output sink '%s'\n", CbCtx.pszDefaultSink)); + } + } + else if (fLog) + LogRel2(("PulseAudio: No default output sink found\n")); + + if (RT_SUCCESS(rc)) + { + if (CbCtx.pszDefaultSource) + { + if (fLog) + LogRel2(("PulseAudio: Default input source is '%s'\n", CbCtx.pszDefaultSource)); + + pThis->fEnumOpSuccess = false; + rc = paWaitFor(pThis, pa_context_get_source_info_by_name(pThis->pContext, CbCtx.pszDefaultSource, + paEnumSourceCb, &CbCtx)); + if ( (RT_FAILURE(rc) || !pThis->fEnumOpSuccess) + && fLog) + { + LogRel(("PulseAudio: Error enumerating properties for default input source '%s'\n", CbCtx.pszDefaultSource)); + } + } + else if (fLog) + LogRel2(("PulseAudio: No default input source found\n")); + } + + if (RT_SUCCESS(rc)) + { + if (fLog) + { + LogRel2(("PulseAudio: Found %RU8 host playback device(s)\n", CbCtx.cDevOut)); + LogRel2(("PulseAudio: Found %RU8 host capturing device(s)\n", CbCtx.cDevIn)); + } + + if (pCfg) + memcpy(pCfg, &Cfg, sizeof(PDMAUDIOBACKENDCFG)); + } + + if (CbCtx.pszDefaultSink) + { + RTStrFree(CbCtx.pszDefaultSink); + CbCtx.pszDefaultSink = NULL; + } + + if (CbCtx.pszDefaultSource) + { + RTStrFree(CbCtx.pszDefaultSource); + CbCtx.pszDefaultSource = NULL; + } + } + else if (fLog) + LogRel(("PulseAudio: Error enumerating PulseAudio server properties\n")); + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +static int paDestroyStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA) +{ + LogFlowFuncEnter(); + + if (pStreamPA->pStream) + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + + pa_stream_disconnect(pStreamPA->pStream); + pa_stream_unref(pStreamPA->pStream); + + pStreamPA->pStream = NULL; + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + } + + return VINF_SUCCESS; +} + + +static int paDestroyStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA) +{ + if (pStreamPA->pStream) + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + + /* Make sure to cancel a pending draining operation, if any. */ + if (pStreamPA->pDrainOp) + { + pa_operation_cancel(pStreamPA->pDrainOp); + pStreamPA->pDrainOp = NULL; + } + + pa_stream_disconnect(pStreamPA->pStream); + pa_stream_unref(pStreamPA->pStream); + + pStreamPA->pStream = NULL; + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + } + + return VINF_SUCCESS; +} + + +static int paControlStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + int rc = VINF_SUCCESS; + + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + case PDMAUDIOSTREAMCMD_RESUME: + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + + if ( pStreamPA->pDrainOp + && pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_DONE) + { + pa_operation_cancel(pStreamPA->pDrainOp); + pa_operation_unref(pStreamPA->pDrainOp); + + pStreamPA->pDrainOp = NULL; + } + else + { + /* Uncork (resume) stream. */ + rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Uncork */, paStreamCbSuccess, pStreamPA)); + } + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + break; + } + + case PDMAUDIOSTREAMCMD_DISABLE: + case PDMAUDIOSTREAMCMD_PAUSE: + { + /* Pause audio output (the Pause bit of the AC97 x_CR register is set). + * Note that we must return immediately from here! */ + pa_threaded_mainloop_lock(pThis->pMainLoop); + if (!pStreamPA->pDrainOp) + { + rc = paWaitFor(pThis, pa_stream_trigger(pStreamPA->pStream, paStreamCbSuccess, pStreamPA)); + if (RT_SUCCESS(rc)) + pStreamPA->pDrainOp = pa_stream_drain(pStreamPA->pStream, paStreamCbDrain, pStreamPA); + } + pa_threaded_mainloop_unlock(pThis->pMainLoop); + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +static int paControlStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd)); + + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + case PDMAUDIOSTREAMCMD_RESUME: + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Play / resume */, paStreamCbSuccess, pStreamPA)); + pa_threaded_mainloop_unlock(pThis->pMainLoop); + break; + } + + case PDMAUDIOSTREAMCMD_DISABLE: + case PDMAUDIOSTREAMCMD_PAUSE: + { + pa_threaded_mainloop_lock(pThis->pMainLoop); + if (pStreamPA->pu8PeekBuf) /* Do we need to drop the peek buffer?*/ + { + pa_stream_drop(pStreamPA->pStream); + pStreamPA->pu8PeekBuf = NULL; + } + + rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 1 /* Stop / pause */, paStreamCbSuccess, pStreamPA)); + pa_threaded_mainloop_unlock(pThis->pMainLoop); + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} + */ +static DECLCALLBACK(void) drvHostPulseAudioShutdown(PPDMIHOSTAUDIO pInterface) +{ + AssertPtrReturnVoid(pInterface); + + PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); + + LogFlowFuncEnter(); + + if (pThis->pMainLoop) + pa_threaded_mainloop_stop(pThis->pMainLoop); + + if (pThis->pContext) + { + pa_context_disconnect(pThis->pContext); + pa_context_unref(pThis->pContext); + pThis->pContext = NULL; + } + + if (pThis->pMainLoop) + { + pa_threaded_mainloop_free(pThis->pMainLoop); + pThis->pMainLoop = NULL; + } + + LogFlowFuncLeave(); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHostPulseAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); + + return paEnumerate(pThis, pBackendCfg, PULSEAUDIOENUMCBFLAGS_LOG /* fEnum */); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostPulseAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(enmDir); + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostPulseAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); + PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream; + + int rc; + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + rc = paCreateStreamIn (pThis, pStreamPA, pCfgReq, pCfgAcq); + else if (pCfgReq->enmDir == PDMAUDIODIR_OUT) + rc = paCreateStreamOut(pThis, pStreamPA, pCfgReq, pCfgAcq); + else + AssertFailedReturn(VERR_NOT_IMPLEMENTED); + + if (RT_SUCCESS(rc)) + { + pStreamPA->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); + if (!pStreamPA->pCfg) + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHostPulseAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); + PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream; + + if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc; + if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN) + rc = paDestroyStreamIn (pThis, pStreamPA); + else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT) + rc = paDestroyStreamOut(pThis, pStreamPA); + else + AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + + if (RT_SUCCESS(rc)) + { + DrvAudioHlpStreamCfgFree(pStreamPA->pCfg); + pStreamPA->pCfg = NULL; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} + */ +static DECLCALLBACK(int) drvHostPulseAudioStreamControl(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); + PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream; + + if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc; + if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN) + rc = paControlStreamIn (pThis, pStreamPA, enmStreamCmd); + else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT) + rc = paControlStreamOut(pThis, pStreamPA, enmStreamCmd); + else + AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + + return rc; +} + + +static uint32_t paStreamGetAvail(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA) +{ + pa_threaded_mainloop_lock(pThis->pMainLoop); + + uint32_t cbAvail = 0; + + if (PA_STREAM_IS_GOOD(pa_stream_get_state(pStreamPA->pStream))) + { + if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN) + { + cbAvail = (uint32_t)pa_stream_readable_size(pStreamPA->pStream); + Log3Func(("cbReadable=%RU32\n", cbAvail)); + } + else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT) + { + size_t cbWritable = pa_stream_writable_size(pStreamPA->pStream); + + Log3Func(("cbWritable=%zu, maxLength=%RU32, minReq=%RU32\n", + cbWritable, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq)); + + /* Don't report more writable than the PA server can handle. */ + if (cbWritable > pStreamPA->BufAttr.maxlength) + cbWritable = pStreamPA->BufAttr.maxlength; + + cbAvail = (uint32_t)cbWritable; + } + else + AssertFailed(); + } + + pa_threaded_mainloop_unlock(pThis->pMainLoop); + + return cbAvail; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostPulseAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); + PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream; + + return paStreamGetAvail(pThis, pStreamPA); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostPulseAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); + PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream; + + return paStreamGetAvail(pThis, pStreamPA); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} + */ +static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostPulseAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + RT_NOREF(pStream); + + PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface); + + PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_NONE; + + /* Check PulseAudio's general status. */ + if ( pThis->pContext + && PA_CONTEXT_IS_GOOD(pa_context_get_state(pThis->pContext))) + { + strmSts = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED; + } + + return strmSts; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} + */ +static DECLCALLBACK(int) drvHostPulseAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + /* Nothing to do here for PulseAudio. */ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostPulseAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + AssertPtrReturn(pInterface, NULL); + AssertPtrReturn(pszIID, NULL); + + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + + return NULL; +} + + +/** + * Destructs a PulseAudio Audio driver instance. + * + * @copydoc FNPDMDRVDESTRUCT + */ +static DECLCALLBACK(void) drvHostPulseAudioDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + LogFlowFuncEnter(); +} + + +/** + * Constructs a PulseAudio Audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvHostPulseAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER); + + PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO); + LogRel(("Audio: Initializing PulseAudio driver\n")); + + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostPulseAudioQueryInterface; + /* IHostAudio */ + PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostPulseAudio); + + int rc2 = CFGMR3QueryString(pCfg, "StreamName", pThis->szStreamName, sizeof(pThis->szStreamName)); + AssertMsgRCReturn(rc2, ("Confguration error: No/bad \"StreamName\" value, rc=%Rrc\n", rc2), rc2); + + return VINF_SUCCESS; +} + + +/** + * Pulse audio driver registration record. + */ +const PDMDRVREG g_DrvHostPulseAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "PulseAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Pulse Audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTPULSEAUDIO), + /* pfnConstruct */ + drvHostPulseAudioConstruct, + /* pfnDestruct */ + drvHostPulseAudioDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + +#if 0 /* unused */ +static struct audio_option pulse_options[] = +{ + {"DAC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_out, + "DAC period size in milliseconds", NULL, 0}, + {"ADC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_in, + "ADC period size in milliseconds", NULL, 0} +}; +#endif + diff --git a/src/VBox/Devices/Audio/DrvHostValidationKit.cpp b/src/VBox/Devices/Audio/DrvHostValidationKit.cpp new file mode 100644 index 00000000..3344e001 --- /dev/null +++ b/src/VBox/Devices/Audio/DrvHostValidationKit.cpp @@ -0,0 +1,482 @@ +/* $Id: DrvHostValidationKit.cpp $ */ +/** @file + * ValidationKit audio driver - host backend for dumping and injecting audio data + * from/to the device emulation. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <iprt/alloc.h> +#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */ + +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <VBox/log.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include "DrvAudio.h" +#include "VBoxDD.h" + + +/** + * Structure for keeping a VAKIT input/output stream. + */ +typedef struct VAKITAUDIOSTREAM +{ + /** The stream's acquired configuration. */ + PPDMAUDIOSTREAMCFG pCfg; + /** Audio file to dump output to or read input from. */ + PPDMAUDIOFILE pFile; + /** Text file to store timing of audio buffers submittions**/ + RTFILE hFileTiming; + /** Timestamp of the first play or record request**/ + uint64_t tsStarted; + /** Total number of samples played or recorded so far**/ + uint32_t uSamplesSinceStarted; + union + { + struct + { + /** Timestamp of last captured samples. */ + uint64_t tsLastCaptured; + } In; + struct + { + /** Timestamp of last played samples. */ + uint64_t tsLastPlayed; + uint8_t *pu8PlayBuffer; + uint32_t cbPlayBuffer; + } Out; + }; +} VAKITAUDIOSTREAM, *PVAKITAUDIOSTREAM; + +/** + * VAKIT audio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHOSTVAKITAUDIO +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; +} DRVHOSTVAKITAUDIO, *PDRVHOSTVAKITAUDIO; + +/*******************************************PDM_AUDIO_DRIVER******************************/ + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHostVaKitAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Validation Kit audio driver"); + + pBackendCfg->cbStreamOut = sizeof(VAKITAUDIOSTREAM); + pBackendCfg->cbStreamIn = sizeof(VAKITAUDIOSTREAM); + + pBackendCfg->cMaxStreamsOut = 1; /* Output */ + pBackendCfg->cMaxStreamsIn = 0; /* No input supported yet. */ + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} + */ +static DECLCALLBACK(int) drvHostVaKitAudioInit(PPDMIHOSTAUDIO pInterface) +{ + RT_NOREF(pInterface); + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} + */ +static DECLCALLBACK(void) drvHostVaKitAudioShutdown(PPDMIHOSTAUDIO pInterface) +{ + RT_NOREF(pInterface); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostVaKitAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(enmDir); + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +static int vakitCreateStreamIn(PDRVHOSTVAKITAUDIO pDrv, PVAKITAUDIOSTREAM pStreamDbg, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + RT_NOREF(pDrv, pStreamDbg, pCfgReq, pCfgAcq); + + return VINF_SUCCESS; +} + + +static int vakitCreateStreamOut(PDRVHOSTVAKITAUDIO pDrv, PVAKITAUDIOSTREAM pStreamDbg, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + RT_NOREF(pDrv, pCfgAcq); + + int rc = VINF_SUCCESS; + + pStreamDbg->tsStarted = 0; + pStreamDbg->uSamplesSinceStarted = 0; + pStreamDbg->Out.tsLastPlayed = 0; + pStreamDbg->Out.cbPlayBuffer = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props); + pStreamDbg->Out.pu8PlayBuffer = (uint8_t *)RTMemAlloc(pStreamDbg->Out.cbPlayBuffer); + if (!pStreamDbg->Out.pu8PlayBuffer) + rc = VERR_NO_MEMORY; + + if (RT_SUCCESS(rc)) + { + char szTemp[RTPATH_MAX]; + rc = RTPathTemp(szTemp, sizeof(szTemp)); + + RTPathAppend(szTemp, sizeof(szTemp), "VBoxTestTmp\\VBoxAudioValKit"); + + if (RT_SUCCESS(rc)) + { + char szFile[RTPATH_MAX + 1]; + + rc = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), szTemp, "VaKit", + 0 /* Instance */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE); + if (RT_SUCCESS(rc)) + { + rc = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE, &pStreamDbg->pFile); + if (RT_SUCCESS(rc)) + rc = DrvAudioHlpFileOpen(pStreamDbg->pFile, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, &pCfgReq->Props); + } + + if (RT_FAILURE(rc)) + { + LogRel(("VaKitAudio: Creating output file '%s' failed with %Rrc\n", szFile, rc)); + } + else + { + size_t cch; + char szTimingInfo[128]; + cch = RTStrPrintf(szTimingInfo, sizeof(szTimingInfo), "# %dHz %dch %dbit\n", + pCfgReq->Props.uHz, pCfgReq->Props.cChannels, pCfgReq->Props.cBytes * 8); + + RTFileWrite(pStreamDbg->hFileTiming, szTimingInfo, cch, NULL); + } + } + else + LogRel(("VaKitAudio: Unable to retrieve temp dir: %Rrc\n", rc)); + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostVaKitAudioStreamCreate(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + PDRVHOSTVAKITAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTVAKITAUDIO, IHostAudio); + PVAKITAUDIOSTREAM pStreamDbg = (PVAKITAUDIOSTREAM)pStream; + + int rc; + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + rc = vakitCreateStreamIn( pDrv, pStreamDbg, pCfgReq, pCfgAcq); + else + rc = vakitCreateStreamOut(pDrv, pStreamDbg, pCfgReq, pCfgAcq); + + if (RT_SUCCESS(rc)) + { + pStreamDbg->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); + if (!pStreamDbg->pCfg) + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHostVaKitAudioStreamPlay(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf, + uint32_t *pcxWritten) +{ + PDRVHOSTVAKITAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTVAKITAUDIO, IHostAudio); + PVAKITAUDIOSTREAM pStreamDbg = (PVAKITAUDIOSTREAM)pStream; + RT_NOREF(pDrv); + + uint64_t tsSinceStart; + size_t cch; + char szTimingInfo[128]; + + if (pStreamDbg->tsStarted == 0) + { + pStreamDbg->tsStarted = RTTimeNanoTS(); + tsSinceStart = 0; + } + else + { + tsSinceStart = RTTimeNanoTS() - pStreamDbg->tsStarted; + } + + // Microseconds are used everythere below + uint32_t sBuf = cxBuf >> pStreamDbg->pCfg->Props.cShift; + cch = RTStrPrintf(szTimingInfo, sizeof(szTimingInfo), "%d %d %d %d\n", + (uint32_t)(tsSinceStart / 1000), // Host time elapsed since Guest submitted the first buffer for playback + (uint32_t)(pStreamDbg->uSamplesSinceStarted * 1.0E6 / pStreamDbg->pCfg->Props.uHz), // how long all the samples submitted previously were played + (uint32_t)(sBuf * 1.0E6 / pStreamDbg->pCfg->Props.uHz), // how long a new uSamplesReady samples should\will be played + sBuf); + RTFileWrite(pStreamDbg->hFileTiming, szTimingInfo, cch, NULL); + pStreamDbg->uSamplesSinceStarted += sBuf; + + /* Remember when samples were consumed. */ + // pStreamDbg->Out.tsLastPlayed = PDMDrvHlpTMGetVirtualTime(pDrv->pDrvIns);; + + int rc2 = DrvAudioHlpFileWrite(pStreamDbg->pFile, pvBuf, cxBuf, 0 /* fFlags */); + if (RT_FAILURE(rc2)) + LogRel(("VaKitAudio: Writing output failed with %Rrc\n", rc2)); + + *pcxWritten = cxBuf; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHostVaKitAudioStreamCapture(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, void *pvBuf, uint32_t cxBuf, + uint32_t *pcxRead) +{ + RT_NOREF(pInterface, pStream, pvBuf, cxBuf); + + /* Never capture anything. */ + if (pcxRead) + *pcxRead = 0; + + return VINF_SUCCESS; +} + + +static int vakitDestroyStreamIn(PDRVHOSTVAKITAUDIO pDrv, PVAKITAUDIOSTREAM pStreamDbg) +{ + RT_NOREF(pDrv, pStreamDbg); + return VINF_SUCCESS; +} + + +static int vakitDestroyStreamOut(PDRVHOSTVAKITAUDIO pDrv, PVAKITAUDIOSTREAM pStreamDbg) +{ + RT_NOREF(pDrv); + + if (pStreamDbg->Out.pu8PlayBuffer) + { + RTMemFree(pStreamDbg->Out.pu8PlayBuffer); + pStreamDbg->Out.pu8PlayBuffer = NULL; + } + + if (pStreamDbg->pFile) + { + size_t cbDataSize = DrvAudioHlpFileGetDataSize(pStreamDbg->pFile); + if (cbDataSize) + LogRel(("VaKitAudio: Created output file '%s' (%zu bytes)\n", pStreamDbg->pFile->szName, cbDataSize)); + + DrvAudioHlpFileDestroy(pStreamDbg->pFile); + pStreamDbg->pFile = NULL; + } + + return VINF_SUCCESS; +} + + +static DECLCALLBACK(int) drvHostVaKitAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + + PDRVHOSTVAKITAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTVAKITAUDIO, IHostAudio); + PVAKITAUDIOSTREAM pStreamDbg = (PVAKITAUDIOSTREAM)pStream; + + if (!pStreamDbg->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc; + if (pStreamDbg->pCfg->enmDir == PDMAUDIODIR_IN) + rc = vakitDestroyStreamIn (pDrv, pStreamDbg); + else + rc = vakitDestroyStreamOut(pDrv, pStreamDbg); + + if (RT_SUCCESS(rc)) + { + DrvAudioHlpStreamCfgFree(pStreamDbg->pCfg); + pStreamDbg->pCfg = NULL; + } + + return rc; +} + +static DECLCALLBACK(int) drvHostVaKitAudioStreamControl(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + RT_NOREF(enmStreamCmd); + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostVaKitAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return UINT32_MAX; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostVaKitAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return UINT32_MAX; +} + +static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostVaKitAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return (PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED); +} + +static DECLCALLBACK(int) drvHostVaKitAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostVaKitAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTVAKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVAKITAUDIO); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/** + * Constructs a VaKit audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvHostVaKitAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHOSTVAKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVAKITAUDIO); + LogRel(("Audio: Initializing VAKIT driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostVaKitAudioQueryInterface; + /* IHostAudio */ + PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostVaKitAudio); + + return VINF_SUCCESS; +} + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvHostValidationKitAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "ValidationKitAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "ValidationKitAudio audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTVAKITAUDIO), + /* pfnConstruct */ + drvHostVaKitAudioConstruct, + /* pfnDestruct */ + NULL, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff --git a/src/VBox/Devices/Audio/HDACodec.cpp b/src/VBox/Devices/Audio/HDACodec.cpp new file mode 100644 index 00000000..a13ce526 --- /dev/null +++ b/src/VBox/Devices/Audio/HDACodec.cpp @@ -0,0 +1,3301 @@ +/* $Id: HDACodec.cpp $ */ +/** @file + * HDACodec - VBox HD Audio Codec. + * + * Implemented based on the Intel HD Audio specification and the + * Sigmatel/IDT STAC9220 datasheet. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_HDA_CODEC +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdmaudioifs.h> +#include <iprt/assert.h> +#include <iprt/uuid.h> +#include <iprt/string.h> +#include <iprt/mem.h> +#include <iprt/asm.h> +#include <iprt/cpp/utils.h> + +#include "VBoxDD.h" +#include "DrvAudio.h" +#include "HDACodec.h" +#include "DevHDACommon.h" +#include "AudioMixer.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* PRM 5.3.1 */ +/** Codec address mask. */ +#define CODEC_CAD_MASK 0xF0000000 +/** Codec address shift. */ +#define CODEC_CAD_SHIFT 28 +#define CODEC_DIRECT_MASK RT_BIT(27) +/** Node ID mask. */ +#define CODEC_NID_MASK 0x07F00000 +/** Node ID shift. */ +#define CODEC_NID_SHIFT 20 +#define CODEC_VERBDATA_MASK 0x000FFFFF +#define CODEC_VERB_4BIT_CMD 0x000FFFF0 +#define CODEC_VERB_4BIT_DATA 0x0000000F +#define CODEC_VERB_8BIT_CMD 0x000FFF00 +#define CODEC_VERB_8BIT_DATA 0x000000FF +#define CODEC_VERB_16BIT_CMD 0x000F0000 +#define CODEC_VERB_16BIT_DATA 0x0000FFFF + +#define CODEC_CAD(cmd) (((cmd) & CODEC_CAD_MASK) >> CODEC_CAD_SHIFT) +#define CODEC_DIRECT(cmd) ((cmd) & CODEC_DIRECT_MASK) +#define CODEC_NID(cmd) ((((cmd) & CODEC_NID_MASK)) >> CODEC_NID_SHIFT) +#define CODEC_VERBDATA(cmd) ((cmd) & CODEC_VERBDATA_MASK) +#define CODEC_VERB_CMD(cmd, mask, x) (((cmd) & (mask)) >> (x)) +#define CODEC_VERB_CMD4(cmd) (CODEC_VERB_CMD((cmd), CODEC_VERB_4BIT_CMD, 4)) +#define CODEC_VERB_CMD8(cmd) (CODEC_VERB_CMD((cmd), CODEC_VERB_8BIT_CMD, 8)) +#define CODEC_VERB_CMD16(cmd) (CODEC_VERB_CMD((cmd), CODEC_VERB_16BIT_CMD, 16)) +#define CODEC_VERB_PAYLOAD4(cmd) ((cmd) & CODEC_VERB_4BIT_DATA) +#define CODEC_VERB_PAYLOAD8(cmd) ((cmd) & CODEC_VERB_8BIT_DATA) +#define CODEC_VERB_PAYLOAD16(cmd) ((cmd) & CODEC_VERB_16BIT_DATA) + +#define CODEC_VERB_GET_AMP_DIRECTION RT_BIT(15) +#define CODEC_VERB_GET_AMP_SIDE RT_BIT(13) +#define CODEC_VERB_GET_AMP_INDEX 0x7 + +/* HDA spec 7.3.3.7 NoteA */ +#define CODEC_GET_AMP_DIRECTION(cmd) (((cmd) & CODEC_VERB_GET_AMP_DIRECTION) >> 15) +#define CODEC_GET_AMP_SIDE(cmd) (((cmd) & CODEC_VERB_GET_AMP_SIDE) >> 13) +#define CODEC_GET_AMP_INDEX(cmd) (CODEC_GET_AMP_DIRECTION(cmd) ? 0 : ((cmd) & CODEC_VERB_GET_AMP_INDEX)) + +/* HDA spec 7.3.3.7 NoteC */ +#define CODEC_VERB_SET_AMP_OUT_DIRECTION RT_BIT(15) +#define CODEC_VERB_SET_AMP_IN_DIRECTION RT_BIT(14) +#define CODEC_VERB_SET_AMP_LEFT_SIDE RT_BIT(13) +#define CODEC_VERB_SET_AMP_RIGHT_SIDE RT_BIT(12) +#define CODEC_VERB_SET_AMP_INDEX (0x7 << 8) +#define CODEC_VERB_SET_AMP_MUTE RT_BIT(7) +/** Note: 7-bit value [6:0]. */ +#define CODEC_VERB_SET_AMP_GAIN 0x7F + +#define CODEC_SET_AMP_IS_OUT_DIRECTION(cmd) (((cmd) & CODEC_VERB_SET_AMP_OUT_DIRECTION) != 0) +#define CODEC_SET_AMP_IS_IN_DIRECTION(cmd) (((cmd) & CODEC_VERB_SET_AMP_IN_DIRECTION) != 0) +#define CODEC_SET_AMP_IS_LEFT_SIDE(cmd) (((cmd) & CODEC_VERB_SET_AMP_LEFT_SIDE) != 0) +#define CODEC_SET_AMP_IS_RIGHT_SIDE(cmd) (((cmd) & CODEC_VERB_SET_AMP_RIGHT_SIDE) != 0) +#define CODEC_SET_AMP_INDEX(cmd) (((cmd) & CODEC_VERB_SET_AMP_INDEX) >> 7) +#define CODEC_SET_AMP_MUTE(cmd) ((cmd) & CODEC_VERB_SET_AMP_MUTE) +#define CODEC_SET_AMP_GAIN(cmd) ((cmd) & CODEC_VERB_SET_AMP_GAIN) + +/* HDA spec 7.3.3.1 defines layout of configuration registers/verbs (0xF00) */ +/* VendorID (7.3.4.1) */ +#define CODEC_MAKE_F00_00(vendorID, deviceID) (((vendorID) << 16) | (deviceID)) +#define CODEC_F00_00_VENDORID(f00_00) (((f00_00) >> 16) & 0xFFFF) +#define CODEC_F00_00_DEVICEID(f00_00) ((f00_00) & 0xFFFF) + +/** RevisionID (7.3.4.2). */ +#define CODEC_MAKE_F00_02(majRev, minRev, venFix, venProg, stepFix, stepProg) \ + ( (((majRev) & 0xF) << 20) \ + | (((minRev) & 0xF) << 16) \ + | (((venFix) & 0xF) << 12) \ + | (((venProg) & 0xF) << 8) \ + | (((stepFix) & 0xF) << 4) \ + | ((stepProg) & 0xF)) + +/** Subordinate node count (7.3.4.3). */ +#define CODEC_MAKE_F00_04(startNodeNumber, totalNodeNumber) ((((startNodeNumber) & 0xFF) << 16)|((totalNodeNumber) & 0xFF)) +#define CODEC_F00_04_TO_START_NODE_NUMBER(f00_04) (((f00_04) >> 16) & 0xFF) +#define CODEC_F00_04_TO_NODE_COUNT(f00_04) ((f00_04) & 0xFF) +/* + * Function Group Type (7.3.4.4) + * 0 & [0x3-0x7f] are reserved types + * [0x80 - 0xff] are vendor defined function groups + */ +#define CODEC_MAKE_F00_05(UnSol, NodeType) (((UnSol) << 8)|(NodeType)) +#define CODEC_F00_05_UNSOL RT_BIT(8) +#define CODEC_F00_05_AFG (0x1) +#define CODEC_F00_05_MFG (0x2) +#define CODEC_F00_05_IS_UNSOL(f00_05) RT_BOOL((f00_05) & RT_BIT(8)) +#define CODEC_F00_05_GROUP(f00_05) ((f00_05) & 0xff) +/* Audio Function Group capabilities (7.3.4.5). */ +#define CODEC_MAKE_F00_08(BeepGen, InputDelay, OutputDelay) ((((BeepGen) & 0x1) << 16)| (((InputDelay) & 0xF) << 8) | ((OutputDelay) & 0xF)) +#define CODEC_F00_08_BEEP_GEN(f00_08) ((f00_08) & RT_BIT(16) + +/* Converter Stream, Channel (7.3.3.11). */ +#define CODEC_F00_06_GET_STREAM_ID(cmd) (((cmd) >> 4) & 0x0F) +#define CODEC_F00_06_GET_CHANNEL_ID(cmd) (((cmd) & 0x0F)) + +/* Widget Capabilities (7.3.4.6). */ +#define CODEC_MAKE_F00_09(type, delay, chan_ext) \ + ( (((type) & 0xF) << 20) \ + | (((delay) & 0xF) << 16) \ + | (((chan_ext) & 0xF) << 13)) +/* note: types 0x8-0xe are reserved */ +#define CODEC_F00_09_TYPE_AUDIO_OUTPUT (0x0) +#define CODEC_F00_09_TYPE_AUDIO_INPUT (0x1) +#define CODEC_F00_09_TYPE_AUDIO_MIXER (0x2) +#define CODEC_F00_09_TYPE_AUDIO_SELECTOR (0x3) +#define CODEC_F00_09_TYPE_PIN_COMPLEX (0x4) +#define CODEC_F00_09_TYPE_POWER_WIDGET (0x5) +#define CODEC_F00_09_TYPE_VOLUME_KNOB (0x6) +#define CODEC_F00_09_TYPE_BEEP_GEN (0x7) +#define CODEC_F00_09_TYPE_VENDOR_DEFINED (0xF) + +#define CODEC_F00_09_CAP_CP RT_BIT(12) +#define CODEC_F00_09_CAP_L_R_SWAP RT_BIT(11) +#define CODEC_F00_09_CAP_POWER_CTRL RT_BIT(10) +#define CODEC_F00_09_CAP_DIGITAL RT_BIT(9) +#define CODEC_F00_09_CAP_CONNECTION_LIST RT_BIT(8) +#define CODEC_F00_09_CAP_UNSOL RT_BIT(7) +#define CODEC_F00_09_CAP_PROC_WIDGET RT_BIT(6) +#define CODEC_F00_09_CAP_STRIPE RT_BIT(5) +#define CODEC_F00_09_CAP_FMT_OVERRIDE RT_BIT(4) +#define CODEC_F00_09_CAP_AMP_FMT_OVERRIDE RT_BIT(3) +#define CODEC_F00_09_CAP_OUT_AMP_PRESENT RT_BIT(2) +#define CODEC_F00_09_CAP_IN_AMP_PRESENT RT_BIT(1) +#define CODEC_F00_09_CAP_STEREO RT_BIT(0) + +#define CODEC_F00_09_TYPE(f00_09) (((f00_09) >> 20) & 0xF) + +#define CODEC_F00_09_IS_CAP_CP(f00_09) RT_BOOL((f00_09) & RT_BIT(12)) +#define CODEC_F00_09_IS_CAP_L_R_SWAP(f00_09) RT_BOOL((f00_09) & RT_BIT(11)) +#define CODEC_F00_09_IS_CAP_POWER_CTRL(f00_09) RT_BOOL((f00_09) & RT_BIT(10)) +#define CODEC_F00_09_IS_CAP_DIGITAL(f00_09) RT_BOOL((f00_09) & RT_BIT(9)) +#define CODEC_F00_09_IS_CAP_CONNECTION_LIST(f00_09) RT_BOOL((f00_09) & RT_BIT(8)) +#define CODEC_F00_09_IS_CAP_UNSOL(f00_09) RT_BOOL((f00_09) & RT_BIT(7)) +#define CODEC_F00_09_IS_CAP_PROC_WIDGET(f00_09) RT_BOOL((f00_09) & RT_BIT(6)) +#define CODEC_F00_09_IS_CAP_STRIPE(f00_09) RT_BOOL((f00_09) & RT_BIT(5)) +#define CODEC_F00_09_IS_CAP_FMT_OVERRIDE(f00_09) RT_BOOL((f00_09) & RT_BIT(4)) +#define CODEC_F00_09_IS_CAP_AMP_OVERRIDE(f00_09) RT_BOOL((f00_09) & RT_BIT(3)) +#define CODEC_F00_09_IS_CAP_OUT_AMP_PRESENT(f00_09) RT_BOOL((f00_09) & RT_BIT(2)) +#define CODEC_F00_09_IS_CAP_IN_AMP_PRESENT(f00_09) RT_BOOL((f00_09) & RT_BIT(1)) +#define CODEC_F00_09_IS_CAP_LSB(f00_09) RT_BOOL((f00_09) & RT_BIT(0)) + +/* Supported PCM size, rates (7.3.4.7) */ +#define CODEC_F00_0A_32_BIT RT_BIT(19) +#define CODEC_F00_0A_24_BIT RT_BIT(18) +#define CODEC_F00_0A_16_BIT RT_BIT(17) +#define CODEC_F00_0A_8_BIT RT_BIT(16) + +#define CODEC_F00_0A_48KHZ_MULT_8X RT_BIT(11) +#define CODEC_F00_0A_48KHZ_MULT_4X RT_BIT(10) +#define CODEC_F00_0A_44_1KHZ_MULT_4X RT_BIT(9) +#define CODEC_F00_0A_48KHZ_MULT_2X RT_BIT(8) +#define CODEC_F00_0A_44_1KHZ_MULT_2X RT_BIT(7) +#define CODEC_F00_0A_48KHZ RT_BIT(6) +#define CODEC_F00_0A_44_1KHZ RT_BIT(5) +/* 2/3 * 48kHz */ +#define CODEC_F00_0A_48KHZ_2_3X RT_BIT(4) +/* 1/2 * 44.1kHz */ +#define CODEC_F00_0A_44_1KHZ_1_2X RT_BIT(3) +/* 1/3 * 48kHz */ +#define CODEC_F00_0A_48KHZ_1_3X RT_BIT(2) +/* 1/4 * 44.1kHz */ +#define CODEC_F00_0A_44_1KHZ_1_4X RT_BIT(1) +/* 1/6 * 48kHz */ +#define CODEC_F00_0A_48KHZ_1_6X RT_BIT(0) + +/* Supported streams formats (7.3.4.8) */ +#define CODEC_F00_0B_AC3 RT_BIT(2) +#define CODEC_F00_0B_FLOAT32 RT_BIT(1) +#define CODEC_F00_0B_PCM RT_BIT(0) + +/* Pin Capabilities (7.3.4.9)*/ +#define CODEC_MAKE_F00_0C(vref_ctrl) (((vref_ctrl) & 0xFF) << 8) +#define CODEC_F00_0C_CAP_HBR RT_BIT(27) +#define CODEC_F00_0C_CAP_DP RT_BIT(24) +#define CODEC_F00_0C_CAP_EAPD RT_BIT(16) +#define CODEC_F00_0C_CAP_HDMI RT_BIT(7) +#define CODEC_F00_0C_CAP_BALANCED_IO RT_BIT(6) +#define CODEC_F00_0C_CAP_INPUT RT_BIT(5) +#define CODEC_F00_0C_CAP_OUTPUT RT_BIT(4) +#define CODEC_F00_0C_CAP_HEADPHONE_AMP RT_BIT(3) +#define CODEC_F00_0C_CAP_PRESENCE_DETECT RT_BIT(2) +#define CODEC_F00_0C_CAP_TRIGGER_REQUIRED RT_BIT(1) +#define CODEC_F00_0C_CAP_IMPENDANCE_SENSE RT_BIT(0) + +#define CODEC_F00_0C_IS_CAP_HBR(f00_0c) ((f00_0c) & RT_BIT(27)) +#define CODEC_F00_0C_IS_CAP_DP(f00_0c) ((f00_0c) & RT_BIT(24)) +#define CODEC_F00_0C_IS_CAP_EAPD(f00_0c) ((f00_0c) & RT_BIT(16)) +#define CODEC_F00_0C_IS_CAP_HDMI(f00_0c) ((f00_0c) & RT_BIT(7)) +#define CODEC_F00_0C_IS_CAP_BALANCED_IO(f00_0c) ((f00_0c) & RT_BIT(6)) +#define CODEC_F00_0C_IS_CAP_INPUT(f00_0c) ((f00_0c) & RT_BIT(5)) +#define CODEC_F00_0C_IS_CAP_OUTPUT(f00_0c) ((f00_0c) & RT_BIT(4)) +#define CODEC_F00_0C_IS_CAP_HP(f00_0c) ((f00_0c) & RT_BIT(3)) +#define CODEC_F00_0C_IS_CAP_PRESENCE_DETECT(f00_0c) ((f00_0c) & RT_BIT(2)) +#define CODEC_F00_0C_IS_CAP_TRIGGER_REQUIRED(f00_0c) ((f00_0c) & RT_BIT(1)) +#define CODEC_F00_0C_IS_CAP_IMPENDANCE_SENSE(f00_0c) ((f00_0c) & RT_BIT(0)) + +/* Input Amplifier capabilities (7.3.4.10). */ +#define CODEC_MAKE_F00_0D(mute_cap, step_size, num_steps, offset) \ + ( (((mute_cap) & UINT32_C(0x1)) << 31) \ + | (((step_size) & UINT32_C(0xFF)) << 16) \ + | (((num_steps) & UINT32_C(0xFF)) << 8) \ + | ((offset) & UINT32_C(0xFF))) + +#define CODEC_F00_0D_CAP_MUTE RT_BIT(7) + +#define CODEC_F00_0D_IS_CAP_MUTE(f00_0d) ( ( f00_0d) & RT_BIT(31)) +#define CODEC_F00_0D_STEP_SIZE(f00_0d) ((( f00_0d) & (0x7F << 16)) >> 16) +#define CODEC_F00_0D_NUM_STEPS(f00_0d) ((((f00_0d) & (0x7F << 8)) >> 8) + 1) +#define CODEC_F00_0D_OFFSET(f00_0d) ( (f00_0d) & 0x7F) + +/** Indicates that the amplifier can be muted. */ +#define CODEC_AMP_CAP_MUTE 0x1 +/** The amplifier's maximum number of steps. We want + * a ~90dB dynamic range, so 64 steps with 1.25dB each + * should do the trick. + * + * As we want to map our range to [0..128] values we can avoid + * multiplication and simply doing a shift later. + * + * Produces -96dB to +0dB. + * "0" indicates a step of 0.25dB, "127" indicates a step of 32dB. + */ +#define CODEC_AMP_NUM_STEPS 0x7F +/** The initial gain offset (and when doing a node reset). */ +#define CODEC_AMP_OFF_INITIAL 0x7F +/** The amplifier's gain step size. */ +#define CODEC_AMP_STEP_SIZE 0x2 + +/* Output Amplifier capabilities (7.3.4.10) */ +#define CODEC_MAKE_F00_12 CODEC_MAKE_F00_0D + +#define CODEC_F00_12_IS_CAP_MUTE(f00_12) CODEC_F00_0D_IS_CAP_MUTE(f00_12) +#define CODEC_F00_12_STEP_SIZE(f00_12) CODEC_F00_0D_STEP_SIZE(f00_12) +#define CODEC_F00_12_NUM_STEPS(f00_12) CODEC_F00_0D_NUM_STEPS(f00_12) +#define CODEC_F00_12_OFFSET(f00_12) CODEC_F00_0D_OFFSET(f00_12) + +/* Connection list lenght (7.3.4.11). */ +#define CODEC_MAKE_F00_0E(long_form, length) \ + ( (((long_form) & 0x1) << 7) \ + | ((length) & 0x7F)) +/* Indicates short-form NIDs. */ +#define CODEC_F00_0E_LIST_NID_SHORT 0 +/* Indicates long-form NIDs. */ +#define CODEC_F00_0E_LIST_NID_LONG 1 +#define CODEC_F00_0E_IS_LONG(f00_0e) RT_BOOL((f00_0e) & RT_BIT(7)) +#define CODEC_F00_0E_COUNT(f00_0e) ((f00_0e) & 0x7F) +/* Supported Power States (7.3.4.12) */ +#define CODEC_F00_0F_EPSS RT_BIT(31) +#define CODEC_F00_0F_CLKSTOP RT_BIT(30) +#define CODEC_F00_0F_S3D3 RT_BIT(29) +#define CODEC_F00_0F_D3COLD RT_BIT(4) +#define CODEC_F00_0F_D3 RT_BIT(3) +#define CODEC_F00_0F_D2 RT_BIT(2) +#define CODEC_F00_0F_D1 RT_BIT(1) +#define CODEC_F00_0F_D0 RT_BIT(0) + +/* Processing capabilities 7.3.4.13 */ +#define CODEC_MAKE_F00_10(num, benign) ((((num) & 0xFF) << 8) | ((benign) & 0x1)) +#define CODEC_F00_10_NUM(f00_10) (((f00_10) & (0xFF << 8)) >> 8) +#define CODEC_F00_10_BENING(f00_10) ((f00_10) & 0x1) + +/* GPIO count (7.3.4.14). */ +#define CODEC_MAKE_F00_11(wake, unsol, numgpi, numgpo, numgpio) \ + ( (((wake) & UINT32_C(0x1)) << 31) \ + | (((unsol) & UINT32_C(0x1)) << 30) \ + | (((numgpi) & UINT32_C(0xFF)) << 16) \ + | (((numgpo) & UINT32_C(0xFF)) << 8) \ + | ((numgpio) & UINT32_C(0xFF))) + +/* Processing States (7.3.3.4). */ +#define CODEC_F03_OFF (0) +#define CODEC_F03_ON RT_BIT(0) +#define CODEC_F03_BENING RT_BIT(1) +/* Power States (7.3.3.10). */ +#define CODEC_MAKE_F05(reset, stopok, error, act, set) \ + ( (((reset) & 0x1) << 10) \ + | (((stopok) & 0x1) << 9) \ + | (((error) & 0x1) << 8) \ + | (((act) & 0xF) << 4) \ + | ((set) & 0xF)) +#define CODEC_F05_D3COLD (4) +#define CODEC_F05_D3 (3) +#define CODEC_F05_D2 (2) +#define CODEC_F05_D1 (1) +#define CODEC_F05_D0 (0) + +#define CODEC_F05_IS_RESET(value) (((value) & RT_BIT(10)) != 0) +#define CODEC_F05_IS_STOPOK(value) (((value) & RT_BIT(9)) != 0) +#define CODEC_F05_IS_ERROR(value) (((value) & RT_BIT(8)) != 0) +#define CODEC_F05_ACT(value) (((value) & 0xF0) >> 4) +#define CODEC_F05_SET(value) (((value) & 0xF)) + +#define CODEC_F05_GE(p0, p1) ((p0) <= (p1)) +#define CODEC_F05_LE(p0, p1) ((p0) >= (p1)) + +/* Converter Stream, Channel (7.3.3.11). */ +#define CODEC_MAKE_F06(stream, channel) \ + ( (((stream) & 0xF) << 4) \ + | ((channel) & 0xF)) +#define CODEC_F06_STREAM(value) ((value) & 0xF0) +#define CODEC_F06_CHANNEL(value) ((value) & 0xF) + +/* Pin Widged Control (7.3.3.13). */ +#define CODEC_F07_VREF_HIZ (0) +#define CODEC_F07_VREF_50 (0x1) +#define CODEC_F07_VREF_GROUND (0x2) +#define CODEC_F07_VREF_80 (0x4) +#define CODEC_F07_VREF_100 (0x5) +#define CODEC_F07_IN_ENABLE RT_BIT(5) +#define CODEC_F07_OUT_ENABLE RT_BIT(6) +#define CODEC_F07_OUT_H_ENABLE RT_BIT(7) + +/* Volume Knob Control (7.3.3.29). */ +#define CODEC_F0F_IS_DIRECT RT_BIT(7) +#define CODEC_F0F_VOLUME (0x7F) + +/* Unsolicited enabled (7.3.3.14). */ +#define CODEC_MAKE_F08(enable, tag) ((((enable) & 1) << 7) | ((tag) & 0x3F)) + +/* Converter formats (7.3.3.8) and (3.7.1). */ +/* This is the same format as SDnFMT. */ +#define CODEC_MAKE_A HDA_SDFMT_MAKE + +#define CODEC_A_TYPE HDA_SDFMT_TYPE +#define CODEC_A_TYPE_PCM HDA_SDFMT_TYPE_PCM +#define CODEC_A_TYPE_NON_PCM HDA_SDFMT_TYPE_NON_PCM + +#define CODEC_A_BASE HDA_SDFMT_BASE +#define CODEC_A_BASE_48KHZ HDA_SDFMT_BASE_48KHZ +#define CODEC_A_BASE_44KHZ HDA_SDFMT_BASE_44KHZ + +/* Pin Sense (7.3.3.15). */ +#define CODEC_MAKE_F09_ANALOG(fPresent, impedance) \ +( (((fPresent) & 0x1) << 31) \ + | (((impedance) & UINT32_C(0x7FFFFFFF)))) +#define CODEC_F09_ANALOG_NA UINT32_C(0x7FFFFFFF) +#define CODEC_MAKE_F09_DIGITAL(fPresent, fELDValid) \ +( (((fPresent) & UINT32_C(0x1)) << 31) \ + | (((fELDValid) & UINT32_C(0x1)) << 30)) + +#define CODEC_MAKE_F0C(lrswap, eapd, btl) ((((lrswap) & 1) << 2) | (((eapd) & 1) << 1) | ((btl) & 1)) +#define CODEC_FOC_IS_LRSWAP(f0c) RT_BOOL((f0c) & RT_BIT(2)) +#define CODEC_FOC_IS_EAPD(f0c) RT_BOOL((f0c) & RT_BIT(1)) +#define CODEC_FOC_IS_BTL(f0c) RT_BOOL((f0c) & RT_BIT(0)) +/* HDA spec 7.3.3.31 defines layout of configuration registers/verbs (0xF1C) */ +/* Configuration's port connection */ +#define CODEC_F1C_PORT_MASK (0x3) +#define CODEC_F1C_PORT_SHIFT (30) + +#define CODEC_F1C_PORT_COMPLEX (0x0) +#define CODEC_F1C_PORT_NO_PHYS (0x1) +#define CODEC_F1C_PORT_FIXED (0x2) +#define CODEC_F1C_BOTH (0x3) + +/* Configuration default: connection */ +#define CODEC_F1C_PORT_MASK (0x3) +#define CODEC_F1C_PORT_SHIFT (30) + +/* Connected to a jack (1/8", ATAPI, ...). */ +#define CODEC_F1C_PORT_COMPLEX (0x0) +/* No physical connection. */ +#define CODEC_F1C_PORT_NO_PHYS (0x1) +/* Fixed function device (integrated speaker, integrated mic, ...). */ +#define CODEC_F1C_PORT_FIXED (0x2) +/* Both, a jack and an internal device are attached. */ +#define CODEC_F1C_BOTH (0x3) + +/* Configuration default: Location */ +#define CODEC_F1C_LOCATION_MASK (0x3F) +#define CODEC_F1C_LOCATION_SHIFT (24) + +/* [4:5] bits of location region means chassis attachment */ +#define CODEC_F1C_LOCATION_PRIMARY_CHASSIS (0) +#define CODEC_F1C_LOCATION_INTERNAL RT_BIT(4) +#define CODEC_F1C_LOCATION_SECONDRARY_CHASSIS RT_BIT(5) +#define CODEC_F1C_LOCATION_OTHER RT_BIT(5) + +/* [0:3] bits of location region means geometry location attachment */ +#define CODEC_F1C_LOCATION_NA (0) +#define CODEC_F1C_LOCATION_REAR (0x1) +#define CODEC_F1C_LOCATION_FRONT (0x2) +#define CODEC_F1C_LOCATION_LEFT (0x3) +#define CODEC_F1C_LOCATION_RIGTH (0x4) +#define CODEC_F1C_LOCATION_TOP (0x5) +#define CODEC_F1C_LOCATION_BOTTOM (0x6) +#define CODEC_F1C_LOCATION_SPECIAL_0 (0x7) +#define CODEC_F1C_LOCATION_SPECIAL_1 (0x8) +#define CODEC_F1C_LOCATION_SPECIAL_2 (0x9) + +/* Configuration default: Device type */ +#define CODEC_F1C_DEVICE_MASK (0xF) +#define CODEC_F1C_DEVICE_SHIFT (20) +#define CODEC_F1C_DEVICE_LINE_OUT (0) +#define CODEC_F1C_DEVICE_SPEAKER (0x1) +#define CODEC_F1C_DEVICE_HP (0x2) +#define CODEC_F1C_DEVICE_CD (0x3) +#define CODEC_F1C_DEVICE_SPDIF_OUT (0x4) +#define CODEC_F1C_DEVICE_DIGITAL_OTHER_OUT (0x5) +#define CODEC_F1C_DEVICE_MODEM_LINE_SIDE (0x6) +#define CODEC_F1C_DEVICE_MODEM_HANDSET_SIDE (0x7) +#define CODEC_F1C_DEVICE_LINE_IN (0x8) +#define CODEC_F1C_DEVICE_AUX (0x9) +#define CODEC_F1C_DEVICE_MIC (0xA) +#define CODEC_F1C_DEVICE_PHONE (0xB) +#define CODEC_F1C_DEVICE_SPDIF_IN (0xC) +#define CODEC_F1C_DEVICE_RESERVED (0xE) +#define CODEC_F1C_DEVICE_OTHER (0xF) + +/* Configuration default: Connection type */ +#define CODEC_F1C_CONNECTION_TYPE_MASK (0xF) +#define CODEC_F1C_CONNECTION_TYPE_SHIFT (16) + +#define CODEC_F1C_CONNECTION_TYPE_UNKNOWN (0) +#define CODEC_F1C_CONNECTION_TYPE_1_8INCHES (0x1) +#define CODEC_F1C_CONNECTION_TYPE_1_4INCHES (0x2) +#define CODEC_F1C_CONNECTION_TYPE_ATAPI (0x3) +#define CODEC_F1C_CONNECTION_TYPE_RCA (0x4) +#define CODEC_F1C_CONNECTION_TYPE_OPTICAL (0x5) +#define CODEC_F1C_CONNECTION_TYPE_OTHER_DIGITAL (0x6) +#define CODEC_F1C_CONNECTION_TYPE_ANALOG (0x7) +#define CODEC_F1C_CONNECTION_TYPE_DIN (0x8) +#define CODEC_F1C_CONNECTION_TYPE_XLR (0x9) +#define CODEC_F1C_CONNECTION_TYPE_RJ_11 (0xA) +#define CODEC_F1C_CONNECTION_TYPE_COMBO (0xB) +#define CODEC_F1C_CONNECTION_TYPE_OTHER (0xF) + +/* Configuration's color */ +#define CODEC_F1C_COLOR_MASK (0xF) +#define CODEC_F1C_COLOR_SHIFT (12) +#define CODEC_F1C_COLOR_UNKNOWN (0) +#define CODEC_F1C_COLOR_BLACK (0x1) +#define CODEC_F1C_COLOR_GREY (0x2) +#define CODEC_F1C_COLOR_BLUE (0x3) +#define CODEC_F1C_COLOR_GREEN (0x4) +#define CODEC_F1C_COLOR_RED (0x5) +#define CODEC_F1C_COLOR_ORANGE (0x6) +#define CODEC_F1C_COLOR_YELLOW (0x7) +#define CODEC_F1C_COLOR_PURPLE (0x8) +#define CODEC_F1C_COLOR_PINK (0x9) +#define CODEC_F1C_COLOR_RESERVED_0 (0xA) +#define CODEC_F1C_COLOR_RESERVED_1 (0xB) +#define CODEC_F1C_COLOR_RESERVED_2 (0xC) +#define CODEC_F1C_COLOR_RESERVED_3 (0xD) +#define CODEC_F1C_COLOR_WHITE (0xE) +#define CODEC_F1C_COLOR_OTHER (0xF) + +/* Configuration's misc */ +#define CODEC_F1C_MISC_MASK (0xF) +#define CODEC_F1C_MISC_SHIFT (8) +#define CODEC_F1C_MISC_NONE 0 +#define CODEC_F1C_MISC_JACK_NO_PRESENCE_DETECT RT_BIT(0) +#define CODEC_F1C_MISC_RESERVED_0 RT_BIT(1) +#define CODEC_F1C_MISC_RESERVED_1 RT_BIT(2) +#define CODEC_F1C_MISC_RESERVED_2 RT_BIT(3) + +/* Configuration default: Association */ +#define CODEC_F1C_ASSOCIATION_MASK (0xF) +#define CODEC_F1C_ASSOCIATION_SHIFT (4) + +/** Reserved; don't use. */ +#define CODEC_F1C_ASSOCIATION_INVALID 0x0 +#define CODEC_F1C_ASSOCIATION_GROUP_0 0x1 +#define CODEC_F1C_ASSOCIATION_GROUP_1 0x2 +#define CODEC_F1C_ASSOCIATION_GROUP_2 0x3 +#define CODEC_F1C_ASSOCIATION_GROUP_3 0x4 +#define CODEC_F1C_ASSOCIATION_GROUP_4 0x5 +#define CODEC_F1C_ASSOCIATION_GROUP_5 0x6 +#define CODEC_F1C_ASSOCIATION_GROUP_6 0x7 +#define CODEC_F1C_ASSOCIATION_GROUP_7 0x8 +/* Note: Windows OSes will treat group 15 (0xF) as single PIN devices. + * The sequence number associated with that group then will be ignored. */ +#define CODEC_F1C_ASSOCIATION_GROUP_15 0xF + +/* Configuration default: Association Sequence. */ +#define CODEC_F1C_SEQ_MASK (0xF) +#define CODEC_F1C_SEQ_SHIFT (0) + +/* Implementation identification (7.3.3.30). */ +#define CODEC_MAKE_F20(bmid, bsku, aid) \ + ( (((bmid) & 0xFFFF) << 16) \ + | (((bsku) & 0xFF) << 8) \ + | (((aid) & 0xFF)) \ + ) + +/* Macro definition helping in filling the configuration registers. */ +#define CODEC_MAKE_F1C(port_connectivity, location, device, connection_type, color, misc, association, sequence) \ + ( (((port_connectivity) & 0xF) << CODEC_F1C_PORT_SHIFT) \ + | (((location) & 0xF) << CODEC_F1C_LOCATION_SHIFT) \ + | (((device) & 0xF) << CODEC_F1C_DEVICE_SHIFT) \ + | (((connection_type) & 0xF) << CODEC_F1C_CONNECTION_TYPE_SHIFT) \ + | (((color) & 0xF) << CODEC_F1C_COLOR_SHIFT) \ + | (((misc) & 0xF) << CODEC_F1C_MISC_SHIFT) \ + | (((association) & 0xF) << CODEC_F1C_ASSOCIATION_SHIFT) \ + | (((sequence) & 0xF))) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** The F00 parameter length (in dwords). */ +#define CODECNODE_F00_PARAM_LENGTH 20 +/** The F02 parameter length (in dwords). */ +#define CODECNODE_F02_PARAM_LENGTH 16 + +/** + * Common (or core) codec node structure. + */ +typedef struct CODECCOMMONNODE +{ + /** The node's ID. */ + uint8_t uID; + /** The node's name. */ + char const *pszName; + /** The SDn ID this node is assigned to. + * 0 means not assigned, 1 is SDn0. */ + uint8_t uSD; + /** The SDn's channel to use. + * Only valid if a valid SDn ID is set. */ + uint8_t uChannel; + /* PRM 5.3.6 */ + uint32_t au32F00_param[CODECNODE_F00_PARAM_LENGTH]; + uint32_t au32F02_param[CODECNODE_F02_PARAM_LENGTH]; +} CODECCOMMONNODE; +typedef CODECCOMMONNODE *PCODECCOMMONNODE; +AssertCompile(CODECNODE_F00_PARAM_LENGTH == 20); /* saved state */ +AssertCompile(CODECNODE_F02_PARAM_LENGTH == 16); /* saved state */ + +/** + * Compile time assertion on the expected node size. + */ +#define AssertNodeSize(a_Node, a_cParams) \ + AssertCompile((a_cParams) <= (60 + 6)); /* the max size - saved state */ \ + AssertCompile( sizeof(a_Node) - sizeof(CODECCOMMONNODE) \ + == (((a_cParams) * sizeof(uint32_t) + sizeof(void *) - 1) & ~(sizeof(void *) - 1)) ) + +typedef struct ROOTCODECNODE +{ + CODECCOMMONNODE node; +} ROOTCODECNODE, *PROOTCODECNODE; +AssertNodeSize(ROOTCODECNODE, 0); + +#define AMPLIFIER_SIZE 60 +typedef uint32_t AMPLIFIER[AMPLIFIER_SIZE]; +#define AMPLIFIER_IN 0 +#define AMPLIFIER_OUT 1 +#define AMPLIFIER_LEFT 1 +#define AMPLIFIER_RIGHT 0 +#define AMPLIFIER_REGISTER(amp, inout, side, index) ((amp)[30*(inout) + 15*(side) + (index)]) +typedef struct DACNODE +{ + CODECCOMMONNODE node; + uint32_t u32F0d_param; + uint32_t u32F04_param; + uint32_t u32F05_param; + uint32_t u32F06_param; + uint32_t u32F0c_param; + + uint32_t u32A_param; + AMPLIFIER B_params; + +} DACNODE, *PDACNODE; +AssertNodeSize(DACNODE, 6 + 60); + +typedef struct ADCNODE +{ + CODECCOMMONNODE node; + uint32_t u32F01_param; + uint32_t u32F03_param; + uint32_t u32F05_param; + uint32_t u32F06_param; + uint32_t u32F09_param; + + uint32_t u32A_param; + AMPLIFIER B_params; +} ADCNODE, *PADCNODE; +AssertNodeSize(DACNODE, 6 + 60); + +typedef struct SPDIFOUTNODE +{ + CODECCOMMONNODE node; + uint32_t u32F05_param; + uint32_t u32F06_param; + uint32_t u32F09_param; + uint32_t u32F0d_param; + + uint32_t u32A_param; + AMPLIFIER B_params; +} SPDIFOUTNODE, *PSPDIFOUTNODE; +AssertNodeSize(SPDIFOUTNODE, 5 + 60); + +typedef struct SPDIFINNODE +{ + CODECCOMMONNODE node; + uint32_t u32F05_param; + uint32_t u32F06_param; + uint32_t u32F09_param; + uint32_t u32F0d_param; + + uint32_t u32A_param; + AMPLIFIER B_params; +} SPDIFINNODE, *PSPDIFINNODE; +AssertNodeSize(SPDIFINNODE, 5 + 60); + +typedef struct AFGCODECNODE +{ + CODECCOMMONNODE node; + uint32_t u32F05_param; + uint32_t u32F08_param; + uint32_t u32F17_param; + uint32_t u32F20_param; +} AFGCODECNODE, *PAFGCODECNODE; +AssertNodeSize(AFGCODECNODE, 4); + +typedef struct PORTNODE +{ + CODECCOMMONNODE node; + uint32_t u32F01_param; + uint32_t u32F07_param; + uint32_t u32F08_param; + uint32_t u32F09_param; + uint32_t u32F1c_param; + AMPLIFIER B_params; +} PORTNODE, *PPORTNODE; +AssertNodeSize(PORTNODE, 5 + 60); + +typedef struct DIGOUTNODE +{ + CODECCOMMONNODE node; + uint32_t u32F01_param; + uint32_t u32F05_param; + uint32_t u32F07_param; + uint32_t u32F08_param; + uint32_t u32F09_param; + uint32_t u32F1c_param; +} DIGOUTNODE, *PDIGOUTNODE; +AssertNodeSize(DIGOUTNODE, 6); + +typedef struct DIGINNODE +{ + CODECCOMMONNODE node; + uint32_t u32F05_param; + uint32_t u32F07_param; + uint32_t u32F08_param; + uint32_t u32F09_param; + uint32_t u32F0c_param; + uint32_t u32F1c_param; + uint32_t u32F1e_param; +} DIGINNODE, *PDIGINNODE; +AssertNodeSize(DIGINNODE, 7); + +typedef struct ADCMUXNODE +{ + CODECCOMMONNODE node; + uint32_t u32F01_param; + + uint32_t u32A_param; + AMPLIFIER B_params; +} ADCMUXNODE, *PADCMUXNODE; +AssertNodeSize(ADCMUXNODE, 2 + 60); + +typedef struct PCBEEPNODE +{ + CODECCOMMONNODE node; + uint32_t u32F07_param; + uint32_t u32F0a_param; + + uint32_t u32A_param; + AMPLIFIER B_params; + uint32_t u32F1c_param; +} PCBEEPNODE, *PPCBEEPNODE; +AssertNodeSize(PCBEEPNODE, 3 + 60 + 1); + +typedef struct CDNODE +{ + CODECCOMMONNODE node; + uint32_t u32F07_param; + uint32_t u32F1c_param; +} CDNODE, *PCDNODE; +AssertNodeSize(CDNODE, 2); + +typedef struct VOLUMEKNOBNODE +{ + CODECCOMMONNODE node; + uint32_t u32F08_param; + uint32_t u32F0f_param; +} VOLUMEKNOBNODE, *PVOLUMEKNOBNODE; +AssertNodeSize(VOLUMEKNOBNODE, 2); + +typedef struct ADCVOLNODE +{ + CODECCOMMONNODE node; + uint32_t u32F0c_param; + uint32_t u32F01_param; + uint32_t u32A_params; + AMPLIFIER B_params; +} ADCVOLNODE, *PADCVOLNODE; +AssertNodeSize(ADCVOLNODE, 3 + 60); + +typedef struct RESNODE +{ + CODECCOMMONNODE node; + uint32_t u32F05_param; + uint32_t u32F06_param; + uint32_t u32F07_param; + uint32_t u32F1c_param; + + uint32_t u32A_param; +} RESNODE, *PRESNODE; +AssertNodeSize(RESNODE, 5); + +/** + * Used for the saved state. + */ +typedef struct CODECSAVEDSTATENODE +{ + CODECCOMMONNODE Core; + uint32_t au32Params[60 + 6]; +} CODECSAVEDSTATENODE; +AssertNodeSize(CODECSAVEDSTATENODE, 60 + 6); + +typedef union CODECNODE +{ + CODECCOMMONNODE node; + ROOTCODECNODE root; + AFGCODECNODE afg; + DACNODE dac; + ADCNODE adc; + SPDIFOUTNODE spdifout; + SPDIFINNODE spdifin; + PORTNODE port; + DIGOUTNODE digout; + DIGINNODE digin; + ADCMUXNODE adcmux; + PCBEEPNODE pcbeep; + CDNODE cdnode; + VOLUMEKNOBNODE volumeKnob; + ADCVOLNODE adcvol; + RESNODE reserved; + CODECSAVEDSTATENODE SavedState; +} CODECNODE, *PCODECNODE; +AssertNodeSize(CODECNODE, 60 + 6); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/* STAC9220 - Nodes IDs / names. */ +#define STAC9220_NID_ROOT 0x0 /* Root node */ +#define STAC9220_NID_AFG 0x1 /* Audio Configuration Group */ +#define STAC9220_NID_DAC0 0x2 /* Out */ +#define STAC9220_NID_DAC1 0x3 /* Out */ +#define STAC9220_NID_DAC2 0x4 /* Out */ +#define STAC9220_NID_DAC3 0x5 /* Out */ +#define STAC9220_NID_ADC0 0x6 /* In */ +#define STAC9220_NID_ADC1 0x7 /* In */ +#define STAC9220_NID_SPDIF_OUT 0x8 /* Out */ +#define STAC9220_NID_SPDIF_IN 0x9 /* In */ +/** Also known as PIN_A. */ +#define STAC9220_NID_PIN_HEADPHONE0 0xA /* In, Out */ +#define STAC9220_NID_PIN_B 0xB /* In, Out */ +#define STAC9220_NID_PIN_C 0xC /* In, Out */ +/** Also known as PIN D. */ +#define STAC9220_NID_PIN_HEADPHONE1 0xD /* In, Out */ +#define STAC9220_NID_PIN_E 0xE /* In */ +#define STAC9220_NID_PIN_F 0xF /* In, Out */ +/** Also known as DIGOUT0. */ +#define STAC9220_NID_PIN_SPDIF_OUT 0x10 /* Out */ +/** Also known as DIGIN. */ +#define STAC9220_NID_PIN_SPDIF_IN 0x11 /* In */ +#define STAC9220_NID_ADC0_MUX 0x12 /* In */ +#define STAC9220_NID_ADC1_MUX 0x13 /* In */ +#define STAC9220_NID_PCBEEP 0x14 /* Out */ +#define STAC9220_NID_PIN_CD 0x15 /* In */ +#define STAC9220_NID_VOL_KNOB 0x16 +#define STAC9220_NID_AMP_ADC0 0x17 /* In */ +#define STAC9220_NID_AMP_ADC1 0x18 /* In */ +/* Only for STAC9221. */ +#define STAC9221_NID_ADAT_OUT 0x19 /* Out */ +#define STAC9221_NID_I2S_OUT 0x1A /* Out */ +#define STAC9221_NID_PIN_I2S_OUT 0x1B /* Out */ + +/** Number of total nodes emulated. */ +#define STAC9221_NUM_NODES 0x1C + +/* STAC9220 - Referenced through STAC9220WIDGET in the constructor below. */ +static uint8_t const g_abStac9220Ports[] = { STAC9220_NID_PIN_HEADPHONE0, STAC9220_NID_PIN_B, STAC9220_NID_PIN_C, STAC9220_NID_PIN_HEADPHONE1, STAC9220_NID_PIN_E, STAC9220_NID_PIN_F, 0 }; +static uint8_t const g_abStac9220Dacs[] = { STAC9220_NID_DAC0, STAC9220_NID_DAC1, STAC9220_NID_DAC2, STAC9220_NID_DAC3, 0 }; +static uint8_t const g_abStac9220Adcs[] = { STAC9220_NID_ADC0, STAC9220_NID_ADC1, 0 }; +static uint8_t const g_abStac9220SpdifOuts[] = { STAC9220_NID_SPDIF_OUT, 0 }; +static uint8_t const g_abStac9220SpdifIns[] = { STAC9220_NID_SPDIF_IN, 0 }; +static uint8_t const g_abStac9220DigOutPins[] = { STAC9220_NID_PIN_SPDIF_OUT, 0 }; +static uint8_t const g_abStac9220DigInPins[] = { STAC9220_NID_PIN_SPDIF_IN, 0 }; +static uint8_t const g_abStac9220AdcVols[] = { STAC9220_NID_AMP_ADC0, STAC9220_NID_AMP_ADC1, 0 }; +static uint8_t const g_abStac9220AdcMuxs[] = { STAC9220_NID_ADC0_MUX, STAC9220_NID_ADC1_MUX, 0 }; +static uint8_t const g_abStac9220Pcbeeps[] = { STAC9220_NID_PCBEEP, 0 }; +static uint8_t const g_abStac9220Cds[] = { STAC9220_NID_PIN_CD, 0 }; +static uint8_t const g_abStac9220VolKnobs[] = { STAC9220_NID_VOL_KNOB, 0 }; +/* STAC 9221. */ +/** @todo Is STAC9220_NID_SPDIF_IN really correct for reserved nodes? */ +static uint8_t const g_abStac9220Reserveds[] = { STAC9220_NID_SPDIF_IN, STAC9221_NID_ADAT_OUT, STAC9221_NID_I2S_OUT, STAC9221_NID_PIN_I2S_OUT, 0 }; + +/** SSM description of a CODECNODE. */ +static SSMFIELD const g_aCodecNodeFields[] = +{ + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.uID), + SSMFIELD_ENTRY_PAD_HC_AUTO(3, 3), + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F00_param), + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F02_param), + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, au32Params), + SSMFIELD_ENTRY_TERM() +}; + +/** Backward compatibility with v1 of the CODECNODE. */ +static SSMFIELD const g_aCodecNodeFieldsV1[] = +{ + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.uID), + SSMFIELD_ENTRY_PAD_HC_AUTO(3, 7), + SSMFIELD_ENTRY_OLD_HCPTR(Core.name), + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F00_param), + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F02_param), + SSMFIELD_ENTRY( CODECSAVEDSTATENODE, au32Params), + SSMFIELD_ENTRY_TERM() +}; + + + +#if 0 /* unused */ +static DECLCALLBACK(void) stac9220DbgNodes(PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF(pszArgs); + for (uint8_t i = 1; i < pThis->cTotalNodes; i++) + { + PCODECNODE pNode = &pThis->paNodes[i]; + AMPLIFIER *pAmp = &pNode->dac.B_params; + + uint8_t lVol = AMPLIFIER_REGISTER(*pAmp, AMPLIFIER_OUT, AMPLIFIER_LEFT, 0) & 0x7f; + uint8_t rVol = AMPLIFIER_REGISTER(*pAmp, AMPLIFIER_OUT, AMPLIFIER_RIGHT, 0) & 0x7f; + + pHlp->pfnPrintf(pHlp, "0x%x: lVol=%RU8, rVol=%RU8\n", i, lVol, rVol); + } +} +#endif + +/** + * Resets the codec with all its connected nodes. + * + * @param pThis HDA codec to reset. + */ +static DECLCALLBACK(void) stac9220Reset(PHDACODEC pThis) +{ + AssertPtrReturnVoid(pThis->paNodes); + AssertPtrReturnVoid(pThis->pfnNodeReset); + + LogRel(("HDA: Codec reset\n")); + + pThis->fInReset = true; + + for (uint8_t i = 0; i < pThis->cTotalNodes; i++) + pThis->pfnNodeReset(pThis, i, &pThis->paNodes[i]); + + pThis->fInReset = false; +} + +/** + * Resets a single node of the codec. + * + * @returns IPRT status code. + * @param pThis HDA codec of node to reset. + * @param uNID Node ID to set node to. + * @param pNode Node to reset. + */ +static DECLCALLBACK(int) stac9220ResetNode(PHDACODEC pThis, uint8_t uNID, PCODECNODE pNode) +{ + LogFlowFunc(("NID=0x%x (%RU8)\n", uNID, uNID)); + + if ( !pThis->fInReset + && ( uNID != STAC9220_NID_ROOT + && uNID != STAC9220_NID_AFG) + ) + { + RT_ZERO(pNode->node); + } + + /* Set common parameters across all nodes. */ + pNode->node.uID = uNID; + pNode->node.uSD = 0; + + switch (uNID) + { + /* Root node. */ + case STAC9220_NID_ROOT: + { + /* Set the revision ID. */ + pNode->root.node.au32F00_param[0x02] = CODEC_MAKE_F00_02(0x1, 0x0, 0x3, 0x4, 0x0, 0x1); + break; + } + + /* + * AFG (Audio Function Group). + */ + case STAC9220_NID_AFG: + { + pNode->afg.node.au32F00_param[0x08] = CODEC_MAKE_F00_08(1, 0xd, 0xd); + /* We set the AFG's PCM capabitilies fixed to 44.1kHz, 16-bit signed. */ + pNode->afg.node.au32F00_param[0x0A] = CODEC_F00_0A_44_1KHZ | CODEC_F00_0A_16_BIT; + pNode->afg.node.au32F00_param[0x0B] = CODEC_F00_0B_PCM; + pNode->afg.node.au32F00_param[0x0C] = CODEC_MAKE_F00_0C(0x17) + | CODEC_F00_0C_CAP_BALANCED_IO + | CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_OUTPUT + | CODEC_F00_0C_CAP_PRESENCE_DETECT + | CODEC_F00_0C_CAP_TRIGGER_REQUIRED + | CODEC_F00_0C_CAP_IMPENDANCE_SENSE; + + /* Default input amplifier capabilities. */ + pNode->node.au32F00_param[0x0D] = CODEC_MAKE_F00_0D(CODEC_AMP_CAP_MUTE, + CODEC_AMP_STEP_SIZE, + CODEC_AMP_NUM_STEPS, + CODEC_AMP_OFF_INITIAL); + /* Default output amplifier capabilities. */ + pNode->node.au32F00_param[0x12] = CODEC_MAKE_F00_12(CODEC_AMP_CAP_MUTE, + CODEC_AMP_STEP_SIZE, + CODEC_AMP_NUM_STEPS, + CODEC_AMP_OFF_INITIAL); + + pNode->afg.node.au32F00_param[0x11] = CODEC_MAKE_F00_11(1, 1, 0, 0, 4); + pNode->afg.node.au32F00_param[0x0F] = CODEC_F00_0F_D3 + | CODEC_F00_0F_D2 + | CODEC_F00_0F_D1 + | CODEC_F00_0F_D0; + + pNode->afg.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D2, CODEC_F05_D2); /* PS-Act: D2, PS->Set D2. */ + pNode->afg.u32F08_param = 0; + pNode->afg.u32F17_param = 0; + break; + } + + /* + * DACs. + */ + case STAC9220_NID_DAC0: /* DAC0: Headphones 0 + 1 */ + case STAC9220_NID_DAC1: /* DAC1: PIN C */ + case STAC9220_NID_DAC2: /* DAC2: PIN B */ + case STAC9220_NID_DAC3: /* DAC3: PIN F */ + { + pNode->dac.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ, + HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_1X, HDA_SDFMT_16_BIT, + HDA_SDFMT_CHAN_STEREO); + + /* 7.3.4.6: Audio widget capabilities. */ + pNode->dac.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_OUTPUT, 13, 0) + | CODEC_F00_09_CAP_L_R_SWAP + | CODEC_F00_09_CAP_POWER_CTRL + | CODEC_F00_09_CAP_OUT_AMP_PRESENT + | CODEC_F00_09_CAP_STEREO; + + /* Connection list; must be 0 if the only connection for the widget is + * to the High Definition Audio Link. */ + pNode->dac.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 0 /* Entries */); + + pNode->dac.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D3, CODEC_F05_D3); + + RT_ZERO(pNode->dac.B_params); + AMPLIFIER_REGISTER(pNode->dac.B_params, AMPLIFIER_OUT, AMPLIFIER_LEFT, 0) = 0x7F | RT_BIT(7); + AMPLIFIER_REGISTER(pNode->dac.B_params, AMPLIFIER_OUT, AMPLIFIER_RIGHT, 0) = 0x7F | RT_BIT(7); + break; + } + + /* + * ADCs. + */ + case STAC9220_NID_ADC0: /* Analog input. */ + { + pNode->node.au32F02_param[0] = STAC9220_NID_AMP_ADC0; + goto adc_init; + } + + case STAC9220_NID_ADC1: /* Analog input (CD). */ + { + pNode->node.au32F02_param[0] = STAC9220_NID_AMP_ADC1; + + /* Fall through is intentional. */ + adc_init: + + pNode->adc.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ, + HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_1X, HDA_SDFMT_16_BIT, + HDA_SDFMT_CHAN_STEREO); + + pNode->adc.u32F03_param = RT_BIT(0); + pNode->adc.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D3, CODEC_F05_D3); /* PS-Act: D3 Set: D3 */ + + pNode->adc.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_INPUT, 0xD, 0) + | CODEC_F00_09_CAP_POWER_CTRL + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_PROC_WIDGET + | CODEC_F00_09_CAP_STEREO; + /* Connection list entries. */ + pNode->adc.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); + break; + } + + /* + * SP/DIF In/Out. + */ + case STAC9220_NID_SPDIF_OUT: + { + pNode->spdifout.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ, + HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_1X, HDA_SDFMT_16_BIT, + HDA_SDFMT_CHAN_STEREO); + pNode->spdifout.u32F06_param = 0; + pNode->spdifout.u32F0d_param = 0; + + pNode->spdifout.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_OUTPUT, 4, 0) + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_FMT_OVERRIDE + | CODEC_F00_09_CAP_STEREO; + + /* Use a fixed format from AFG. */ + pNode->spdifout.node.au32F00_param[0xA] = pThis->paNodes[STAC9220_NID_AFG].node.au32F00_param[0xA]; + pNode->spdifout.node.au32F00_param[0xB] = CODEC_F00_0B_PCM; + break; + } + + case STAC9220_NID_SPDIF_IN: + { + pNode->spdifin.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ, + HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_1X, HDA_SDFMT_16_BIT, + HDA_SDFMT_CHAN_STEREO); + + pNode->spdifin.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_INPUT, 4, 0) + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_FMT_OVERRIDE + | CODEC_F00_09_CAP_STEREO; + + /* Use a fixed format from AFG. */ + pNode->spdifin.node.au32F00_param[0xA] = pThis->paNodes[STAC9220_NID_AFG].node.au32F00_param[0xA]; + pNode->spdifin.node.au32F00_param[0xB] = CODEC_F00_0B_PCM; + + /* Connection list entries. */ + pNode->spdifin.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); + pNode->spdifin.node.au32F02_param[0] = 0x11; + break; + } + + /* + * PINs / Ports. + */ + case STAC9220_NID_PIN_HEADPHONE0: /* Port A: Headphone in/out (front). */ + { + pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(0 /*fPresent*/, CODEC_F09_ANALOG_NA); + + pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17) + | CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_OUTPUT + | CODEC_F00_0C_CAP_HEADPHONE_AMP + | CODEC_F00_0C_CAP_PRESENCE_DETECT + | CODEC_F00_0C_CAP_TRIGGER_REQUIRED; + + /* Connection list entry 0: Goes to DAC0. */ + pNode->port.node.au32F02_param[0] = STAC9220_NID_DAC0; + + if (!pThis->fInReset) + pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_FRONT, + CODEC_F1C_DEVICE_HP, + CODEC_F1C_CONNECTION_TYPE_1_8INCHES, + CODEC_F1C_COLOR_GREEN, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_1, 0x0 /* Seq */); + goto port_init; + } + + case STAC9220_NID_PIN_B: /* Port B: Rear CLFE (Center / Subwoofer). */ + { + pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /*fPresent*/, CODEC_F09_ANALOG_NA); + + pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17) + | CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_OUTPUT + | CODEC_F00_0C_CAP_PRESENCE_DETECT + | CODEC_F00_0C_CAP_TRIGGER_REQUIRED; + + /* Connection list entry 0: Goes to DAC2. */ + pNode->port.node.au32F02_param[0] = STAC9220_NID_DAC2; + + if (!pThis->fInReset) + pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_REAR, + CODEC_F1C_DEVICE_SPEAKER, + CODEC_F1C_CONNECTION_TYPE_1_8INCHES, + CODEC_F1C_COLOR_BLACK, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_0, 0x1 /* Seq */); + goto port_init; + } + + case STAC9220_NID_PIN_C: /* Rear Speaker. */ + { + pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /*fPresent*/, CODEC_F09_ANALOG_NA); + + pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17) + | CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_OUTPUT + | CODEC_F00_0C_CAP_PRESENCE_DETECT + | CODEC_F00_0C_CAP_TRIGGER_REQUIRED; + + /* Connection list entry 0: Goes to DAC1. */ + pNode->port.node.au32F02_param[0x0] = STAC9220_NID_DAC1; + + if (!pThis->fInReset) + pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_REAR, + CODEC_F1C_DEVICE_SPEAKER, + CODEC_F1C_CONNECTION_TYPE_1_8INCHES, + CODEC_F1C_COLOR_GREEN, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_0, 0x0 /* Seq */); + goto port_init; + } + + case STAC9220_NID_PIN_HEADPHONE1: /* Also known as PIN_D. */ + { + pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /*fPresent*/, CODEC_F09_ANALOG_NA); + + pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17) + | CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_OUTPUT + | CODEC_F00_0C_CAP_HEADPHONE_AMP + | CODEC_F00_0C_CAP_PRESENCE_DETECT + | CODEC_F00_0C_CAP_TRIGGER_REQUIRED; + + /* Connection list entry 0: Goes to DAC1. */ + pNode->port.node.au32F02_param[0x0] = STAC9220_NID_DAC0; + + if (!pThis->fInReset) + pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_FRONT, + CODEC_F1C_DEVICE_MIC, + CODEC_F1C_CONNECTION_TYPE_1_8INCHES, + CODEC_F1C_COLOR_PINK, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_15, 0x0 /* Ignored */); + /* Fall through is intentional. */ + + port_init: + + pNode->port.u32F07_param = CODEC_F07_IN_ENABLE + | CODEC_F07_OUT_ENABLE; + pNode->port.u32F08_param = 0; + + pNode->port.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_UNSOL + | CODEC_F00_09_CAP_STEREO; + /* Connection list entries. */ + pNode->port.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); + break; + } + + case STAC9220_NID_PIN_E: + { + pNode->port.u32F07_param = CODEC_F07_IN_ENABLE; + pNode->port.u32F08_param = 0; + /* If Line in is reported as enabled, OS X sees no speakers! Windows does + * not care either way, although Linux does. + */ + pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(0 /* fPresent */, 0); + + pNode->port.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) + | CODEC_F00_09_CAP_UNSOL + | CODEC_F00_09_CAP_STEREO; + + pNode->port.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_PRESENCE_DETECT; + + if (!pThis->fInReset) + pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_REAR, + CODEC_F1C_DEVICE_LINE_IN, + CODEC_F1C_CONNECTION_TYPE_1_8INCHES, + CODEC_F1C_COLOR_BLUE, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_4, 0x1 /* Seq */); + break; + } + + case STAC9220_NID_PIN_F: + { + pNode->port.u32F07_param = CODEC_F07_IN_ENABLE | CODEC_F07_OUT_ENABLE; + pNode->port.u32F08_param = 0; + pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /* fPresent */, CODEC_F09_ANALOG_NA); + + pNode->port.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_UNSOL + | CODEC_F00_09_CAP_OUT_AMP_PRESENT + | CODEC_F00_09_CAP_STEREO; + + pNode->port.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_OUTPUT; + + /* Connection list entry 0: Goes to DAC3. */ + pNode->port.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); + pNode->port.node.au32F02_param[0x0] = STAC9220_NID_DAC3; + + if (!pThis->fInReset) + pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_INTERNAL, + CODEC_F1C_DEVICE_SPEAKER, + CODEC_F1C_CONNECTION_TYPE_1_8INCHES, + CODEC_F1C_COLOR_ORANGE, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_0, 0x2 /* Seq */); + break; + } + + case STAC9220_NID_PIN_SPDIF_OUT: /* Rear SPDIF Out. */ + { + pNode->digout.u32F07_param = CODEC_F07_OUT_ENABLE; + pNode->digout.u32F09_param = 0; + + pNode->digout.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_STEREO; + pNode->digout.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_OUTPUT; + + /* Connection list entries. */ + pNode->digout.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 3 /* Entries */); + pNode->digout.node.au32F02_param[0x0] = RT_MAKE_U32_FROM_U8(STAC9220_NID_SPDIF_OUT, + STAC9220_NID_AMP_ADC0, STAC9221_NID_ADAT_OUT, 0); + if (!pThis->fInReset) + pNode->digout.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_REAR, + CODEC_F1C_DEVICE_SPDIF_OUT, + CODEC_F1C_CONNECTION_TYPE_DIN, + CODEC_F1C_COLOR_BLACK, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_2, 0x0 /* Seq */); + break; + } + + case STAC9220_NID_PIN_SPDIF_IN: + { + pNode->digin.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D3, CODEC_F05_D3); /* PS-Act: D3 -> D3 */ + pNode->digin.u32F07_param = CODEC_F07_IN_ENABLE; + pNode->digin.u32F08_param = 0; + pNode->digin.u32F09_param = CODEC_MAKE_F09_DIGITAL(0, 0); + pNode->digin.u32F0c_param = 0; + + pNode->digin.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 3, 0) + | CODEC_F00_09_CAP_POWER_CTRL + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_UNSOL + | CODEC_F00_09_CAP_STEREO; + + pNode->digin.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_EAPD + | CODEC_F00_0C_CAP_INPUT + | CODEC_F00_0C_CAP_PRESENCE_DETECT; + if (!pThis->fInReset) + pNode->digin.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX, + CODEC_F1C_LOCATION_REAR, + CODEC_F1C_DEVICE_SPDIF_IN, + CODEC_F1C_CONNECTION_TYPE_OTHER_DIGITAL, + CODEC_F1C_COLOR_BLACK, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_5, 0x0 /* Seq */); + break; + } + + case STAC9220_NID_ADC0_MUX: + { + pNode->adcmux.u32F01_param = 0; /* Connection select control index (STAC9220_NID_PIN_E). */ + goto adcmux_init; + } + + case STAC9220_NID_ADC1_MUX: + { + pNode->adcmux.u32F01_param = 1; /* Connection select control index (STAC9220_NID_PIN_CD). */ + /* Fall through is intentional. */ + + adcmux_init: + + pNode->adcmux.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_SELECTOR, 0, 0) + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_AMP_FMT_OVERRIDE + | CODEC_F00_09_CAP_OUT_AMP_PRESENT + | CODEC_F00_09_CAP_STEREO; + + pNode->adcmux.node.au32F00_param[0xD] = CODEC_MAKE_F00_0D(0, 27, 4, 0); + + /* Connection list entries. */ + pNode->adcmux.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 7 /* Entries */); + pNode->adcmux.node.au32F02_param[0x0] = RT_MAKE_U32_FROM_U8(STAC9220_NID_PIN_E, + STAC9220_NID_PIN_CD, + STAC9220_NID_PIN_F, + STAC9220_NID_PIN_B); + pNode->adcmux.node.au32F02_param[0x4] = RT_MAKE_U32_FROM_U8(STAC9220_NID_PIN_C, + STAC9220_NID_PIN_HEADPHONE1, + STAC9220_NID_PIN_HEADPHONE0, + 0x0 /* Unused */); + + /* STAC 9220 v10 6.21-22.{4,5} both(left and right) out amplifiers initialized with 0. */ + RT_ZERO(pNode->adcmux.B_params); + break; + } + + case STAC9220_NID_PCBEEP: + { + pNode->pcbeep.u32F0a_param = 0; + + pNode->pcbeep.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_BEEP_GEN, 0, 0) + | CODEC_F00_09_CAP_AMP_FMT_OVERRIDE + | CODEC_F00_09_CAP_OUT_AMP_PRESENT; + pNode->pcbeep.node.au32F00_param[0xD] = CODEC_MAKE_F00_0D(0, 17, 3, 3); + + RT_ZERO(pNode->pcbeep.B_params); + break; + } + + case STAC9220_NID_PIN_CD: + { + pNode->cdnode.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) + | CODEC_F00_09_CAP_STEREO; + pNode->cdnode.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_INPUT; + + if (!pThis->fInReset) + pNode->cdnode.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_FIXED, + CODEC_F1C_LOCATION_INTERNAL, + CODEC_F1C_DEVICE_CD, + CODEC_F1C_CONNECTION_TYPE_ATAPI, + CODEC_F1C_COLOR_UNKNOWN, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_4, 0x2 /* Seq */); + break; + } + + case STAC9220_NID_VOL_KNOB: + { + pNode->volumeKnob.u32F08_param = 0; + pNode->volumeKnob.u32F0f_param = 0x7f; + + pNode->volumeKnob.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_VOLUME_KNOB, 0, 0); + pNode->volumeKnob.node.au32F00_param[0xD] = RT_BIT(7) | 0x7F; + + /* Connection list entries. */ + pNode->volumeKnob.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 4 /* Entries */); + pNode->volumeKnob.node.au32F02_param[0x0] = RT_MAKE_U32_FROM_U8(STAC9220_NID_DAC0, + STAC9220_NID_DAC1, + STAC9220_NID_DAC2, + STAC9220_NID_DAC3); + break; + } + + case STAC9220_NID_AMP_ADC0: /* ADC0Vol */ + { + pNode->adcvol.node.au32F02_param[0] = STAC9220_NID_ADC0_MUX; + goto adcvol_init; + } + + case STAC9220_NID_AMP_ADC1: /* ADC1Vol */ + { + pNode->adcvol.node.au32F02_param[0] = STAC9220_NID_ADC1_MUX; + /* Fall through is intentional. */ + + adcvol_init: + + pNode->adcvol.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_SELECTOR, 0, 0) + | CODEC_F00_09_CAP_L_R_SWAP + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_IN_AMP_PRESENT + | CODEC_F00_09_CAP_STEREO; + + + pNode->adcvol.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); + + RT_ZERO(pNode->adcvol.B_params); + AMPLIFIER_REGISTER(pNode->adcvol.B_params, AMPLIFIER_IN, AMPLIFIER_LEFT, 0) = RT_BIT(7); + AMPLIFIER_REGISTER(pNode->adcvol.B_params, AMPLIFIER_IN, AMPLIFIER_RIGHT, 0) = RT_BIT(7); + break; + } + + /* + * STAC9221 nodes. + */ + + case STAC9221_NID_ADAT_OUT: + { + pNode->node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_VENDOR_DEFINED, 3, 0) + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_STEREO; + break; + } + + case STAC9221_NID_I2S_OUT: + { + pNode->node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_OUTPUT, 3, 0) + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_STEREO; + break; + } + + case STAC9221_NID_PIN_I2S_OUT: + { + pNode->node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0) + | CODEC_F00_09_CAP_DIGITAL + | CODEC_F00_09_CAP_CONNECTION_LIST + | CODEC_F00_09_CAP_STEREO; + + pNode->node.au32F00_param[0xC] = CODEC_F00_0C_CAP_OUTPUT; + + /* Connection list entries. */ + pNode->node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */); + pNode->node.au32F02_param[0] = STAC9221_NID_I2S_OUT; + + if (!pThis->fInReset) + pNode->reserved.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_NO_PHYS, + CODEC_F1C_LOCATION_NA, + CODEC_F1C_DEVICE_LINE_OUT, + CODEC_F1C_CONNECTION_TYPE_UNKNOWN, + CODEC_F1C_COLOR_UNKNOWN, + CODEC_F1C_MISC_NONE, + CODEC_F1C_ASSOCIATION_GROUP_15, 0x0 /* Ignored */); + break; + } + + default: + AssertMsgFailed(("Node %RU8 not implemented\n", uNID)); + break; + } + + return VINF_SUCCESS; +} + +static int stac9220Construct(PHDACODEC pThis) +{ + unconst(pThis->cTotalNodes) = STAC9221_NUM_NODES; + + pThis->pfnReset = stac9220Reset; + pThis->pfnNodeReset = stac9220ResetNode; + + pThis->u16VendorId = 0x8384; /* SigmaTel */ + /* + * Note: The Linux kernel uses "patch_stac922x" for the fixups, + * which in turn uses "ref922x_pin_configs" for the configuration + * defaults tweaking in sound/pci/hda/patch_sigmatel.c. + */ + pThis->u16DeviceId = 0x7680; /* STAC9221 A1 */ + pThis->u8BSKU = 0x76; + pThis->u8AssemblyId = 0x80; + + pThis->paNodes = (PCODECNODE)RTMemAllocZ(sizeof(CODECNODE) * pThis->cTotalNodes); + if (!pThis->paNodes) + return VERR_NO_MEMORY; + + pThis->fInReset = false; + +#define STAC9220WIDGET(type) pThis->au8##type##s = g_abStac9220##type##s + STAC9220WIDGET(Port); + STAC9220WIDGET(Dac); + STAC9220WIDGET(Adc); + STAC9220WIDGET(AdcVol); + STAC9220WIDGET(AdcMux); + STAC9220WIDGET(Pcbeep); + STAC9220WIDGET(SpdifIn); + STAC9220WIDGET(SpdifOut); + STAC9220WIDGET(DigInPin); + STAC9220WIDGET(DigOutPin); + STAC9220WIDGET(Cd); + STAC9220WIDGET(VolKnob); + STAC9220WIDGET(Reserved); +#undef STAC9220WIDGET + + unconst(pThis->u8AdcVolsLineIn) = STAC9220_NID_AMP_ADC0; + unconst(pThis->u8DacLineOut) = STAC9220_NID_DAC1; + + /* + * Initialize all codec nodes. + * This is specific to the codec, so do this here. + * + * Note: Do *not* call stac9220Reset() here, as this would not + * initialize the node default configuration values then! + */ + AssertPtr(pThis->paNodes); + AssertPtr(pThis->pfnNodeReset); + + for (uint8_t i = 0; i < pThis->cTotalNodes; i++) + { + int rc2 = stac9220ResetNode(pThis, i, &pThis->paNodes[i]); + AssertRC(rc2); + } + + return VINF_SUCCESS; +} + + +/* + * Some generic predicate functions. + */ + +#define DECLISNODEOFTYPE(type) \ + DECLINLINE(bool) hdaCodecIs##type##Node(PHDACODEC pThis, uint8_t cNode) \ + { \ + Assert(pThis->au8##type##s); \ + for (int i = 0; pThis->au8##type##s[i] != 0; ++i) \ + if (pThis->au8##type##s[i] == cNode) \ + return true; \ + return false; \ + } +/* hdaCodecIsPortNode */ +DECLISNODEOFTYPE(Port) +/* hdaCodecIsDacNode */ +DECLISNODEOFTYPE(Dac) +/* hdaCodecIsAdcVolNode */ +DECLISNODEOFTYPE(AdcVol) +/* hdaCodecIsAdcNode */ +DECLISNODEOFTYPE(Adc) +/* hdaCodecIsAdcMuxNode */ +DECLISNODEOFTYPE(AdcMux) +/* hdaCodecIsPcbeepNode */ +DECLISNODEOFTYPE(Pcbeep) +/* hdaCodecIsSpdifOutNode */ +DECLISNODEOFTYPE(SpdifOut) +/* hdaCodecIsSpdifInNode */ +DECLISNODEOFTYPE(SpdifIn) +/* hdaCodecIsDigInPinNode */ +DECLISNODEOFTYPE(DigInPin) +/* hdaCodecIsDigOutPinNode */ +DECLISNODEOFTYPE(DigOutPin) +/* hdaCodecIsCdNode */ +DECLISNODEOFTYPE(Cd) +/* hdaCodecIsVolKnobNode */ +DECLISNODEOFTYPE(VolKnob) +/* hdaCodecIsReservedNode */ +DECLISNODEOFTYPE(Reserved) + + +/* + * Misc helpers. + */ +static int hdaCodecToAudVolume(PHDACODEC pThis, PCODECNODE pNode, AMPLIFIER *pAmp, PDMAUDIOMIXERCTL enmMixerCtl) +{ + RT_NOREF(pNode); + + uint8_t iDir; + switch (enmMixerCtl) + { + case PDMAUDIOMIXERCTL_VOLUME_MASTER: + case PDMAUDIOMIXERCTL_FRONT: + iDir = AMPLIFIER_OUT; + break; + case PDMAUDIOMIXERCTL_LINE_IN: + case PDMAUDIOMIXERCTL_MIC_IN: + iDir = AMPLIFIER_IN; + break; + default: + AssertMsgFailedReturn(("Invalid mixer control %RU32\n", enmMixerCtl), VERR_INVALID_PARAMETER); + break; + } + + int iMute; + iMute = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_LEFT, 0) & RT_BIT(7); + iMute |= AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_RIGHT, 0) & RT_BIT(7); + iMute >>=7; + iMute &= 0x1; + + uint8_t lVol = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_LEFT, 0) & 0x7f; + uint8_t rVol = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_RIGHT, 0) & 0x7f; + + /* + * The STAC9220 volume controls have 0 to -96dB attenuation range in 128 steps. + * We have 0 to -96dB range in 256 steps. HDA volume setting of 127 must map + * to 255 internally (0dB), while HDA volume setting of 0 (-96dB) should map + * to 1 (rather than zero) internally. + */ + lVol = (lVol + 1) * (2 * 255) / 256; + rVol = (rVol + 1) * (2 * 255) / 256; + + PDMAUDIOVOLUME Vol = { RT_BOOL(iMute), lVol, rVol }; + + LogFunc(("[NID0x%02x] %RU8/%RU8 (%s)\n", + pNode->node.uID, lVol, rVol, RT_BOOL(iMute) ? "Muted" : "Unmuted")); + + LogRel2(("HDA: Setting volume for mixer control '%s' to %RU8/%RU8 (%s)\n", + DrvAudioHlpAudMixerCtlToStr(enmMixerCtl), lVol, rVol, RT_BOOL(iMute) ? "Muted" : "Unmuted")); + + return pThis->pfnCbMixerSetVolume(pThis->pHDAState, enmMixerCtl, &Vol); +} + +DECLINLINE(void) hdaCodecSetRegister(uint32_t *pu32Reg, uint32_t u32Cmd, uint8_t u8Offset, uint32_t mask) +{ + Assert((pu32Reg && u8Offset < 32)); + *pu32Reg &= ~(mask << u8Offset); + *pu32Reg |= (u32Cmd & mask) << u8Offset; +} + +DECLINLINE(void) hdaCodecSetRegisterU8(uint32_t *pu32Reg, uint32_t u32Cmd, uint8_t u8Offset) +{ + hdaCodecSetRegister(pu32Reg, u32Cmd, u8Offset, CODEC_VERB_8BIT_DATA); +} + +DECLINLINE(void) hdaCodecSetRegisterU16(uint32_t *pu32Reg, uint32_t u32Cmd, uint8_t u8Offset) +{ + hdaCodecSetRegister(pu32Reg, u32Cmd, u8Offset, CODEC_VERB_16BIT_DATA); +} + + +/* + * Verb processor functions. + */ +#if 0 /* unused */ + +static DECLCALLBACK(int) vrbProcUnimplemented(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + RT_NOREF(pThis, cmd); + LogFlowFunc(("cmd(raw:%x: cad:%x, d:%c, nid:%x, verb:%x)\n", cmd, + CODEC_CAD(cmd), CODEC_DIRECT(cmd) ? 'N' : 'Y', CODEC_NID(cmd), CODEC_VERBDATA(cmd))); + *pResp = 0; + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) vrbProcBreak(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + int rc; + rc = vrbProcUnimplemented(pThis, cmd, pResp); + *pResp |= CODEC_RESPONSE_UNSOLICITED; + return rc; +} + +#endif /* unused */ + +/* B-- */ +static DECLCALLBACK(int) vrbProcGetAmplifier(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + /* HDA spec 7.3.3.7 Note A */ + /** @todo If index out of range response should be 0. */ + uint8_t u8Index = CODEC_GET_AMP_DIRECTION(cmd) == AMPLIFIER_OUT ? 0 : CODEC_GET_AMP_INDEX(cmd); + + PCODECNODE pNode = &pThis->paNodes[CODEC_NID(cmd)]; + if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + *pResp = AMPLIFIER_REGISTER(pNode->dac.B_params, + CODEC_GET_AMP_DIRECTION(cmd), + CODEC_GET_AMP_SIDE(cmd), + u8Index); + else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd))) + *pResp = AMPLIFIER_REGISTER(pNode->adcvol.B_params, + CODEC_GET_AMP_DIRECTION(cmd), + CODEC_GET_AMP_SIDE(cmd), + u8Index); + else if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(cmd))) + *pResp = AMPLIFIER_REGISTER(pNode->adcmux.B_params, + CODEC_GET_AMP_DIRECTION(cmd), + CODEC_GET_AMP_SIDE(cmd), + u8Index); + else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd))) + *pResp = AMPLIFIER_REGISTER(pNode->pcbeep.B_params, + CODEC_GET_AMP_DIRECTION(cmd), + CODEC_GET_AMP_SIDE(cmd), + u8Index); + else if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) + *pResp = AMPLIFIER_REGISTER(pNode->port.B_params, + CODEC_GET_AMP_DIRECTION(cmd), + CODEC_GET_AMP_SIDE(cmd), + u8Index); + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + *pResp = AMPLIFIER_REGISTER(pNode->adc.B_params, + CODEC_GET_AMP_DIRECTION(cmd), + CODEC_GET_AMP_SIDE(cmd), + u8Index); + else + LogRel2(("HDA: Warning: Unhandled get amplifier command: 0x%x (NID=0x%x [%RU8])\n", cmd, CODEC_NID(cmd), CODEC_NID(cmd))); + + return VINF_SUCCESS; +} + +/* 3-- */ +static DECLCALLBACK(int) vrbProcSetAmplifier(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + PCODECNODE pNode = &pThis->paNodes[CODEC_NID(cmd)]; + AMPLIFIER *pAmplifier = NULL; + if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + pAmplifier = &pNode->dac.B_params; + else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd))) + pAmplifier = &pNode->adcvol.B_params; + else if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(cmd))) + pAmplifier = &pNode->adcmux.B_params; + else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd))) + pAmplifier = &pNode->pcbeep.B_params; + else if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) + pAmplifier = &pNode->port.B_params; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + pAmplifier = &pNode->adc.B_params; + else + LogRel2(("HDA: Warning: Unhandled set amplifier command: 0x%x (Payload=%RU16, NID=0x%x [%RU8])\n", + cmd, CODEC_VERB_PAYLOAD16(cmd), CODEC_NID(cmd), CODEC_NID(cmd))); + + if (!pAmplifier) + return VINF_SUCCESS; + + bool fIsOut = CODEC_SET_AMP_IS_OUT_DIRECTION(cmd); + bool fIsIn = CODEC_SET_AMP_IS_IN_DIRECTION(cmd); + bool fIsLeft = CODEC_SET_AMP_IS_LEFT_SIDE(cmd); + bool fIsRight = CODEC_SET_AMP_IS_RIGHT_SIDE(cmd); + uint8_t u8Index = CODEC_SET_AMP_INDEX(cmd); + + if ( (!fIsLeft && !fIsRight) + || (!fIsOut && !fIsIn)) + return VINF_SUCCESS; + + LogFunc(("[NID0x%02x] fIsOut=%RTbool, fIsIn=%RTbool, fIsLeft=%RTbool, fIsRight=%RTbool, Idx=%RU8\n", + CODEC_NID(cmd), fIsOut, fIsIn, fIsLeft, fIsRight, u8Index)); + + if (fIsIn) + { + if (fIsLeft) + hdaCodecSetRegisterU8(&LIFIER_REGISTER(*pAmplifier, AMPLIFIER_IN, AMPLIFIER_LEFT, u8Index), cmd, 0); + if (fIsRight) + hdaCodecSetRegisterU8(&LIFIER_REGISTER(*pAmplifier, AMPLIFIER_IN, AMPLIFIER_RIGHT, u8Index), cmd, 0); + + // if (CODEC_NID(cmd) == pThis->u8AdcVolsLineIn) + // { + hdaCodecToAudVolume(pThis, pNode, pAmplifier, PDMAUDIOMIXERCTL_LINE_IN); + // } + } + if (fIsOut) + { + if (fIsLeft) + hdaCodecSetRegisterU8(&LIFIER_REGISTER(*pAmplifier, AMPLIFIER_OUT, AMPLIFIER_LEFT, u8Index), cmd, 0); + if (fIsRight) + hdaCodecSetRegisterU8(&LIFIER_REGISTER(*pAmplifier, AMPLIFIER_OUT, AMPLIFIER_RIGHT, u8Index), cmd, 0); + + if (CODEC_NID(cmd) == pThis->u8DacLineOut) + hdaCodecToAudVolume(pThis, pNode, pAmplifier, PDMAUDIOMIXERCTL_FRONT); + } + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) vrbProcGetParameter(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + Assert((cmd & CODEC_VERB_8BIT_DATA) < CODECNODE_F00_PARAM_LENGTH); + if ((cmd & CODEC_VERB_8BIT_DATA) >= CODECNODE_F00_PARAM_LENGTH) + { + *pResp = 0; + + LogFlowFunc(("invalid F00 parameter %d\n", (cmd & CODEC_VERB_8BIT_DATA))); + return VINF_SUCCESS; + } + + *pResp = pThis->paNodes[CODEC_NID(cmd)].node.au32F00_param[cmd & CODEC_VERB_8BIT_DATA]; + return VINF_SUCCESS; +} + +/* F01 */ +static DECLCALLBACK(int) vrbProcGetConSelectCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].adcmux.u32F01_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F01_param; + else if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F01_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32F01_param; + else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].adcvol.u32F01_param; + else + LogRel2(("HDA: Warning: Unhandled get connection select control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + return VINF_SUCCESS; +} + +/* 701 */ +static DECLCALLBACK(int) vrbProcSetConSelectCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adcmux.u32F01_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F01_param; + else if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F01_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adc.u32F01_param; + else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adcvol.u32F01_param; + else + LogRel2(("HDA: Warning: Unhandled set connection select control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, cmd, 0); + + return VINF_SUCCESS; +} + +/* F07 */ +static DECLCALLBACK(int) vrbProcGetPinCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F07_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F07_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F07_param; + else if (hdaCodecIsCdNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].cdnode.u32F07_param; + else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].pcbeep.u32F07_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32F07_param; + else + LogRel2(("HDA: Warning: Unhandled get pin control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + return VINF_SUCCESS; +} + +/* 707 */ +static DECLCALLBACK(int) vrbProcSetPinCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F07_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F07_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F07_param; + else if (hdaCodecIsCdNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].cdnode.u32F07_param; + else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].pcbeep.u32F07_param; + else if ( hdaCodecIsReservedNode(pThis, CODEC_NID(cmd)) + && CODEC_NID(cmd) == 0x1b) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].reserved.u32F07_param; + else + LogRel2(("HDA: Warning: Unhandled set pin control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, cmd, 0); + + return VINF_SUCCESS; +} + +/* F08 */ +static DECLCALLBACK(int) vrbProcGetUnsolicitedEnabled(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F08_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F08_param; + else if ((cmd) == STAC9220_NID_AFG) + *pResp = pThis->paNodes[CODEC_NID(cmd)].afg.u32F08_param; + else if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].volumeKnob.u32F08_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F08_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F08_param; + else + LogRel2(("HDA: Warning: Unhandled get unsolicited enabled command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + return VINF_SUCCESS; +} + +/* 708 */ +static DECLCALLBACK(int) vrbProcSetUnsolicitedEnabled(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F08_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F08_param; + else if (CODEC_NID(cmd) == STAC9220_NID_AFG) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].afg.u32F08_param; + else if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].volumeKnob.u32F08_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F08_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F08_param; + else + LogRel2(("HDA: Warning: Unhandled set unsolicited enabled command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, cmd, 0); + + return VINF_SUCCESS; +} + +/* F09 */ +static DECLCALLBACK(int) vrbProcGetPinSense(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F09_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F09_param; + else + { + AssertFailed(); + LogRel2(("HDA: Warning: Unhandled get pin sense command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + } + + return VINF_SUCCESS; +} + +/* 709 */ +static DECLCALLBACK(int) vrbProcSetPinSense(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F09_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F09_param; + else + LogRel2(("HDA: Warning: Unhandled set pin sense command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, cmd, 0); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) vrbProcGetConnectionListEntry(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + Assert((cmd & CODEC_VERB_8BIT_DATA) < CODECNODE_F02_PARAM_LENGTH); + if ((cmd & CODEC_VERB_8BIT_DATA) >= CODECNODE_F02_PARAM_LENGTH) + { + LogFlowFunc(("access to invalid F02 index %d\n", (cmd & CODEC_VERB_8BIT_DATA))); + return VINF_SUCCESS; + } + *pResp = pThis->paNodes[CODEC_NID(cmd)].node.au32F02_param[cmd & CODEC_VERB_8BIT_DATA]; + return VINF_SUCCESS; +} + +/* F03 */ +static DECLCALLBACK(int) vrbProcGetProcessingState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32F03_param; + + return VINF_SUCCESS; +} + +/* 703 */ +static DECLCALLBACK(int) vrbProcSetProcessingState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + hdaCodecSetRegisterU8(&pThis->paNodes[CODEC_NID(cmd)].adc.u32F03_param, cmd, 0); + return VINF_SUCCESS; +} + +/* F0D */ +static DECLCALLBACK(int) vrbProcGetDigitalConverter(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F0d_param; + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F0d_param; + + return VINF_SUCCESS; +} + +static int codecSetDigitalConverter(PHDACODEC pThis, uint32_t cmd, uint8_t u8Offset, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) + hdaCodecSetRegisterU8(&pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F0d_param, cmd, u8Offset); + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) + hdaCodecSetRegisterU8(&pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F0d_param, cmd, u8Offset); + return VINF_SUCCESS; +} + +/* 70D */ +static DECLCALLBACK(int) vrbProcSetDigitalConverter1(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + return codecSetDigitalConverter(pThis, cmd, 0, pResp); +} + +/* 70E */ +static DECLCALLBACK(int) vrbProcSetDigitalConverter2(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + return codecSetDigitalConverter(pThis, cmd, 8, pResp); +} + +/* F20 */ +static DECLCALLBACK(int) vrbProcGetSubId(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + Assert(CODEC_CAD(cmd) == pThis->id); + Assert(CODEC_NID(cmd) < pThis->cTotalNodes); + if (CODEC_NID(cmd) >= pThis->cTotalNodes) + { + LogFlowFunc(("invalid node address %d\n", CODEC_NID(cmd))); + return VINF_SUCCESS; + } + if (CODEC_NID(cmd) == STAC9220_NID_AFG) + *pResp = pThis->paNodes[CODEC_NID(cmd)].afg.u32F20_param; + else + *pResp = 0; + return VINF_SUCCESS; +} + +static int codecSetSubIdX(PHDACODEC pThis, uint32_t cmd, uint8_t u8Offset) +{ + Assert(CODEC_CAD(cmd) == pThis->id); + Assert(CODEC_NID(cmd) < pThis->cTotalNodes); + if (CODEC_NID(cmd) >= pThis->cTotalNodes) + { + LogFlowFunc(("invalid node address %d\n", CODEC_NID(cmd))); + return VINF_SUCCESS; + } + uint32_t *pu32Reg; + if (CODEC_NID(cmd) == STAC9220_NID_AFG) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].afg.u32F20_param; + else + AssertFailedReturn(VINF_SUCCESS); + hdaCodecSetRegisterU8(pu32Reg, cmd, u8Offset); + return VINF_SUCCESS; +} + +/* 720 */ +static DECLCALLBACK(int) vrbProcSetSubId0(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + return codecSetSubIdX(pThis, cmd, 0); +} + +/* 721 */ +static DECLCALLBACK(int) vrbProcSetSubId1(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + return codecSetSubIdX(pThis, cmd, 8); +} + +/* 722 */ +static DECLCALLBACK(int) vrbProcSetSubId2(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + return codecSetSubIdX(pThis, cmd, 16); +} + +/* 723 */ +static DECLCALLBACK(int) vrbProcSetSubId3(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + return codecSetSubIdX(pThis, cmd, 24); +} + +static DECLCALLBACK(int) vrbProcReset(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + Assert(CODEC_CAD(cmd) == pThis->id); + Assert(CODEC_NID(cmd) == STAC9220_NID_AFG); + + if ( CODEC_NID(cmd) == STAC9220_NID_AFG + && pThis->pfnReset) + { + pThis->pfnReset(pThis); + } + + *pResp = 0; + return VINF_SUCCESS; +} + +/* F05 */ +static DECLCALLBACK(int) vrbProcGetPowerState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (CODEC_NID(cmd) == STAC9220_NID_AFG) + *pResp = pThis->paNodes[CODEC_NID(cmd)].afg.u32F05_param; + else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32F05_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32F05_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F05_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F05_param; + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F05_param; + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F05_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32F05_param; + else + LogRel2(("HDA: Warning: Unhandled get power state command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + LogFunc(("[NID0x%02x]: fReset=%RTbool, fStopOk=%RTbool, Act=D%RU8, Set=D%RU8\n", + CODEC_NID(cmd), CODEC_F05_IS_RESET(*pResp), CODEC_F05_IS_STOPOK(*pResp), CODEC_F05_ACT(*pResp), CODEC_F05_SET(*pResp))); + return VINF_SUCCESS; +} + +/* 705 */ +#if 1 +static DECLCALLBACK(int) vrbProcSetPowerState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + uint32_t *pu32Reg = NULL; + if (CODEC_NID(cmd) == STAC9220_NID_AFG) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].afg.u32F05_param; + else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F05_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F05_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F05_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adc.u32F05_param; + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F05_param; + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F05_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].reserved.u32F05_param; + else + { + LogRel2(("HDA: Warning: Unhandled set power state command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + } + + if (!pu32Reg) + return VINF_SUCCESS; + + uint8_t uPwrCmd = CODEC_F05_SET (cmd); + bool fReset = CODEC_F05_IS_RESET (*pu32Reg); + bool fStopOk = CODEC_F05_IS_STOPOK(*pu32Reg); +#ifdef LOG_ENABLED + bool fError = CODEC_F05_IS_ERROR (*pu32Reg); + uint8_t uPwrAct = CODEC_F05_ACT (*pu32Reg); + uint8_t uPwrSet = CODEC_F05_SET (*pu32Reg); + LogFunc(("[NID0x%02x] Cmd=D%RU8, fReset=%RTbool, fStopOk=%RTbool, fError=%RTbool, uPwrAct=D%RU8, uPwrSet=D%RU8\n", + CODEC_NID(cmd), uPwrCmd, fReset, fStopOk, fError, uPwrAct, uPwrSet)); + LogFunc(("AFG: Act=D%RU8, Set=D%RU8\n", + CODEC_F05_ACT(pThis->paNodes[STAC9220_NID_AFG].afg.u32F05_param), + CODEC_F05_SET(pThis->paNodes[STAC9220_NID_AFG].afg.u32F05_param))); +#endif + + if (CODEC_NID(cmd) == STAC9220_NID_AFG) + *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, uPwrCmd /* PS-Act */, uPwrCmd /* PS-Set */); + + const uint8_t uAFGPwrAct = CODEC_F05_ACT(pThis->paNodes[STAC9220_NID_AFG].afg.u32F05_param); + if (uAFGPwrAct == CODEC_F05_D0) /* Only propagate power state if AFG is on (D0). */ + { + /* Propagate to all other nodes under this AFG. */ + LogFunc(("Propagating Act=D%RU8 (AFG), Set=D%RU8 to all AFG child nodes ...\n", uAFGPwrAct, uPwrCmd)); + +#define PROPAGATE_PWR_STATE(_aList, _aMember) \ + { \ + const uint8_t *pu8NodeIndex = &_aList[0]; \ + while (*(++pu8NodeIndex)) \ + { \ + pThis->paNodes[*pu8NodeIndex]._aMember.u32F05_param = \ + CODEC_MAKE_F05(fReset, fStopOk, 0, uAFGPwrAct, uPwrCmd); \ + LogFunc(("\t[NID0x%02x]: Act=D%RU8, Set=D%RU8\n", *pu8NodeIndex, \ + CODEC_F05_ACT(pThis->paNodes[*pu8NodeIndex]._aMember.u32F05_param), \ + CODEC_F05_SET(pThis->paNodes[*pu8NodeIndex]._aMember.u32F05_param))); \ + } \ + } + + PROPAGATE_PWR_STATE(pThis->au8Dacs, dac); + PROPAGATE_PWR_STATE(pThis->au8Adcs, adc); + PROPAGATE_PWR_STATE(pThis->au8DigInPins, digin); + PROPAGATE_PWR_STATE(pThis->au8DigOutPins, digout); + PROPAGATE_PWR_STATE(pThis->au8SpdifIns, spdifin); + PROPAGATE_PWR_STATE(pThis->au8SpdifOuts, spdifout); + PROPAGATE_PWR_STATE(pThis->au8Reserveds, reserved); + +#undef PROPAGATE_PWR_STATE + } + /* + * If this node is a reqular node (not the AFG one), adopt PS-Set of the AFG node + * as PS-Set of this node. PS-Act always is one level under PS-Set here. + */ + else + { + *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, uAFGPwrAct, uPwrCmd); + } + + LogFunc(("[NID0x%02x] fReset=%RTbool, fStopOk=%RTbool, Act=D%RU8, Set=D%RU8\n", + CODEC_NID(cmd), + CODEC_F05_IS_RESET(*pu32Reg), CODEC_F05_IS_STOPOK(*pu32Reg), CODEC_F05_ACT(*pu32Reg), CODEC_F05_SET(*pu32Reg))); + + return VINF_SUCCESS; +} +#else +DECLINLINE(void) codecPropogatePowerState(uint32_t *pu32F05_param) +{ + Assert(pu32F05_param); + if (!pu32F05_param) + return; + bool fReset = CODEC_F05_IS_RESET(*pu32F05_param); + bool fStopOk = CODEC_F05_IS_STOPOK(*pu32F05_param); + uint8_t u8SetPowerState = CODEC_F05_SET(*pu32F05_param); + *pu32F05_param = CODEC_MAKE_F05(fReset, fStopOk, 0, u8SetPowerState, u8SetPowerState); +} + +static DECLCALLBACK(int) vrbProcSetPowerState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + Assert(CODEC_CAD(cmd) == pThis->id); + Assert(CODEC_NID(cmd) < pThis->cTotalNodes); + if (CODEC_NID(cmd) >= pThis->cTotalNodes) + { + LogFlowFunc(("invalid node address %d\n", CODEC_NID(cmd))); + return VINF_SUCCESS; + } + *pResp = 0; + uint32_t *pu32Reg; + if (CODEC_NID(cmd) == 1 /* AFG */) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].afg.u32F05_param; + else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F05_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F05_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adc.u32F05_param; + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F05_param; + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F05_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].reserved.u32F05_param; + else + AssertFailedReturn(VINF_SUCCESS); + + bool fReset = CODEC_F05_IS_RESET(*pu32Reg); + bool fStopOk = CODEC_F05_IS_STOPOK(*pu32Reg); + + if (CODEC_NID(cmd) != 1 /* AFG */) + { + /* + * We shouldn't propogate actual power state, which actual for AFG + */ + *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, + CODEC_F05_ACT(pThis->paNodes[1].afg.u32F05_param), + CODEC_F05_SET(cmd)); + } + + /* Propagate next power state only if AFG is on or verb modifies AFG power state */ + if ( CODEC_NID(cmd) == 1 /* AFG */ + || !CODEC_F05_ACT(pThis->paNodes[1].afg.u32F05_param)) + { + *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, CODEC_F05_SET(cmd), CODEC_F05_SET(cmd)); + if ( CODEC_NID(cmd) == 1 /* AFG */ + && (CODEC_F05_SET(cmd)) == CODEC_F05_D0) + { + /* now we're powered on AFG and may propogate power states on nodes */ + const uint8_t *pu8NodeIndex = &pThis->au8Dacs[0]; + while (*(++pu8NodeIndex)) + codecPropogatePowerState(&pThis->paNodes[*pu8NodeIndex].dac.u32F05_param); + + pu8NodeIndex = &pThis->au8Adcs[0]; + while (*(++pu8NodeIndex)) + codecPropogatePowerState(&pThis->paNodes[*pu8NodeIndex].adc.u32F05_param); + + pu8NodeIndex = &pThis->au8DigInPins[0]; + while (*(++pu8NodeIndex)) + codecPropogatePowerState(&pThis->paNodes[*pu8NodeIndex].digin.u32F05_param); + } + } + return VINF_SUCCESS; +} +#endif + +/* F06 */ +static DECLCALLBACK(int) vrbProcGetStreamId(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32F06_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32F06_param; + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F06_param; + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F06_param; + else if (CODEC_NID(cmd) == STAC9221_NID_I2S_OUT) + *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32F06_param; + else + LogRel2(("HDA: Warning: Unhandled get stream ID command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + LogFlowFunc(("[NID0x%02x] Stream ID=%RU8, channel=%RU8\n", + CODEC_NID(cmd), CODEC_F00_06_GET_STREAM_ID(cmd), CODEC_F00_06_GET_CHANNEL_ID(cmd))); + + return VINF_SUCCESS; +} + +/* 706 */ +static DECLCALLBACK(int) vrbProcSetStreamId(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + uint8_t uSD = CODEC_F00_06_GET_STREAM_ID(cmd); + uint8_t uChannel = CODEC_F00_06_GET_CHANNEL_ID(cmd); + + LogFlowFunc(("[NID0x%02x] Setting to stream ID=%RU8, channel=%RU8\n", + CODEC_NID(cmd), uSD, uChannel)); + + PDMAUDIODIR enmDir; + uint32_t *pu32Addr = NULL; + if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + { + pu32Addr = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F06_param; + enmDir = PDMAUDIODIR_OUT; + } + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + { + pu32Addr = &pThis->paNodes[CODEC_NID(cmd)].adc.u32F06_param; + enmDir = PDMAUDIODIR_IN; + } + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) + { + pu32Addr = &pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F06_param; + enmDir = PDMAUDIODIR_OUT; + } + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) + { + pu32Addr = &pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F06_param; + enmDir = PDMAUDIODIR_IN; + } + else + { + enmDir = PDMAUDIODIR_UNKNOWN; + LogRel2(("HDA: Warning: Unhandled set stream ID command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + } + + /* Do we (re-)assign our input/output SDn (SDI/SDO) IDs? */ + if (enmDir != PDMAUDIODIR_UNKNOWN) + { + pThis->paNodes[CODEC_NID(cmd)].node.uSD = uSD; + pThis->paNodes[CODEC_NID(cmd)].node.uChannel = uChannel; + + if (enmDir == PDMAUDIODIR_OUT) + { + /** @todo Check if non-interleaved streams need a different channel / SDn? */ + + /* Propagate to the controller. */ + pThis->pfnCbMixerControl(pThis->pHDAState, PDMAUDIOMIXERCTL_FRONT, uSD, uChannel); +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + pThis->pfnCbMixerControl(pThis->pHDAState, PDMAUDIOMIXERCTL_CENTER_LFE, uSD, uChannel); + pThis->pfnCbMixerControl(pThis->pHDAState, PDMAUDIOMIXERCTL_REAR, uSD, uChannel); +#endif + } + else if (enmDir == PDMAUDIODIR_IN) + { + pThis->pfnCbMixerControl(pThis->pHDAState, PDMAUDIOMIXERCTL_LINE_IN, uSD, uChannel); +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + pThis->pfnCbMixerControl(pThis->pHDAState, PDMAUDIOMIXERCTL_MIC_IN, uSD, uChannel); +#endif + } + } + + if (pu32Addr) + hdaCodecSetRegisterU8(pu32Addr, cmd, 0); + + return VINF_SUCCESS; +} + +/* A0 */ +static DECLCALLBACK(int) vrbProcGetConverterFormat(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32A_param; + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32A_param; + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifout.u32A_param; + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifin.u32A_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32A_param; + else + LogRel2(("HDA: Warning: Unhandled get converter format command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + return VINF_SUCCESS; +} + +/* Also see section 3.7.1. */ +static DECLCALLBACK(int) vrbProcSetConverterFormat(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + hdaCodecSetRegisterU16(&pThis->paNodes[CODEC_NID(cmd)].dac.u32A_param, cmd, 0); + else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd))) + hdaCodecSetRegisterU16(&pThis->paNodes[CODEC_NID(cmd)].adc.u32A_param, cmd, 0); + else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd))) + hdaCodecSetRegisterU16(&pThis->paNodes[CODEC_NID(cmd)].spdifout.u32A_param, cmd, 0); + else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd))) + hdaCodecSetRegisterU16(&pThis->paNodes[CODEC_NID(cmd)].spdifin.u32A_param, cmd, 0); + else + LogRel2(("HDA: Warning: Unhandled set converter format command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + return VINF_SUCCESS; +} + +/* F0C */ +static DECLCALLBACK(int) vrbProcGetEAPD_BTLEnabled(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].adcvol.u32F0c_param; + else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32F0c_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F0c_param; + else + LogRel2(("HDA: Warning: Unhandled get EAPD/BTL enabled command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + return VINF_SUCCESS; +} + +/* 70C */ +static DECLCALLBACK(int) vrbProcSetEAPD_BTLEnabled(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adcvol.u32F0c_param; + else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F0c_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F0c_param; + else + LogRel2(("HDA: Warning: Unhandled set EAPD/BTL enabled command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, cmd, 0); + + return VINF_SUCCESS; +} + +/* F0F */ +static DECLCALLBACK(int) vrbProcGetVolumeKnobCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].volumeKnob.u32F0f_param; + else + LogRel2(("HDA: Warning: Unhandled get volume knob control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + return VINF_SUCCESS; +} + +/* 70F */ +static DECLCALLBACK(int) vrbProcSetVolumeKnobCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].volumeKnob.u32F0f_param; + else + LogRel2(("HDA: Warning: Unhandled set volume knob control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, cmd, 0); + + return VINF_SUCCESS; +} + +/* F15 */ +static DECLCALLBACK(int) vrbProcGetGPIOData(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + RT_NOREF(pThis, cmd); + *pResp = 0; + return VINF_SUCCESS; +} + +/* 715 */ +static DECLCALLBACK(int) vrbProcSetGPIOData(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + RT_NOREF(pThis, cmd); + *pResp = 0; + return VINF_SUCCESS; +} + +/* F16 */ +static DECLCALLBACK(int) vrbProcGetGPIOEnableMask(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + RT_NOREF(pThis, cmd); + *pResp = 0; + return VINF_SUCCESS; +} + +/* 716 */ +static DECLCALLBACK(int) vrbProcSetGPIOEnableMask(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + RT_NOREF(pThis, cmd); + *pResp = 0; + return VINF_SUCCESS; +} + +/* F17 */ +static DECLCALLBACK(int) vrbProcGetGPIODirection(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + /* Note: this is true for ALC885. */ + if (CODEC_NID(cmd) == STAC9220_NID_AFG) + *pResp = pThis->paNodes[1].afg.u32F17_param; + else + LogRel2(("HDA: Warning: Unhandled get GPIO direction command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + return VINF_SUCCESS; +} + +/* 717 */ +static DECLCALLBACK(int) vrbProcSetGPIODirection(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + uint32_t *pu32Reg = NULL; + if (CODEC_NID(cmd) == STAC9220_NID_AFG) + pu32Reg = &pThis->paNodes[1].afg.u32F17_param; + else + LogRel2(("HDA: Warning: Unhandled set GPIO direction command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, cmd, 0); + + return VINF_SUCCESS; +} + +/* F1C */ +static DECLCALLBACK(int) vrbProcGetConfig(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F1c_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F1c_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F1c_param; + else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].pcbeep.u32F1c_param; + else if (hdaCodecIsCdNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].cdnode.u32F1c_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32F1c_param; + else + LogRel2(("HDA: Warning: Unhandled get config command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + return VINF_SUCCESS; +} + +static int codecSetConfigX(PHDACODEC pThis, uint32_t cmd, uint8_t u8Offset) +{ + uint32_t *pu32Reg = NULL; + if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F1c_param; + else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F1c_param; + else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F1c_param; + else if (hdaCodecIsCdNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].cdnode.u32F1c_param; + else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].pcbeep.u32F1c_param; + else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].reserved.u32F1c_param; + else + LogRel2(("HDA: Warning: Unhandled set config command (%RU8) for NID0x%02x: 0x%x\n", u8Offset, CODEC_NID(cmd), cmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, cmd, u8Offset); + + return VINF_SUCCESS; +} + +/* 71C */ +static DECLCALLBACK(int) vrbProcSetConfig0(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + return codecSetConfigX(pThis, cmd, 0); +} + +/* 71D */ +static DECLCALLBACK(int) vrbProcSetConfig1(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + return codecSetConfigX(pThis, cmd, 8); +} + +/* 71E */ +static DECLCALLBACK(int) vrbProcSetConfig2(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + return codecSetConfigX(pThis, cmd, 16); +} + +/* 71E */ +static DECLCALLBACK(int) vrbProcSetConfig3(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + return codecSetConfigX(pThis, cmd, 24); +} + +/* F04 */ +static DECLCALLBACK(int) vrbProcGetSDISelect(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32F04_param; + else + LogRel2(("HDA: Warning: Unhandled get SDI select command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + return VINF_SUCCESS; +} + +/* 704 */ +static DECLCALLBACK(int) vrbProcSetSDISelect(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp) +{ + *pResp = 0; + + uint32_t *pu32Reg = NULL; + if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd))) + pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F04_param; + else + LogRel2(("HDA: Warning: Unhandled set SDI select command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd)); + + if (pu32Reg) + hdaCodecSetRegisterU8(pu32Reg, cmd, 0); + + return VINF_SUCCESS; +} + +/** + * HDA codec verb map. + * @todo Any reason not to use binary search here? + */ +static const CODECVERB g_aCodecVerbs[] = +{ + /* Verb Verb mask Callback Name + * ---------- --------------------- ---------------------------------------------------------- + */ + { 0x000F0000, CODEC_VERB_8BIT_CMD , vrbProcGetParameter , "GetParameter " }, + { 0x000F0100, CODEC_VERB_8BIT_CMD , vrbProcGetConSelectCtrl , "GetConSelectCtrl " }, + { 0x00070100, CODEC_VERB_8BIT_CMD , vrbProcSetConSelectCtrl , "SetConSelectCtrl " }, + { 0x000F0600, CODEC_VERB_8BIT_CMD , vrbProcGetStreamId , "GetStreamId " }, + { 0x00070600, CODEC_VERB_8BIT_CMD , vrbProcSetStreamId , "SetStreamId " }, + { 0x000F0700, CODEC_VERB_8BIT_CMD , vrbProcGetPinCtrl , "GetPinCtrl " }, + { 0x00070700, CODEC_VERB_8BIT_CMD , vrbProcSetPinCtrl , "SetPinCtrl " }, + { 0x000F0800, CODEC_VERB_8BIT_CMD , vrbProcGetUnsolicitedEnabled , "GetUnsolicitedEnabled " }, + { 0x00070800, CODEC_VERB_8BIT_CMD , vrbProcSetUnsolicitedEnabled , "SetUnsolicitedEnabled " }, + { 0x000F0900, CODEC_VERB_8BIT_CMD , vrbProcGetPinSense , "GetPinSense " }, + { 0x00070900, CODEC_VERB_8BIT_CMD , vrbProcSetPinSense , "SetPinSense " }, + { 0x000F0200, CODEC_VERB_8BIT_CMD , vrbProcGetConnectionListEntry , "GetConnectionListEntry" }, + { 0x000F0300, CODEC_VERB_8BIT_CMD , vrbProcGetProcessingState , "GetProcessingState " }, + { 0x00070300, CODEC_VERB_8BIT_CMD , vrbProcSetProcessingState , "SetProcessingState " }, + { 0x000F0D00, CODEC_VERB_8BIT_CMD , vrbProcGetDigitalConverter , "GetDigitalConverter " }, + { 0x00070D00, CODEC_VERB_8BIT_CMD , vrbProcSetDigitalConverter1 , "SetDigitalConverter1 " }, + { 0x00070E00, CODEC_VERB_8BIT_CMD , vrbProcSetDigitalConverter2 , "SetDigitalConverter2 " }, + { 0x000F2000, CODEC_VERB_8BIT_CMD , vrbProcGetSubId , "GetSubId " }, + { 0x00072000, CODEC_VERB_8BIT_CMD , vrbProcSetSubId0 , "SetSubId0 " }, + { 0x00072100, CODEC_VERB_8BIT_CMD , vrbProcSetSubId1 , "SetSubId1 " }, + { 0x00072200, CODEC_VERB_8BIT_CMD , vrbProcSetSubId2 , "SetSubId2 " }, + { 0x00072300, CODEC_VERB_8BIT_CMD , vrbProcSetSubId3 , "SetSubId3 " }, + { 0x0007FF00, CODEC_VERB_8BIT_CMD , vrbProcReset , "Reset " }, + { 0x000F0500, CODEC_VERB_8BIT_CMD , vrbProcGetPowerState , "GetPowerState " }, + { 0x00070500, CODEC_VERB_8BIT_CMD , vrbProcSetPowerState , "SetPowerState " }, + { 0x000F0C00, CODEC_VERB_8BIT_CMD , vrbProcGetEAPD_BTLEnabled , "GetEAPD_BTLEnabled " }, + { 0x00070C00, CODEC_VERB_8BIT_CMD , vrbProcSetEAPD_BTLEnabled , "SetEAPD_BTLEnabled " }, + { 0x000F0F00, CODEC_VERB_8BIT_CMD , vrbProcGetVolumeKnobCtrl , "GetVolumeKnobCtrl " }, + { 0x00070F00, CODEC_VERB_8BIT_CMD , vrbProcSetVolumeKnobCtrl , "SetVolumeKnobCtrl " }, + { 0x000F1500, CODEC_VERB_8BIT_CMD , vrbProcGetGPIOData , "GetGPIOData " }, + { 0x00071500, CODEC_VERB_8BIT_CMD , vrbProcSetGPIOData , "SetGPIOData " }, + { 0x000F1600, CODEC_VERB_8BIT_CMD , vrbProcGetGPIOEnableMask , "GetGPIOEnableMask " }, + { 0x00071600, CODEC_VERB_8BIT_CMD , vrbProcSetGPIOEnableMask , "SetGPIOEnableMask " }, + { 0x000F1700, CODEC_VERB_8BIT_CMD , vrbProcGetGPIODirection , "GetGPIODirection " }, + { 0x00071700, CODEC_VERB_8BIT_CMD , vrbProcSetGPIODirection , "SetGPIODirection " }, + { 0x000F1C00, CODEC_VERB_8BIT_CMD , vrbProcGetConfig , "GetConfig " }, + { 0x00071C00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig0 , "SetConfig0 " }, + { 0x00071D00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig1 , "SetConfig1 " }, + { 0x00071E00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig2 , "SetConfig2 " }, + { 0x00071F00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig3 , "SetConfig3 " }, + { 0x000A0000, CODEC_VERB_16BIT_CMD, vrbProcGetConverterFormat , "GetConverterFormat " }, + { 0x00020000, CODEC_VERB_16BIT_CMD, vrbProcSetConverterFormat , "SetConverterFormat " }, + { 0x000B0000, CODEC_VERB_16BIT_CMD, vrbProcGetAmplifier , "GetAmplifier " }, + { 0x00030000, CODEC_VERB_16BIT_CMD, vrbProcSetAmplifier , "SetAmplifier " }, + { 0x000F0400, CODEC_VERB_8BIT_CMD , vrbProcGetSDISelect , "GetSDISelect " }, + { 0x00070400, CODEC_VERB_8BIT_CMD , vrbProcSetSDISelect , "SetSDISelect " } + /** @todo Implement 0x7e7: IDT Set GPIO (STAC922x only). */ +}; + +#ifdef DEBUG +typedef struct CODECDBGINFO +{ + /** DBGF info helpers. */ + PCDBGFINFOHLP pHlp; + /** Current recursion level. */ + uint8_t uLevel; + /** Pointer to codec state. */ + PHDACODEC pThis; + +} CODECDBGINFO, *PCODECDBGINFO; + +#define CODECDBG_INDENT pInfo->uLevel++; +#define CODECDBG_UNINDENT if (pInfo->uLevel) pInfo->uLevel--; + +#define CODECDBG_PRINT(...) pInfo->pHlp->pfnPrintf(pInfo->pHlp, __VA_ARGS__) +#define CODECDBG_PRINTI(...) codecDbgPrintf(pInfo, __VA_ARGS__) + +static void codecDbgPrintfIndentV(PCODECDBGINFO pInfo, uint16_t uIndent, const char *pszFormat, va_list va) +{ + char *pszValueFormat; + if (RTStrAPrintfV(&pszValueFormat, pszFormat, va)) + { + pInfo->pHlp->pfnPrintf(pInfo->pHlp, "%*s%s", uIndent, "", pszValueFormat); + RTStrFree(pszValueFormat); + } +} + +static void codecDbgPrintf(PCODECDBGINFO pInfo, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + codecDbgPrintfIndentV(pInfo, pInfo->uLevel * 4, pszFormat, va); + va_end(va); +} + +/* Power state */ +static void codecDbgPrintNodeRegF05(PCODECDBGINFO pInfo, uint32_t u32Reg) +{ + codecDbgPrintf(pInfo, "Power (F05): fReset=%RTbool, fStopOk=%RTbool, Set=%RU8, Act=%RU8\n", + CODEC_F05_IS_RESET(u32Reg), CODEC_F05_IS_STOPOK(u32Reg), CODEC_F05_SET(u32Reg), CODEC_F05_ACT(u32Reg)); +} + +static void codecDbgPrintNodeRegA(PCODECDBGINFO pInfo, uint32_t u32Reg) +{ + codecDbgPrintf(pInfo, "RegA: %x\n", u32Reg); +} + +static void codecDbgPrintNodeRegF00(PCODECDBGINFO pInfo, uint32_t *paReg00) +{ + codecDbgPrintf(pInfo, "Parameters (F00):\n"); + + CODECDBG_INDENT + codecDbgPrintf(pInfo, "Connections: %RU8\n", CODEC_F00_0E_COUNT(paReg00[0xE])); + codecDbgPrintf(pInfo, "Amplifier Caps:\n"); + uint32_t uReg = paReg00[0xD]; + CODECDBG_INDENT + codecDbgPrintf(pInfo, "Input Steps=%02RU8, StepSize=%02RU8, StepOff=%02RU8, fCanMute=%RTbool\n", + CODEC_F00_0D_NUM_STEPS(uReg), + CODEC_F00_0D_STEP_SIZE(uReg), + CODEC_F00_0D_OFFSET(uReg), + RT_BOOL(CODEC_F00_0D_IS_CAP_MUTE(uReg))); + + uReg = paReg00[0x12]; + codecDbgPrintf(pInfo, "Output Steps=%02RU8, StepSize=%02RU8, StepOff=%02RU8, fCanMute=%RTbool\n", + CODEC_F00_12_NUM_STEPS(uReg), + CODEC_F00_12_STEP_SIZE(uReg), + CODEC_F00_12_OFFSET(uReg), + RT_BOOL(CODEC_F00_12_IS_CAP_MUTE(uReg))); + CODECDBG_UNINDENT + CODECDBG_UNINDENT +} + +static void codecDbgPrintNodeAmp(PCODECDBGINFO pInfo, uint32_t *paReg, uint8_t uIdx, uint8_t uDir) +{ +#define CODECDBG_AMP(reg, chan) \ + codecDbgPrintf(pInfo, "Amp %RU8 %s %s: In=%RTbool, Out=%RTbool, Left=%RTbool, Right=%RTbool, Idx=%RU8, fMute=%RTbool, uGain=%RU8\n", \ + uIdx, chan, uDir == AMPLIFIER_IN ? "In" : "Out", \ + RT_BOOL(CODEC_SET_AMP_IS_IN_DIRECTION(reg)), RT_BOOL(CODEC_SET_AMP_IS_OUT_DIRECTION(reg)), \ + RT_BOOL(CODEC_SET_AMP_IS_LEFT_SIDE(reg)), RT_BOOL(CODEC_SET_AMP_IS_RIGHT_SIDE(reg)), \ + CODEC_SET_AMP_INDEX(reg), RT_BOOL(CODEC_SET_AMP_MUTE(reg)), CODEC_SET_AMP_GAIN(reg)); + + uint32_t regAmp = AMPLIFIER_REGISTER(paReg, uDir, AMPLIFIER_LEFT, uIdx); + CODECDBG_AMP(regAmp, "Left"); + regAmp = AMPLIFIER_REGISTER(paReg, uDir, AMPLIFIER_RIGHT, uIdx); + CODECDBG_AMP(regAmp, "Right"); + +#undef CODECDBG_AMP +} + +#if 0 /* unused */ +static void codecDbgPrintNodeConnections(PCODECDBGINFO pInfo, PCODECNODE pNode) +{ + if (pNode->node.au32F00_param[0xE] == 0) /* Directly connected to HDA link. */ + { + codecDbgPrintf(pInfo, "[HDA LINK]\n"); + return; + } +} +#endif + +static void codecDbgPrintNode(PCODECDBGINFO pInfo, PCODECNODE pNode, bool fRecursive) +{ + codecDbgPrintf(pInfo, "Node 0x%02x (%02RU8): ", pNode->node.uID, pNode->node.uID); + + if (pNode->node.uID == STAC9220_NID_ROOT) + { + CODECDBG_PRINT("ROOT\n"); + } + else if (pNode->node.uID == STAC9220_NID_AFG) + { + CODECDBG_PRINT("AFG\n"); + CODECDBG_INDENT + codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); + codecDbgPrintNodeRegF05(pInfo, pNode->afg.u32F05_param); + CODECDBG_UNINDENT + } + else if (hdaCodecIsPortNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("PORT\n"); + } + else if (hdaCodecIsDacNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("DAC\n"); + CODECDBG_INDENT + codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); + codecDbgPrintNodeRegF05(pInfo, pNode->dac.u32F05_param); + codecDbgPrintNodeRegA (pInfo, pNode->dac.u32A_param); + codecDbgPrintNodeAmp (pInfo, pNode->dac.B_params, 0, AMPLIFIER_OUT); + CODECDBG_UNINDENT + } + else if (hdaCodecIsAdcVolNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("ADC VOLUME\n"); + CODECDBG_INDENT + codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); + codecDbgPrintNodeRegA (pInfo, pNode->adcvol.u32A_params); + codecDbgPrintNodeAmp (pInfo, pNode->adcvol.B_params, 0, AMPLIFIER_IN); + CODECDBG_UNINDENT + } + else if (hdaCodecIsAdcNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("ADC\n"); + CODECDBG_INDENT + codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); + codecDbgPrintNodeRegF05(pInfo, pNode->adc.u32F05_param); + codecDbgPrintNodeRegA (pInfo, pNode->adc.u32A_param); + codecDbgPrintNodeAmp (pInfo, pNode->adc.B_params, 0, AMPLIFIER_IN); + CODECDBG_UNINDENT + } + else if (hdaCodecIsAdcMuxNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("ADC MUX\n"); + CODECDBG_INDENT + codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param); + codecDbgPrintNodeRegA (pInfo, pNode->adcmux.u32A_param); + codecDbgPrintNodeAmp (pInfo, pNode->adcmux.B_params, 0, AMPLIFIER_IN); + CODECDBG_UNINDENT + } + else if (hdaCodecIsPcbeepNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("PC BEEP\n"); + } + else if (hdaCodecIsSpdifOutNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("SPDIF OUT\n"); + } + else if (hdaCodecIsSpdifInNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("SPDIF IN\n"); + } + else if (hdaCodecIsDigInPinNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("DIGITAL IN PIN\n"); + } + else if (hdaCodecIsDigOutPinNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("DIGITAL OUT PIN\n"); + } + else if (hdaCodecIsCdNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("CD\n"); + } + else if (hdaCodecIsVolKnobNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("VOLUME KNOB\n"); + } + else if (hdaCodecIsReservedNode(pInfo->pThis, pNode->node.uID)) + { + CODECDBG_PRINT("RESERVED\n"); + } + else + CODECDBG_PRINT("UNKNOWN TYPE 0x%x\n", pNode->node.uID); + + if (fRecursive) + { +#define CODECDBG_PRINT_CONLIST_ENTRY(_aNode, _aEntry) \ + if (cCnt >= _aEntry) \ + { \ + const uint8_t uID = RT_BYTE##_aEntry(_aNode->node.au32F02_param[0x0]); \ + if (pNode->node.uID == uID) \ + codecDbgPrintNode(pInfo, _aNode, false /* fRecursive */); \ + } + + /* Slow recursion, but this is debug stuff anyway. */ + for (uint8_t i = 0; i < pInfo->pThis->cTotalNodes; i++) + { + const PCODECNODE pSubNode = &pInfo->pThis->paNodes[i]; + if (pSubNode->node.uID == pNode->node.uID) + continue; + + const uint8_t cCnt = CODEC_F00_0E_COUNT(pSubNode->node.au32F00_param[0xE]); + if (cCnt == 0) /* No connections present? Skip. */ + continue; + + CODECDBG_INDENT + CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 1) + CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 2) + CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 3) + CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 4) + CODECDBG_UNINDENT + } + +#undef CODECDBG_PRINT_CONLIST_ENTRY + } +} + +static DECLCALLBACK(void) codecDbgListNodes(PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF(pszArgs); + pHlp->pfnPrintf(pHlp, "HDA LINK / INPUTS\n"); + + CODECDBGINFO dbgInfo; + dbgInfo.pHlp = pHlp; + dbgInfo.pThis = pThis; + dbgInfo.uLevel = 0; + + PCODECDBGINFO pInfo = &dbgInfo; + + CODECDBG_INDENT + for (uint8_t i = 0; i < pThis->cTotalNodes; i++) + { + PCODECNODE pNode = &pThis->paNodes[i]; + + /* Start with all nodes which have connection entries set. */ + if (CODEC_F00_0E_COUNT(pNode->node.au32F00_param[0xE])) + codecDbgPrintNode(&dbgInfo, pNode, true /* fRecursive */); + } + CODECDBG_UNINDENT +} + +static DECLCALLBACK(void) codecDbgSelector(PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF(pThis, pHlp, pszArgs); +} +#endif + +static DECLCALLBACK(int) codecLookup(PHDACODEC pThis, uint32_t cmd, uint64_t *puResp) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(puResp, VERR_INVALID_POINTER); + + if (CODEC_CAD(cmd) != pThis->id) + { + *puResp = 0; + AssertMsgFailed(("Unknown codec address 0x%x\n", CODEC_CAD(cmd))); + return VERR_INVALID_PARAMETER; + } + + if ( CODEC_VERBDATA(cmd) == 0 + || CODEC_NID(cmd) >= pThis->cTotalNodes) + { + *puResp = 0; + AssertMsgFailed(("[NID0x%02x] Unknown / invalid node or data (0x%x)\n", CODEC_NID(cmd), CODEC_VERBDATA(cmd))); + return VERR_INVALID_PARAMETER; + } + + /** @todo r=andy Implement a binary search here. */ + for (size_t i = 0; i < pThis->cVerbs; i++) + { + if ((CODEC_VERBDATA(cmd) & pThis->paVerbs[i].mask) == pThis->paVerbs[i].verb) + { + int rc2 = pThis->paVerbs[i].pfn(pThis, cmd, puResp); + AssertRC(rc2); + Log3Func(("[NID0x%02x] (0x%x) %s: 0x%x -> 0x%x\n", + CODEC_NID(cmd), pThis->paVerbs[i].verb, pThis->paVerbs[i].pszName, CODEC_VERB_PAYLOAD8(cmd), *puResp)); + return rc2; + } + } + + *puResp = 0; + LogFunc(("[NID0x%02x] Callback for %x not found\n", CODEC_NID(cmd), CODEC_VERBDATA(cmd))); + return VERR_NOT_FOUND; +} + +/* + * APIs exposed to DevHDA. + */ + +int hdaCodecAddStream(PHDACODEC pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + switch (enmMixerCtl) + { + case PDMAUDIOMIXERCTL_VOLUME_MASTER: + case PDMAUDIOMIXERCTL_FRONT: +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + case PDMAUDIOMIXERCTL_CENTER_LFE: + case PDMAUDIOMIXERCTL_REAR: +#endif + { + break; + } + case PDMAUDIOMIXERCTL_LINE_IN: +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + case PDMAUDIOMIXERCTL_MIC_IN: +#endif + { + break; + } + default: + AssertMsgFailed(("Mixer control %d not implemented\n", enmMixerCtl)); + rc = VERR_NOT_IMPLEMENTED; + break; + } + + if (RT_SUCCESS(rc)) + rc = pThis->pfnCbMixerAddStream(pThis->pHDAState, enmMixerCtl, pCfg); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int hdaCodecRemoveStream(PHDACODEC pThis, PDMAUDIOMIXERCTL enmMixerCtl) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + int rc = pThis->pfnCbMixerRemoveStream(pThis->pHDAState, enmMixerCtl); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int hdaCodecSaveState(PHDACODEC pThis, PSSMHANDLE pSSM) +{ + AssertLogRelMsgReturn(pThis->cTotalNodes == STAC9221_NUM_NODES, ("cTotalNodes=%#x, should be 0x1c", pThis->cTotalNodes), + VERR_INTERNAL_ERROR); + SSMR3PutU32(pSSM, pThis->cTotalNodes); + for (unsigned idxNode = 0; idxNode < pThis->cTotalNodes; ++idxNode) + SSMR3PutStructEx(pSSM, &pThis->paNodes[idxNode].SavedState, sizeof(pThis->paNodes[idxNode].SavedState), + 0 /*fFlags*/, g_aCodecNodeFields, NULL /*pvUser*/); + return VINF_SUCCESS; +} + +int hdaCodecLoadState(PHDACODEC pThis, PSSMHANDLE pSSM, uint32_t uVersion) +{ + int rc = VINF_SUCCESS; + + PCSSMFIELD pFields = NULL; + uint32_t fFlags = 0; + switch (uVersion) + { + case HDA_SSM_VERSION_1: + AssertReturn(pThis->cTotalNodes == 0x1c, VERR_INTERNAL_ERROR); + pFields = g_aCodecNodeFieldsV1; + fFlags = SSMSTRUCT_FLAGS_MEM_BAND_AID_RELAXED; + break; + + case HDA_SSM_VERSION_2: + case HDA_SSM_VERSION_3: + AssertReturn(pThis->cTotalNodes == 0x1c, VERR_INTERNAL_ERROR); + pFields = g_aCodecNodeFields; + fFlags = SSMSTRUCT_FLAGS_MEM_BAND_AID_RELAXED; + break; + + /* Since version 4 a flexible node count is supported. */ + case HDA_SSM_VERSION_4: + case HDA_SSM_VERSION_5: + case HDA_SSM_VERSION: + { + uint32_t cNodes; + int rc2 = SSMR3GetU32(pSSM, &cNodes); + AssertRCReturn(rc2, rc2); + if (cNodes != 0x1c) + return VERR_SSM_DATA_UNIT_FORMAT_CHANGED; + AssertReturn(pThis->cTotalNodes == 0x1c, VERR_INTERNAL_ERROR); + + pFields = g_aCodecNodeFields; + fFlags = 0; + break; + } + + default: + rc = VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + break; + } + + if (RT_SUCCESS(rc)) + { + for (unsigned idxNode = 0; idxNode < pThis->cTotalNodes; ++idxNode) + { + uint8_t idOld = pThis->paNodes[idxNode].SavedState.Core.uID; + int rc2 = SSMR3GetStructEx(pSSM, &pThis->paNodes[idxNode].SavedState, + sizeof(pThis->paNodes[idxNode].SavedState), + fFlags, pFields, NULL); + if (RT_SUCCESS(rc)) + rc = rc2; + + if (RT_FAILURE(rc)) + break; + + AssertLogRelMsgReturn(idOld == pThis->paNodes[idxNode].SavedState.Core.uID, + ("loaded %#x, expected %#x\n", pThis->paNodes[idxNode].SavedState.Core.uID, idOld), + VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + } + + if (RT_SUCCESS(rc)) + { + /* + * Update stuff after changing the state. + */ + PCODECNODE pNode; + if (hdaCodecIsDacNode(pThis, pThis->u8DacLineOut)) + { + pNode = &pThis->paNodes[pThis->u8DacLineOut]; + hdaCodecToAudVolume(pThis, pNode, &pNode->dac.B_params, PDMAUDIOMIXERCTL_FRONT); + } + else if (hdaCodecIsSpdifOutNode(pThis, pThis->u8DacLineOut)) + { + pNode = &pThis->paNodes[pThis->u8DacLineOut]; + hdaCodecToAudVolume(pThis, pNode, &pNode->spdifout.B_params, PDMAUDIOMIXERCTL_FRONT); + } + + pNode = &pThis->paNodes[pThis->u8AdcVolsLineIn]; + hdaCodecToAudVolume(pThis, pNode, &pNode->adcvol.B_params, PDMAUDIOMIXERCTL_LINE_IN); + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Powers off the codec. + * + * @param pThis Codec to power off. + */ +void hdaCodecPowerOff(PHDACODEC pThis) +{ + if (!pThis) + return; + + LogFlowFuncEnter(); + + LogRel2(("HDA: Powering off codec ...\n")); + + int rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_FRONT); + AssertRC(rc2); +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_CENTER_LFE); + AssertRC(rc2); + rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_REAR); + AssertRC(rc2); +#endif + +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN + rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_MIC_IN); + AssertRC(rc2); +#endif + rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_LINE_IN); + AssertRC(rc2); +} + +void hdaCodecDestruct(PHDACODEC pThis) +{ + if (!pThis) + return; + + LogFlowFuncEnter(); + + if (pThis->paNodes) + { + RTMemFree(pThis->paNodes); + pThis->paNodes = NULL; + } +} + +int hdaCodecConstruct(PPDMDEVINS pDevIns, PHDACODEC pThis, + uint16_t uLUN, PCFGMNODE pCfg) +{ + AssertPtrReturn(pDevIns, VERR_INVALID_POINTER); + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + pThis->id = uLUN; + pThis->paVerbs = &g_aCodecVerbs[0]; + pThis->cVerbs = RT_ELEMENTS(g_aCodecVerbs); + +#ifdef DEBUG + pThis->pfnDbgSelector = codecDbgSelector; + pThis->pfnDbgListNodes = codecDbgListNodes; +#endif + pThis->pfnLookup = codecLookup; + + int rc = stac9220Construct(pThis); + AssertRCReturn(rc, rc); + + /* Common root node initializers. */ + pThis->paNodes[STAC9220_NID_ROOT].root.node.au32F00_param[0] = CODEC_MAKE_F00_00(pThis->u16VendorId, pThis->u16DeviceId); + pThis->paNodes[STAC9220_NID_ROOT].root.node.au32F00_param[4] = CODEC_MAKE_F00_04(0x1, 0x1); + + /* Common AFG node initializers. */ + pThis->paNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0x4] = CODEC_MAKE_F00_04(0x2, pThis->cTotalNodes - 2); + pThis->paNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0x5] = CODEC_MAKE_F00_05(1, CODEC_F00_05_AFG); + pThis->paNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0xA] = CODEC_F00_0A_44_1KHZ | CODEC_F00_0A_16_BIT; + pThis->paNodes[STAC9220_NID_AFG].afg.u32F20_param = CODEC_MAKE_F20(pThis->u16VendorId, pThis->u8BSKU, pThis->u8AssemblyId); + + /* + * Set initial volume. + */ + PCODECNODE pNode = &pThis->paNodes[pThis->u8DacLineOut]; + hdaCodecToAudVolume(pThis, pNode, &pNode->dac.B_params, PDMAUDIOMIXERCTL_FRONT); + + pNode = &pThis->paNodes[pThis->u8AdcVolsLineIn]; + hdaCodecToAudVolume(pThis, pNode, &pNode->adcvol.B_params, PDMAUDIOMIXERCTL_LINE_IN); +#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN +# error "Implement mic-in support!" +#endif + + LogFlowFuncLeaveRC(rc); + return rc; +} + diff --git a/src/VBox/Devices/Audio/HDACodec.h b/src/VBox/Devices/Audio/HDACodec.h new file mode 100644 index 00000000..56aaf756 --- /dev/null +++ b/src/VBox/Devices/Audio/HDACodec.h @@ -0,0 +1,148 @@ +/* $Id: HDACodec.h $ */ +/** @file + * HDACodec - VBox HD Audio Codec. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_HDACodec_h +#define VBOX_INCLUDED_SRC_Audio_HDACodec_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/list.h> + +#include "AudioMixer.h" + +/** The ICH HDA (Intel) controller. */ +typedef struct HDASTATE *PHDASTATE; +/** The ICH HDA (Intel) codec state. */ +typedef struct HDACODEC *PHDACODEC; +/** The HDA host driver backend. */ +typedef struct HDADRIVER *PHDADRIVER; +typedef struct PDMIAUDIOCONNECTOR *PPDMIAUDIOCONNECTOR; +typedef struct PDMAUDIOGSTSTRMOUT *PPDMAUDIOGSTSTRMOUT; +typedef struct PDMAUDIOGSTSTRMIN *PPDMAUDIOGSTSTRMIN; + +/** + * Verb processor method. + */ +typedef DECLCALLBACK(int) FNHDACODECVERBPROCESSOR(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp); +typedef FNHDACODECVERBPROCESSOR *PFNHDACODECVERBPROCESSOR; +typedef FNHDACODECVERBPROCESSOR **PPFNHDACODECVERBPROCESSOR; + +/* PRM 5.3.1 */ +#define CODEC_RESPONSE_UNSOLICITED RT_BIT_64(34) + +typedef struct CODECVERB +{ + /** Verb. */ + uint32_t verb; + /** Verb mask. */ + uint32_t mask; + /** Function pointer for implementation callback. */ + PFNHDACODECVERBPROCESSOR pfn; + /** Friendly name, for debugging. */ + const char *pszName; +} CODECVERB; + +union CODECNODE; +typedef union CODECNODE CODECNODE, *PCODECNODE; + +/** + * Structure for keeping a HDA codec state. + */ +typedef struct HDACODEC +{ + uint16_t id; + uint16_t u16VendorId; + uint16_t u16DeviceId; + uint8_t u8BSKU; + uint8_t u8AssemblyId; + /** List of assigned HDA drivers to this codec. + * A driver only can be assigned to one codec at a time. */ + RTLISTANCHOR lstDrv; + + CODECVERB const *paVerbs; + size_t cVerbs; + + PCODECNODE paNodes; + /** Pointer to HDA state (controller) this + * codec is assigned to. */ + PHDASTATE pHDAState; + bool fInReset; + + const uint8_t cTotalNodes; + const uint8_t *au8Ports; + const uint8_t *au8Dacs; + const uint8_t *au8AdcVols; + const uint8_t *au8Adcs; + const uint8_t *au8AdcMuxs; + const uint8_t *au8Pcbeeps; + const uint8_t *au8SpdifIns; + const uint8_t *au8SpdifOuts; + const uint8_t *au8DigInPins; + const uint8_t *au8DigOutPins; + const uint8_t *au8Cds; + const uint8_t *au8VolKnobs; + const uint8_t *au8Reserveds; + const uint8_t u8AdcVolsLineIn; + const uint8_t u8DacLineOut; + + /** Public codec functions. */ + DECLR3CALLBACKMEMBER(int, pfnLookup, (PHDACODEC pThis, uint32_t uVerb, uint64_t *puResp)); + DECLR3CALLBACKMEMBER(void, pfnReset, (PHDACODEC pThis)); + DECLR3CALLBACKMEMBER(int, pfnNodeReset, (PHDACODEC pThis, uint8_t, PCODECNODE)); + + /** Callbacks to the HDA controller, mostly used for multiplexing to the various host backends. */ + DECLR3CALLBACKMEMBER(int, pfnCbMixerAddStream, (PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg)); + DECLR3CALLBACKMEMBER(int, pfnCbMixerRemoveStream, (PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl)); + DECLR3CALLBACKMEMBER(int, pfnCbMixerControl, (PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl, uint8_t uSD, uint8_t uChannel)); + DECLR3CALLBACKMEMBER(int, pfnCbMixerSetVolume, (PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOVOLUME pVol)); + + /** These callbacks are set by codec implementation to answer debugger requests. */ + DECLR3CALLBACKMEMBER(void, pfnDbgListNodes, (PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs)); + DECLR3CALLBACKMEMBER(void, pfnDbgSelector, (PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs)); +} HDACODEC; + +int hdaCodecConstruct(PPDMDEVINS pDevIns, PHDACODEC pThis, uint16_t uLUN, PCFGMNODE pCfg); +void hdaCodecDestruct(PHDACODEC pThis); +void hdaCodecPowerOff(PHDACODEC pThis); +int hdaCodecSaveState(PHDACODEC pThis, PSSMHANDLE pSSM); +int hdaCodecLoadState(PHDACODEC pThis, PSSMHANDLE pSSM, uint32_t uVersion); +int hdaCodecAddStream(PHDACODEC pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg); +int hdaCodecRemoveStream(PHDACODEC pThis, PDMAUDIOMIXERCTL enmMixerCtl); + +/** Added (Controller): Current wall clock value (this independent from WALCLK register value). + * Added (Controller): Current IRQ level. + * Added (Per stream): Ring buffer. This is optional and can be skipped if (not) needed. + * Added (Per stream): Struct g_aSSMStreamStateFields7. + * Added (Per stream): Struct g_aSSMStreamPeriodFields7. + * Added (Current BDLE per stream): Struct g_aSSMBDLEDescFields7. + * Added (Current BDLE per stream): Struct g_aSSMBDLEStateFields7. */ +#define HDA_SSM_VERSION 7 +/** Saves the current BDLE state. */ +#define HDA_SSM_VERSION_6 6 +/** Introduced dynamic number of streams + stream identifiers for serialization. + * Bug: Did not save the BDLE states correctly. + * Those will be skipped on load then. */ +#define HDA_SSM_VERSION_5 5 +/** Since this version the number of MMIO registers can be flexible. */ +#define HDA_SSM_VERSION_4 4 +#define HDA_SSM_VERSION_3 3 +#define HDA_SSM_VERSION_2 2 +#define HDA_SSM_VERSION_1 1 + +#endif /* !VBOX_INCLUDED_SRC_Audio_HDACodec_h */ + diff --git a/src/VBox/Devices/Audio/HDAStream.cpp b/src/VBox/Devices/Audio/HDAStream.cpp new file mode 100644 index 00000000..d4e14b6e --- /dev/null +++ b/src/VBox/Devices/Audio/HDAStream.cpp @@ -0,0 +1,2009 @@ +/* $Id: HDAStream.cpp $ */ +/** @file + * HDAStream.cpp - Stream functions for HD Audio. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_HDA +#include <VBox/log.h> + +#include <iprt/mem.h> +#include <iprt/semaphore.h> + +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include "DrvAudio.h" + +#include "DevHDA.h" +#include "HDAStream.h" + + +#ifdef IN_RING3 + +/** + * Creates an HDA stream. + * + * @returns IPRT status code. + * @param pStream HDA stream to create. + * @param pThis HDA state to assign the HDA stream to. + * @param u8SD Stream descriptor number to assign. + */ +int hdaR3StreamCreate(PHDASTREAM pStream, PHDASTATE pThis, uint8_t u8SD) +{ + RT_NOREF(pThis); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + pStream->u8SD = u8SD; + pStream->pMixSink = NULL; + pStream->pHDAState = pThis; + pStream->pTimer = pThis->pTimer[u8SD]; + AssertPtr(pStream->pTimer); + + pStream->State.fInReset = false; + pStream->State.fRunning = false; +#ifdef HDA_USE_DMA_ACCESS_HANDLER + RTListInit(&pStream->State.lstDMAHandlers); +#endif + + int rc = RTCritSectInit(&pStream->CritSect); + AssertRCReturn(rc, rc); + + rc = hdaR3StreamPeriodCreate(&pStream->State.Period); + AssertRCReturn(rc, rc); + + pStream->State.tsLastUpdateNs = 0; + +#ifdef DEBUG + rc = RTCritSectInit(&pStream->Dbg.CritSect); + AssertRCReturn(rc, rc); +#endif + + pStream->Dbg.Runtime.fEnabled = pThis->Dbg.fEnabled; + + if (pStream->Dbg.Runtime.fEnabled) + { + char szFile[64]; + + if (hdaGetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN) + RTStrPrintf(szFile, sizeof(szFile), "hdaStreamWriteSD%RU8", pStream->u8SD); + else + RTStrPrintf(szFile, sizeof(szFile), "hdaStreamReadSD%RU8", pStream->u8SD); + + char szPath[RTPATH_MAX + 1]; + int rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThis->Dbg.szOutPath, szFile, + 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE); + AssertRC(rc2); + rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAG_NONE, &pStream->Dbg.Runtime.pFileStream); + AssertRC(rc2); + + if (hdaGetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN) + RTStrPrintf(szFile, sizeof(szFile), "hdaDMARawWriteSD%RU8", pStream->u8SD); + else + RTStrPrintf(szFile, sizeof(szFile), "hdaDMARawReadSD%RU8", pStream->u8SD); + + rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThis->Dbg.szOutPath, szFile, + 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE); + AssertRC(rc2); + + rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAG_NONE, &pStream->Dbg.Runtime.pFileDMARaw); + AssertRC(rc2); + + if (hdaGetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN) + RTStrPrintf(szFile, sizeof(szFile), "hdaDMAWriteMappedSD%RU8", pStream->u8SD); + else + RTStrPrintf(szFile, sizeof(szFile), "hdaDMAReadMappedSD%RU8", pStream->u8SD); + + rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThis->Dbg.szOutPath, szFile, + 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE); + AssertRC(rc2); + + rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAG_NONE, &pStream->Dbg.Runtime.pFileDMAMapped); + AssertRC(rc2); + + /* Delete stale debugging files from a former run. */ + DrvAudioHlpFileDelete(pStream->Dbg.Runtime.pFileStream); + DrvAudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMARaw); + DrvAudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMAMapped); + } + + return rc; +} + +/** + * Destroys an HDA stream. + * + * @param pStream HDA stream to destroy. + */ +void hdaR3StreamDestroy(PHDASTREAM pStream) +{ + AssertPtrReturnVoid(pStream); + + LogFlowFunc(("[SD%RU8] Destroying ...\n", pStream->u8SD)); + + hdaR3StreamMapDestroy(&pStream->State.Mapping); + + int rc2; + +#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + rc2 = hdaR3StreamAsyncIODestroy(pStream); + AssertRC(rc2); +#endif + + if (RTCritSectIsInitialized(&pStream->CritSect)) + { + rc2 = RTCritSectDelete(&pStream->CritSect); + AssertRC(rc2); + } + + if (pStream->State.pCircBuf) + { + RTCircBufDestroy(pStream->State.pCircBuf); + pStream->State.pCircBuf = NULL; + } + + hdaR3StreamPeriodDestroy(&pStream->State.Period); + +#ifdef DEBUG + if (RTCritSectIsInitialized(&pStream->Dbg.CritSect)) + { + rc2 = RTCritSectDelete(&pStream->Dbg.CritSect); + AssertRC(rc2); + } +#endif + + if (pStream->Dbg.Runtime.fEnabled) + { + DrvAudioHlpFileDestroy(pStream->Dbg.Runtime.pFileStream); + pStream->Dbg.Runtime.pFileStream = NULL; + + DrvAudioHlpFileDestroy(pStream->Dbg.Runtime.pFileDMARaw); + pStream->Dbg.Runtime.pFileDMARaw = NULL; + + DrvAudioHlpFileDestroy(pStream->Dbg.Runtime.pFileDMAMapped); + pStream->Dbg.Runtime.pFileDMAMapped = NULL; + } + + LogFlowFuncLeave(); +} + +/** + * Initializes an HDA stream. + * + * @returns IPRT status code. VINF_NO_CHANGE if the stream does not need (re-)initialization because the stream's (hardware) + * parameters did not change. + * @param pStream HDA stream to initialize. + * @param uSD SD (stream descriptor) number to assign the HDA stream to. + */ +int hdaR3StreamInit(PHDASTREAM pStream, uint8_t uSD) +{ + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PHDASTATE pThis = pStream->pHDAState; + AssertPtr(pThis); + + const uint64_t u64BDLBase = RT_MAKE_U64(HDA_STREAM_REG(pThis, BDPL, uSD), + HDA_STREAM_REG(pThis, BDPU, uSD)); + const uint16_t u16LVI = HDA_STREAM_REG(pThis, LVI, uSD); + const uint32_t u32CBL = HDA_STREAM_REG(pThis, CBL, uSD); + const uint16_t u16FIFOS = HDA_STREAM_REG(pThis, FIFOS, uSD) + 1; + const uint16_t u16FMT = HDA_STREAM_REG(pThis, FMT, uSD); + + /* Is the bare minimum set of registers configured for the stream? + * If not, bail out early, as there's nothing to do here for us (yet). */ + if ( !u64BDLBase + || !u16LVI + || !u32CBL + || !u16FIFOS + || !u16FMT) + { + LogFunc(("[SD%RU8] Registers not set up yet, skipping (re-)initialization\n", uSD)); + return VINF_SUCCESS; + } + + PDMAUDIOPCMPROPS Props; + int rc = hdaR3SDFMTToPCMProps(u16FMT, &Props); + if (RT_FAILURE(rc)) + { + LogRel(("HDA: Warning: Format 0x%x for stream #%RU8 not supported\n", HDA_STREAM_REG(pThis, FMT, uSD), uSD)); + return rc; + } + + /* Reset (any former) stream map. */ + hdaR3StreamMapReset(&pStream->State.Mapping); + + /* + * Initialize the stream mapping in any case, regardless if + * we support surround audio or not. This is needed to handle + * the supported channels within a single audio stream, e.g. mono/stereo. + * + * In other words, the stream mapping *always* knows the real + * number of channels in a single audio stream. + */ + rc = hdaR3StreamMapInit(&pStream->State.Mapping, &Props); + AssertRCReturn(rc, rc); + + /* + * Set the stream's timer Hz rate, based on the stream channel count. + * Currently this is just a rough guess and we might want to optimize this further. + * + * In any case, more channels per SDI/SDO means that we have to drive data more frequently. + */ + if (pThis->uTimerHz == HDA_TIMER_HZ_DEFAULT) /* Make sure that we don't have any custom Hz rate set we want to enforce */ + { + if (Props.cChannels >= 5) + pStream->State.uTimerHz = 300; + else if (Props.cChannels == 4) + pStream->State.uTimerHz = 150; + else + pStream->State.uTimerHz = 100; + } + else + pStream->State.uTimerHz = pThis->uTimerHz; + +#ifndef VBOX_WITH_AUDIO_HDA_51_SURROUND + if (Props.cChannels > 2) + { + /* + * When not running with surround support enabled, override the audio channel count + * with stereo (2) channels so that we at least can properly work with those. + * + * Note: This also involves dealing with surround setups the guest might has set up for us. + */ + LogRel2(("HDA: More than stereo (2) channels are not supported (%RU8 requested), " + "falling back to stereo channels for stream #%RU8\n", Props.cChannels, uSD)); + Props.cChannels = 2; + Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(Props.cBytes, Props.cChannels); + } +#endif + + /* Did some of the vital / critical parameters change? + * If not, we can skip a lot of the (re-)initialization and just (re-)use the existing stuff. + * Also, tell the caller so that further actions can be taken. */ + if ( uSD == pStream->u8SD + && u64BDLBase == pStream->u64BDLBase + && u16LVI == pStream->u16LVI + && u32CBL == pStream->u32CBL + && u16FIFOS == pStream->u16FIFOS + && u16FMT == pStream->u16FMT) + { + LogFunc(("[SD%RU8] No format change, skipping (re-)initialization\n", uSD)); + return VINF_NO_CHANGE; + } + + pStream->u8SD = uSD; + + /* Update all register copies so that we later know that something has changed. */ + pStream->u64BDLBase = u64BDLBase; + pStream->u16LVI = u16LVI; + pStream->u32CBL = u32CBL; + pStream->u16FIFOS = u16FIFOS; + pStream->u16FMT = u16FMT; + + PPDMAUDIOSTREAMCFG pCfg = &pStream->State.Cfg; + pCfg->Props = Props; + + /* (Re-)Allocate the stream's internal DMA buffer, based on the PCM properties we just got above. */ + if (pStream->State.pCircBuf) + { + RTCircBufDestroy(pStream->State.pCircBuf); + pStream->State.pCircBuf = NULL; + } + + /* By default we allocate an internal buffer of 100ms. */ + rc = RTCircBufCreate(&pStream->State.pCircBuf, + DrvAudioHlpMilliToBytes(100 /* ms */, &pCfg->Props)); /** @todo Make this configurable. */ + AssertRCReturn(rc, rc); + + /* Set the stream's direction. */ + pCfg->enmDir = hdaGetDirFromSD(pStream->u8SD); + + /* The the stream's name, based on the direction. */ + switch (pCfg->enmDir) + { + case PDMAUDIODIR_IN: +# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN +# error "Implement me!" +# else + pCfg->DestSource.Source = PDMAUDIORECSOURCE_LINE; + pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; + RTStrCopy(pCfg->szName, sizeof(pCfg->szName), "Line In"); +# endif + break; + + case PDMAUDIODIR_OUT: + /* Destination(s) will be set in hdaAddStreamOut(), + * based on the channels / stream layout. */ + break; + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + /* Set scheduling hint (if available). */ + if (pStream->State.uTimerHz) + pCfg->Device.uSchedulingHintMs = 1000 /* ms */ / pStream->State.uTimerHz; + + LogFunc(("[SD%RU8] DMA @ 0x%x (%RU32 bytes), LVI=%RU16, FIFOS=%RU16\n", + pStream->u8SD, pStream->u64BDLBase, pStream->u32CBL, pStream->u16LVI, pStream->u16FIFOS)); + + /* Make sure that mandatory parameters are set up correctly. */ + AssertStmt(pStream->u32CBL % pStream->State.Mapping.cbFrameSize == 0, rc = VERR_INVALID_PARAMETER); + AssertStmt(pStream->u16LVI >= 1, rc = VERR_INVALID_PARAMETER); + + if (RT_SUCCESS(rc)) + { + /* Make sure that the chosen Hz rate dividable by the stream's rate. */ + if (pStream->State.Cfg.Props.uHz % pStream->State.uTimerHz != 0) + LogRel(("HDA: Stream timer Hz rate (%RU32) does not fit to stream #%RU8 timing (%RU32)\n", + pStream->State.uTimerHz, pStream->u8SD, pStream->State.Cfg.Props.uHz)); + + /* Figure out how many transfer fragments we're going to use for this stream. */ + /** @todo Use a more dynamic fragment size? */ + Assert(pStream->u16LVI <= UINT8_MAX - 1); + uint8_t cFragments = pStream->u16LVI + 1; + if (cFragments <= 1) + cFragments = 2; /* At least two fragments (BDLEs) must be present. */ + + /* + * Handle the stream's position adjustment. + */ + uint32_t cfPosAdjust = 0; + + LogFunc(("[SD%RU8] fPosAdjustEnabled=%RTbool, cPosAdjustFrames=%RU16\n", + pStream->u8SD, pThis->fPosAdjustEnabled, pThis->cPosAdjustFrames)); + + if (pThis->fPosAdjustEnabled) /* Is the position adjustment enabled at all? */ + { + HDABDLE BDLE; + RT_ZERO(BDLE); + + int rc2 = hdaR3BDLEFetch(pThis, &BDLE, pStream->u64BDLBase, 0 /* Entry */); + AssertRC(rc2); + + /* Note: Do *not* check if this BDLE aligns to the stream's frame size. + * It can happen that this isn't the case on some guests, e.g. + * on Windows with a 5.1 speaker setup. + * + * The only thing which counts is that the stream's CBL value + * properly aligns to the stream's frame size. + */ + + /* If no custom set position adjustment is set, apply some + * simple heuristics to detect the appropriate position adjustment. */ + if ( !pThis->cPosAdjustFrames + /* Position adjustmenet buffer *must* have the IOC bit set! */ + && hdaR3BDLENeedsInterrupt(&BDLE)) + { + /** @todo Implement / use a (dynamic) table once this gets more complicated. */ +#ifdef VBOX_WITH_INTEL_HDA + /* Intel ICH / PCH: 1 frame. */ + if (BDLE.Desc.u32BufSize == (uint32_t)(1 * pStream->State.Mapping.cbFrameSize)) + { + cfPosAdjust = 1; + } + /* Intel Baytrail / Braswell: 32 frames. */ + else if (BDLE.Desc.u32BufSize == (uint32_t)(32 * pStream->State.Mapping.cbFrameSize)) + { + cfPosAdjust = 32; + } +#endif + } + else /* Go with the set default. */ + cfPosAdjust = pThis->cPosAdjustFrames; + + if (cfPosAdjust) + { + /* Also adjust the number of fragments, as the position adjustment buffer + * does not count as an own fragment as such. + * + * This e.g. can happen on (newer) Ubuntu guests which use + * 4 (IOC) + 4408 (IOC) + 4408 (IOC) + 4408 (IOC) + 4404 (= 17632) bytes, + * where the first buffer (4) is used as position adjustment. + * + * Only skip a fragment if the whole buffer fragment is used for + * position adjustment. + */ + if ( (cfPosAdjust * pStream->State.Mapping.cbFrameSize) == BDLE.Desc.u32BufSize + && cFragments) + { + cFragments--; + } + + /* Initialize position adjustment counter. */ + pStream->State.cfPosAdjustDefault = cfPosAdjust; + pStream->State.cfPosAdjustLeft = pStream->State.cfPosAdjustDefault; + + LogRel2(("HDA: Position adjustment for stream #%RU8 active (%RU32 frames)\n", + pStream->u8SD, pStream->State.cfPosAdjustDefault)); + } + } + + LogFunc(("[SD%RU8] cfPosAdjust=%RU32, cFragments=%RU8\n", pStream->u8SD, cfPosAdjust, cFragments)); + + /* + * Set up data transfer stuff. + */ + + /* Calculate the fragment size the guest OS expects interrupt delivery at. */ + pStream->State.cbTransferSize = pStream->u32CBL / cFragments; + Assert(pStream->State.cbTransferSize); + Assert(pStream->State.cbTransferSize % pStream->State.Mapping.cbFrameSize == 0); + + /* Calculate the bytes we need to transfer to / from the stream's DMA per iteration. + * This is bound to the device's Hz rate and thus to the (virtual) timing the device expects. */ + pStream->State.cbTransferChunk = (pStream->State.Cfg.Props.uHz / pStream->State.uTimerHz) * pStream->State.Mapping.cbFrameSize; + Assert(pStream->State.cbTransferChunk); + Assert(pStream->State.cbTransferChunk % pStream->State.Mapping.cbFrameSize == 0); + + /* Make sure that the transfer chunk does not exceed the overall transfer size. */ + if (pStream->State.cbTransferChunk > pStream->State.cbTransferSize) + pStream->State.cbTransferChunk = pStream->State.cbTransferSize; + + const uint64_t cTicksPerHz = TMTimerGetFreq(pStream->pTimer) / pStream->State.uTimerHz; + + /* Calculate the timer ticks per byte for this stream. */ + pStream->State.cTicksPerByte = cTicksPerHz / pStream->State.cbTransferChunk; + Assert(pStream->State.cTicksPerByte); + + /* Calculate timer ticks per transfer. */ + pStream->State.cTransferTicks = pStream->State.cbTransferChunk * pStream->State.cTicksPerByte; + Assert(pStream->State.cTransferTicks); + + LogFunc(("[SD%RU8] Timer %uHz (%RU64 ticks per Hz), cTicksPerByte=%RU64, cbTransferChunk=%RU32, cTransferTicks=%RU64, " \ + "cbTransferSize=%RU32\n", + pStream->u8SD, pStream->State.uTimerHz, cTicksPerHz, pStream->State.cTicksPerByte, + pStream->State.cbTransferChunk, pStream->State.cTransferTicks, pStream->State.cbTransferSize)); + + /* Make sure to also update the stream's DMA counter (based on its current LPIB value). */ + hdaR3StreamSetPosition(pStream, HDA_STREAM_REG(pThis, LPIB, pStream->u8SD)); + +#ifdef LOG_ENABLED + hdaR3BDLEDumpAll(pThis, pStream->u64BDLBase, pStream->u16LVI + 1); +#endif + } + + if (RT_FAILURE(rc)) + LogRel(("HDA: Initializing stream #%RU8 failed with %Rrc\n", pStream->u8SD, rc)); + + return rc; +} + +/** + * Resets an HDA stream. + * + * @param pThis HDA state. + * @param pStream HDA stream to reset. + * @param uSD Stream descriptor (SD) number to use for this stream. + */ +void hdaR3StreamReset(PHDASTATE pThis, PHDASTREAM pStream, uint8_t uSD) +{ + AssertPtrReturnVoid(pThis); + AssertPtrReturnVoid(pStream); + AssertReturnVoid(uSD < HDA_MAX_STREAMS); + +# ifdef VBOX_STRICT + AssertReleaseMsg(!pStream->State.fRunning, ("[SD%RU8] Cannot reset stream while in running state\n", uSD)); +# endif + + LogFunc(("[SD%RU8] Reset\n", uSD)); + + /* + * Set reset state. + */ + Assert(ASMAtomicReadBool(&pStream->State.fInReset) == false); /* No nested calls. */ + ASMAtomicXchgBool(&pStream->State.fInReset, true); + + /* + * Second, initialize the registers. + */ + HDA_STREAM_REG(pThis, STS, uSD) = HDA_SDSTS_FIFORDY; + /* According to the ICH6 datasheet, 0x40000 is the default value for stream descriptor register 23:20 + * bits are reserved for stream number 18.2.33, resets SDnCTL except SRST bit. */ + HDA_STREAM_REG(pThis, CTL, uSD) = 0x40000 | (HDA_STREAM_REG(pThis, CTL, uSD) & HDA_SDCTL_SRST); + /* ICH6 defines default values (120 bytes for input and 192 bytes for output descriptors) of FIFO size. 18.2.39. */ + HDA_STREAM_REG(pThis, FIFOS, uSD) = hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN ? HDA_SDIFIFO_120B : HDA_SDOFIFO_192B; + /* See 18.2.38: Always defaults to 0x4 (32 bytes). */ + HDA_STREAM_REG(pThis, FIFOW, uSD) = HDA_SDFIFOW_32B; + HDA_STREAM_REG(pThis, LPIB, uSD) = 0; + HDA_STREAM_REG(pThis, CBL, uSD) = 0; + HDA_STREAM_REG(pThis, LVI, uSD) = 0; + HDA_STREAM_REG(pThis, FMT, uSD) = 0; + HDA_STREAM_REG(pThis, BDPU, uSD) = 0; + HDA_STREAM_REG(pThis, BDPL, uSD) = 0; + +#ifdef HDA_USE_DMA_ACCESS_HANDLER + hdaR3StreamUnregisterDMAHandlers(pThis, pStream); +#endif + + /* Assign the default mixer sink to the stream. */ + pStream->pMixSink = hdaR3GetDefaultSink(pThis, uSD); + + /* Reset position adjustment counter. */ + pStream->State.cfPosAdjustLeft = pStream->State.cfPosAdjustDefault; + + /* Reset transfer stuff. */ + pStream->State.cbTransferProcessed = 0; + pStream->State.cTransferPendingInterrupts = 0; + pStream->State.tsTransferLast = 0; + pStream->State.tsTransferNext = 0; + + /* Initialize other timestamps. */ + pStream->State.tsLastUpdateNs = 0; + + RT_ZERO(pStream->State.BDLE); + pStream->State.uCurBDLE = 0; + + if (pStream->State.pCircBuf) + RTCircBufReset(pStream->State.pCircBuf); + + /* Reset the stream's period. */ + hdaR3StreamPeriodReset(&pStream->State.Period); + +#ifdef DEBUG + pStream->Dbg.cReadsTotal = 0; + pStream->Dbg.cbReadTotal = 0; + pStream->Dbg.tsLastReadNs = 0; + pStream->Dbg.cWritesTotal = 0; + pStream->Dbg.cbWrittenTotal = 0; + pStream->Dbg.cWritesHz = 0; + pStream->Dbg.cbWrittenHz = 0; + pStream->Dbg.tsWriteSlotBegin = 0; +#endif + + /* Report that we're done resetting this stream. */ + HDA_STREAM_REG(pThis, CTL, uSD) = 0; + + LogFunc(("[SD%RU8] Reset\n", uSD)); + + /* Exit reset mode. */ + ASMAtomicXchgBool(&pStream->State.fInReset, false); +} + +/** + * Enables or disables an HDA audio stream. + * + * @returns IPRT status code. + * @param pStream HDA stream to enable or disable. + * @param fEnable Whether to enable or disble the stream. + */ +int hdaR3StreamEnable(PHDASTREAM pStream, bool fEnable) +{ + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + LogFunc(("[SD%RU8] fEnable=%RTbool, pMixSink=%p\n", pStream->u8SD, fEnable, pStream->pMixSink)); + + int rc = VINF_SUCCESS; + + AUDMIXSINKCMD enmCmd = fEnable + ? AUDMIXSINKCMD_ENABLE : AUDMIXSINKCMD_DISABLE; + + /* First, enable or disable the stream and the stream's sink, if any. */ + if ( pStream->pMixSink + && pStream->pMixSink->pMixSink) + rc = AudioMixerSinkCtl(pStream->pMixSink->pMixSink, enmCmd); + + if ( RT_SUCCESS(rc) + && fEnable + && pStream->Dbg.Runtime.fEnabled) + { + Assert(DrvAudioHlpPCMPropsAreValid(&pStream->State.Cfg.Props)); + + if (fEnable) + { + if (!DrvAudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileStream)) + { + int rc2 = DrvAudioHlpFileOpen(pStream->Dbg.Runtime.pFileStream, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, + &pStream->State.Cfg.Props); + AssertRC(rc2); + } + + if (!DrvAudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileDMARaw)) + { + int rc2 = DrvAudioHlpFileOpen(pStream->Dbg.Runtime.pFileDMARaw, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, + &pStream->State.Cfg.Props); + AssertRC(rc2); + } + + if (!DrvAudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileDMAMapped)) + { + int rc2 = DrvAudioHlpFileOpen(pStream->Dbg.Runtime.pFileDMAMapped, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, + &pStream->State.Cfg.Props); + AssertRC(rc2); + } + } + } + + if (RT_SUCCESS(rc)) + { + pStream->State.fRunning = fEnable; + } + + LogFunc(("[SD%RU8] rc=%Rrc\n", pStream->u8SD, rc)); + return rc; +} + +uint32_t hdaR3StreamGetPosition(PHDASTATE pThis, PHDASTREAM pStream) +{ + return HDA_STREAM_REG(pThis, LPIB, pStream->u8SD); +} + +/** + * Updates an HDA stream's current read or write buffer position (depending on the stream type) by + * updating its associated LPIB register and DMA position buffer (if enabled). + * + * @param pStream HDA stream to update read / write position for. + * @param u32LPIB Absolute position (in bytes) to set current read / write position to. + */ +void hdaR3StreamSetPosition(PHDASTREAM pStream, uint32_t u32LPIB) +{ + AssertPtrReturnVoid(pStream); + + Log3Func(("[SD%RU8] LPIB=%RU32 (DMA Position Buffer Enabled: %RTbool)\n", + pStream->u8SD, u32LPIB, pStream->pHDAState->fDMAPosition)); + + /* Update LPIB in any case. */ + HDA_STREAM_REG(pStream->pHDAState, LPIB, pStream->u8SD) = u32LPIB; + + /* Do we need to tell the current DMA position? */ + if (pStream->pHDAState->fDMAPosition) + { + int rc2 = PDMDevHlpPCIPhysWrite(pStream->pHDAState->CTX_SUFF(pDevIns), + pStream->pHDAState->u64DPBase + (pStream->u8SD * 2 * sizeof(uint32_t)), + (void *)&u32LPIB, sizeof(uint32_t)); + AssertRC(rc2); + } +} + +/** + * Retrieves the available size of (buffered) audio data (in bytes) of a given HDA stream. + * + * @returns Available data (in bytes). + * @param pStream HDA stream to retrieve size for. + */ +uint32_t hdaR3StreamGetUsed(PHDASTREAM pStream) +{ + AssertPtrReturn(pStream, 0); + + if (!pStream->State.pCircBuf) + return 0; + + return (uint32_t)RTCircBufUsed(pStream->State.pCircBuf); +} + +/** + * Retrieves the free size of audio data (in bytes) of a given HDA stream. + * + * @returns Free data (in bytes). + * @param pStream HDA stream to retrieve size for. + */ +uint32_t hdaR3StreamGetFree(PHDASTREAM pStream) +{ + AssertPtrReturn(pStream, 0); + + if (!pStream->State.pCircBuf) + return 0; + + return (uint32_t)RTCircBufFree(pStream->State.pCircBuf); +} + +/** + * Returns whether a next transfer for a given stream is scheduled or not. + * This takes pending stream interrupts into account as well as the next scheduled + * transfer timestamp. + * + * @returns True if a next transfer is scheduled, false if not. + * @param pStream HDA stream to retrieve schedule status for. + */ +bool hdaR3StreamTransferIsScheduled(PHDASTREAM pStream) +{ + if (pStream) + { + AssertPtrReturn(pStream->pHDAState, false); + + if (pStream->State.fRunning) + { + if (pStream->State.cTransferPendingInterrupts) + { + Log3Func(("[SD%RU8] Scheduled (%RU8 IRQs pending)\n", pStream->u8SD, pStream->State.cTransferPendingInterrupts)); + return true; + } + + const uint64_t tsNow = TMTimerGet(pStream->pTimer); + if (pStream->State.tsTransferNext > tsNow) + { + Log3Func(("[SD%RU8] Scheduled in %RU64\n", pStream->u8SD, pStream->State.tsTransferNext - tsNow)); + return true; + } + } + } + return false; +} + +/** + * Returns the (virtual) clock timestamp of the next transfer, if any. + * Will return 0 if no new transfer is scheduled. + * + * @returns The (virtual) clock timestamp of the next transfer. + * @param pStream HDA stream to retrieve timestamp for. + */ +uint64_t hdaR3StreamTransferGetNext(PHDASTREAM pStream) +{ + return pStream->State.tsTransferNext; +} + +/** + * Writes audio data from a mixer sink into an HDA stream's DMA buffer. + * + * @returns IPRT status code. + * @param pStream HDA stream to write to. + * @param pvBuf Data buffer to write. + * If NULL, silence will be written. + * @param cbBuf Number of bytes of data buffer to write. + * @param pcbWritten Number of bytes written. Optional. + */ +int hdaR3StreamWrite(PHDASTREAM pStream, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + /* pvBuf is optional. */ + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + /* pcbWritten is optional. */ + + PRTCIRCBUF pCircBuf = pStream->State.pCircBuf; + AssertPtr(pCircBuf); + + int rc = VINF_SUCCESS; + + uint32_t cbWrittenTotal = 0; + uint32_t cbLeft = RT_MIN(cbBuf, (uint32_t)RTCircBufFree(pCircBuf)); + + while (cbLeft) + { + void *pvDst; + size_t cbDst; + + RTCircBufAcquireWriteBlock(pCircBuf, cbLeft, &pvDst, &cbDst); + + if (cbDst) + { + if (pvBuf) + { + memcpy(pvDst, (uint8_t *)pvBuf + cbWrittenTotal, cbDst); + } + else /* Send silence. */ + { + /** @todo Use a sample spec for "silence" based on the PCM parameters. + * For now we ASSUME that silence equals NULLing the data. */ + RT_BZERO(pvDst, cbDst); + } + + if (pStream->Dbg.Runtime.fEnabled) + DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileStream, pvDst, cbDst, 0 /* fFlags */); + } + + RTCircBufReleaseWriteBlock(pCircBuf, cbDst); + + if (RT_FAILURE(rc)) + break; + + Assert(cbLeft >= (uint32_t)cbDst); + cbLeft -= (uint32_t)cbDst; + + cbWrittenTotal += (uint32_t)cbDst; + } + + Log3Func(("cbWrittenTotal=%RU32\n", cbWrittenTotal)); + + if (pcbWritten) + *pcbWritten = cbWrittenTotal; + + return rc; +} + + +/** + * Reads audio data from an HDA stream's DMA buffer and writes into a specified mixer sink. + * + * @returns IPRT status code. + * @param pStream HDA stream to read audio data from. + * @param cbToRead Number of bytes to read. + * @param pcbRead Number of bytes read. Optional. + */ +int hdaR3StreamRead(PHDASTREAM pStream, uint32_t cbToRead, uint32_t *pcbRead) +{ + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertReturn(cbToRead, VERR_INVALID_PARAMETER); + /* pcbWritten is optional. */ + + PHDAMIXERSINK pSink = pStream->pMixSink; + if (!pSink) + { + AssertMsgFailed(("[SD%RU8] Can't read from a stream with no sink attached\n", pStream->u8SD)); + + if (pcbRead) + *pcbRead = 0; + return VINF_SUCCESS; + } + + PRTCIRCBUF pCircBuf = pStream->State.pCircBuf; + AssertPtr(pCircBuf); + + int rc = VINF_SUCCESS; + + uint32_t cbReadTotal = 0; + uint32_t cbLeft = RT_MIN(cbToRead, (uint32_t)RTCircBufUsed(pCircBuf)); + + while (cbLeft) + { + void *pvSrc; + size_t cbSrc; + + uint32_t cbWritten = 0; + + RTCircBufAcquireReadBlock(pCircBuf, cbLeft, &pvSrc, &cbSrc); + + if (cbSrc) + { + if (pStream->Dbg.Runtime.fEnabled) + DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileStream, pvSrc, cbSrc, 0 /* fFlags */); + + rc = AudioMixerSinkWrite(pSink->pMixSink, AUDMIXOP_COPY, pvSrc, (uint32_t)cbSrc, &cbWritten); + AssertRC(rc); + + Assert(cbSrc >= cbWritten); + Log2Func(("[SD%RU8] %RU32/%zu bytes read\n", pStream->u8SD, cbWritten, cbSrc)); + } + + RTCircBufReleaseReadBlock(pCircBuf, cbWritten); + + if (RT_FAILURE(rc)) + break; + + Assert(cbLeft >= cbWritten); + cbLeft -= cbWritten; + + cbReadTotal += cbWritten; + } + + if (pcbRead) + *pcbRead = cbReadTotal; + + return rc; +} + +/** + * Transfers data of an HDA stream according to its usage (input / output). + * + * For an SDO (output) stream this means reading DMA data from the device to + * the HDA stream's internal FIFO buffer. + * + * For an SDI (input) stream this is reading audio data from the HDA stream's + * internal FIFO buffer and writing it as DMA data to the device. + * + * @returns IPRT status code. + * @param pStream HDA stream to update. + * @param cbToProcessMax How much data (in bytes) to process as maximum. + */ +int hdaR3StreamTransfer(PHDASTREAM pStream, uint32_t cbToProcessMax) +{ + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + hdaR3StreamLock(pStream); + + PHDASTATE pThis = pStream->pHDAState; + AssertPtr(pThis); + + PHDASTREAMPERIOD pPeriod = &pStream->State.Period; + if (!hdaR3StreamPeriodLock(pPeriod)) + return VERR_ACCESS_DENIED; + + bool fProceed = true; + + /* Stream not running? */ + if (!pStream->State.fRunning) + { + Log3Func(("[SD%RU8] Not running\n", pStream->u8SD)); + fProceed = false; + } + else if (HDA_STREAM_REG(pThis, STS, pStream->u8SD) & HDA_SDSTS_BCIS) + { + Log3Func(("[SD%RU8] BCIS bit set\n", pStream->u8SD)); + fProceed = false; + } + + if (!fProceed) + { + hdaR3StreamPeriodUnlock(pPeriod); + hdaR3StreamUnlock(pStream); + return VINF_SUCCESS; + } + + const uint64_t tsNow = TMTimerGet(pStream->pTimer); + + if (!pStream->State.tsTransferLast) + pStream->State.tsTransferLast = tsNow; + +#ifdef DEBUG + const int64_t iTimerDelta = tsNow - pStream->State.tsTransferLast; + Log3Func(("[SD%RU8] Time now=%RU64, last=%RU64 -> %RI64 ticks delta\n", + pStream->u8SD, tsNow, pStream->State.tsTransferLast, iTimerDelta)); +#endif + + pStream->State.tsTransferLast = tsNow; + + /* Sanity checks. */ + Assert(pStream->u8SD < HDA_MAX_STREAMS); + Assert(pStream->u64BDLBase); + Assert(pStream->u32CBL); + Assert(pStream->u16FIFOS); + + /* State sanity checks. */ + Assert(ASMAtomicReadBool(&pStream->State.fInReset) == false); + + int rc = VINF_SUCCESS; + + /* Fetch first / next BDL entry. */ + PHDABDLE pBDLE = &pStream->State.BDLE; + if (hdaR3BDLEIsComplete(pBDLE)) + { + rc = hdaR3BDLEFetch(pThis, pBDLE, pStream->u64BDLBase, pStream->State.uCurBDLE); + AssertRC(rc); + } + + uint32_t cbToProcess = RT_MIN(pStream->State.cbTransferSize - pStream->State.cbTransferProcessed, + pStream->State.cbTransferChunk); + + Log3Func(("[SD%RU8] cbToProcess=%RU32, cbToProcessMax=%RU32\n", pStream->u8SD, cbToProcess, cbToProcessMax)); + + if (cbToProcess > cbToProcessMax) + { + LogFunc(("[SD%RU8] Limiting transfer (cbToProcess=%RU32, cbToProcessMax=%RU32)\n", + pStream->u8SD, cbToProcess, cbToProcessMax)); + + /* Never process more than a stream currently can handle. */ + cbToProcess = cbToProcessMax; + } + + uint32_t cbProcessed = 0; + uint32_t cbLeft = cbToProcess; + + uint8_t abChunk[HDA_FIFO_MAX + 1]; + while (cbLeft) + { + /* Limit the chunk to the stream's FIFO size and what's left to process. */ + uint32_t cbChunk = RT_MIN(cbLeft, pStream->u16FIFOS); + + /* Limit the chunk to the remaining data of the current BDLE. */ + cbChunk = RT_MIN(cbChunk, pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff); + + /* If there are position adjustment frames left to be processed, + * make sure that we process them first as a whole. */ + if (pStream->State.cfPosAdjustLeft) + cbChunk = RT_MIN(cbChunk, uint32_t(pStream->State.cfPosAdjustLeft * pStream->State.Mapping.cbFrameSize)); + + Log3Func(("[SD%RU8] cbChunk=%RU32, cPosAdjustFramesLeft=%RU16\n", + pStream->u8SD, cbChunk, pStream->State.cfPosAdjustLeft)); + + if (!cbChunk) + break; + + uint32_t cbDMA = 0; + PRTCIRCBUF pCircBuf = pStream->State.pCircBuf; + + if (hdaGetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN) /* Input (SDI). */ + { + STAM_PROFILE_START(&pThis->StatIn, a); + + uint32_t cbDMAWritten = 0; + uint32_t cbDMAToWrite = cbChunk; + + /** @todo Do we need interleaving streams support here as well? + * Never saw anything else besides mono/stereo mics (yet). */ + while (cbDMAToWrite) + { + void *pvBuf; size_t cbBuf; + RTCircBufAcquireReadBlock(pCircBuf, cbDMAToWrite, &pvBuf, &cbBuf); + + if ( !cbBuf + && !RTCircBufUsed(pCircBuf)) + break; + + memcpy(abChunk + cbDMAWritten, pvBuf, cbBuf); + + RTCircBufReleaseReadBlock(pCircBuf, cbBuf); + + Assert(cbDMAToWrite >= cbBuf); + cbDMAToWrite -= (uint32_t)cbBuf; + cbDMAWritten += (uint32_t)cbBuf; + Assert(cbDMAWritten <= cbChunk); + } + + if (cbDMAToWrite) + { + LogRel2(("HDA: FIFO underflow for stream #%RU8 (%RU32 bytes outstanding)\n", pStream->u8SD, cbDMAToWrite)); + + Assert(cbChunk == cbDMAWritten + cbDMAToWrite); + memset((uint8_t *)abChunk + cbDMAWritten, 0, cbDMAToWrite); + cbDMAWritten = cbChunk; + } + + rc = hdaR3DMAWrite(pThis, pStream, abChunk, cbDMAWritten, &cbDMA /* pcbWritten */); + if (RT_FAILURE(rc)) + LogRel(("HDA: Writing to stream #%RU8 DMA failed with %Rrc\n", pStream->u8SD, rc)); + + STAM_PROFILE_STOP(&pThis->StatIn, a); + } + else if (hdaGetDirFromSD(pStream->u8SD) == PDMAUDIODIR_OUT) /* Output (SDO). */ + { + STAM_PROFILE_START(&pThis->StatOut, a); + + rc = hdaR3DMARead(pThis, pStream, abChunk, cbChunk, &cbDMA /* pcbRead */); + if (RT_SUCCESS(rc)) + { + const uint32_t cbFree = (uint32_t)RTCircBufFree(pCircBuf); + + /* + * Most guests don't use different stream frame sizes than + * the default one, so save a bit of CPU time and don't go into + * the frame extraction code below. + * + * Only macOS guests need the frame extraction branch below at the moment AFAIK. + */ + if (pStream->State.Mapping.cbFrameSize == HDA_FRAME_SIZE_DEFAULT) + { + uint32_t cbDMARead = 0; + uint32_t cbDMALeft = RT_MIN(cbDMA, cbFree); + + while (cbDMALeft) + { + void *pvBuf; size_t cbBuf; + RTCircBufAcquireWriteBlock(pCircBuf, cbDMALeft, &pvBuf, &cbBuf); + + if (cbBuf) + { + memcpy(pvBuf, abChunk + cbDMARead, cbBuf); + cbDMARead += (uint32_t)cbBuf; + cbDMALeft -= (uint32_t)cbBuf; + } + + RTCircBufReleaseWriteBlock(pCircBuf, cbBuf); + } + } + else + { + /* + * The following code extracts the required audio stream (channel) data + * of non-interleaved *and* interleaved audio streams. + * + * We by default only support 2 channels with 16-bit samples (HDA_FRAME_SIZE), + * but an HDA audio stream can have interleaved audio data of multiple audio + * channels in such a single stream ("AA,AA,AA vs. AA,BB,AA,BB"). + * + * So take this into account by just handling the first channel in such a stream ("A") + * and just discard the other channel's data. + * + * I know, the following code is horribly slow, but seems to work for now. + ** @todo Optimize channel data extraction! Use some SSE(3) / intrinsics? + */ + for (unsigned m = 0; m < pStream->State.Mapping.cMappings; m++) + { + const uint32_t cbFrame = pStream->State.Mapping.cbFrameSize; + + Assert(cbFree >= cbDMA); + + PPDMAUDIOSTREAMMAP pMap = &pStream->State.Mapping.paMappings[m]; + AssertPtr(pMap); + + Log3Func(("Mapping #%u: Start (cbDMA=%RU32, cbFrame=%RU32, cbOff=%RU32)\n", + m, cbDMA, cbFrame, pMap->cbOff)); + + uint8_t *pbSrcBuf = abChunk; + size_t cbSrcOff = pMap->cbOff; + Assert(cbChunk >= cbSrcOff); + + for (unsigned i = 0; i < cbDMA / cbFrame; i++) + { + void *pvDstBuf; size_t cbDstBuf; + RTCircBufAcquireWriteBlock(pCircBuf, pMap->cbSize, &pvDstBuf, &cbDstBuf); + + Assert(cbDstBuf >= pMap->cbSize); + + if (cbDstBuf) + { + Log3Func(("Mapping #%u: Frame #%02u: cbSize=%zu, cbFirst=%zu, cbOff=%zu, cbDstBuf=%zu, cbSrcOff=%zu\n", + m, i, pMap->cbSize, pMap->cbFirst, pMap->cbOff, cbDstBuf, cbSrcOff)); + + memcpy(pvDstBuf, pbSrcBuf + cbSrcOff, cbDstBuf); + +#if 0 /* Too slow, even for release builds, so disabled it. */ + if (pStream->Dbg.Runtime.fEnabled) + DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMAMapped, pvDstBuf, cbDstBuf, + 0 /* fFlags */); +#endif + Assert(cbSrcOff <= cbDMA); + if (cbSrcOff + cbFrame + pMap->cbFirst <= cbDMA) + cbSrcOff += cbFrame + pMap->cbFirst; + + Log3Func(("Mapping #%u: Frame #%02u: -> cbSrcOff=%zu\n", m, i, cbSrcOff)); + } + + RTCircBufReleaseWriteBlock(pCircBuf, cbDstBuf); + } + + Log3Func(("Mapping #%u: End cbSize=%zu, cbDMA=%RU32, cbSrcOff=%zu\n", + m, pMap->cbSize, cbDMA, cbSrcOff)); + + Assert(cbSrcOff <= cbDMA); + + const uint32_t cbSrcLeft = cbDMA - (uint32_t)cbSrcOff; + if (cbSrcLeft) + { + Log3Func(("Mapping #%u: cbSrcLeft=%RU32\n", m, cbSrcLeft)); + + if (cbSrcLeft >= pMap->cbSize) + { + void *pvDstBuf; size_t cbDstBuf; + RTCircBufAcquireWriteBlock(pCircBuf, pMap->cbSize, &pvDstBuf, &cbDstBuf); + + Assert(cbDstBuf >= pMap->cbSize); + + if (cbDstBuf) + { + memcpy(pvDstBuf, pbSrcBuf + cbSrcOff, cbDstBuf); + } + + RTCircBufReleaseWriteBlock(pCircBuf, cbDstBuf); + } + + Assert(pMap->cbFrame >= cbSrcLeft); + pMap->cbOff = pMap->cbFrame - cbSrcLeft; + } + else + pMap->cbOff = 0; + + Log3Func(("Mapping #%u finish (cbSrcOff=%zu, cbOff=%zu)\n", m, cbSrcOff, pMap->cbOff)); + } + } + } + else + LogRel(("HDA: Reading from stream #%RU8 DMA failed with %Rrc\n", pStream->u8SD, rc)); + + STAM_PROFILE_STOP(&pThis->StatOut, a); + } + + else /** @todo Handle duplex streams? */ + AssertFailed(); + + if (cbDMA) + { + /* We always increment the position of DMA buffer counter because we're always reading + * into an intermediate DMA buffer. */ + pBDLE->State.u32BufOff += (uint32_t)cbDMA; + Assert(pBDLE->State.u32BufOff <= pBDLE->Desc.u32BufSize); + + /* Are we done doing the position adjustment? + * Only then do the transfer accounting .*/ + if (pStream->State.cfPosAdjustLeft == 0) + { + Assert(cbLeft >= cbDMA); + cbLeft -= cbDMA; + + cbProcessed += cbDMA; + } + + /* + * Update the stream's current position. + * Do this as accurate and close to the actual data transfer as possible. + * All guetsts rely on this, depending on the mechanism they use (LPIB register or DMA counters). + */ + uint32_t cbStreamPos = hdaR3StreamGetPosition(pThis, pStream); + if (cbStreamPos == pStream->u32CBL) + cbStreamPos = 0; + + hdaR3StreamSetPosition(pStream, cbStreamPos + cbDMA); + } + + if (hdaR3BDLEIsComplete(pBDLE)) + { + Log3Func(("[SD%RU8] Complete: %R[bdle]\n", pStream->u8SD, pBDLE)); + + /* Does the current BDLE require an interrupt to be sent? */ + if ( hdaR3BDLENeedsInterrupt(pBDLE) + /* Are we done doing the position adjustment? + * It can happen that a BDLE which is handled while doing the + * position adjustment requires an interrupt on completion (IOC) being set. + * + * In such a case we need to skip such an interrupt and just move on. */ + && pStream->State.cfPosAdjustLeft == 0) + { + /* If the IOCE ("Interrupt On Completion Enable") bit of the SDCTL register is set + * we need to generate an interrupt. + */ + if (HDA_STREAM_REG(pThis, CTL, pStream->u8SD) & HDA_SDCTL_IOCE) + { + pStream->State.cTransferPendingInterrupts++; + + AssertMsg(pStream->State.cTransferPendingInterrupts <= 32, + ("Too many pending interrupts (%RU8) for stream #%RU8\n", + pStream->State.cTransferPendingInterrupts, pStream->u8SD)); + } + } + + if (pStream->State.uCurBDLE == pStream->u16LVI) + { + pStream->State.uCurBDLE = 0; + } + else + pStream->State.uCurBDLE++; + + /* Fetch the next BDLE entry. */ + hdaR3BDLEFetch(pThis, pBDLE, pStream->u64BDLBase, pStream->State.uCurBDLE); + } + + /* Do the position adjustment accounting. */ + pStream->State.cfPosAdjustLeft -= + RT_MIN(pStream->State.cfPosAdjustLeft, cbDMA / pStream->State.Mapping.cbFrameSize); + + if (RT_FAILURE(rc)) + break; + } + + Log3Func(("[SD%RU8] cbToProcess=%RU32, cbProcessed=%RU32, cbLeft=%RU32, %R[bdle], rc=%Rrc\n", + pStream->u8SD, cbToProcess, cbProcessed, cbLeft, pBDLE, rc)); + + /* Sanity. */ + Assert(cbProcessed == cbToProcess); + Assert(cbLeft == 0); + + /* Only do the data accounting if we don't have to do any position + * adjustment anymore. */ + if (pStream->State.cfPosAdjustLeft == 0) + { + hdaR3StreamPeriodInc(pPeriod, RT_MIN(cbProcessed / pStream->State.Mapping.cbFrameSize, + hdaR3StreamPeriodGetRemainingFrames(pPeriod))); + + pStream->State.cbTransferProcessed += cbProcessed; + } + + /* Make sure that we never report more stuff processed than initially announced. */ + if (pStream->State.cbTransferProcessed > pStream->State.cbTransferSize) + pStream->State.cbTransferProcessed = pStream->State.cbTransferSize; + + uint32_t cbTransferLeft = pStream->State.cbTransferSize - pStream->State.cbTransferProcessed; + bool fTransferComplete = !cbTransferLeft; + uint64_t tsTransferNext = 0; + + if (fTransferComplete) + { + /* + * Try updating the wall clock. + * + * Note 1) Only certain guests (like Linux' snd_hda_intel) rely on the WALCLK register + * in order to determine the correct timing of the sound device. Other guests + * like Windows 7 + 10 (or even more exotic ones like Haiku) will completely + * ignore this. + * + * Note 2) When updating the WALCLK register too often / early (or even in a non-monotonic + * fashion) this *will* upset guest device drivers and will completely fuck up the + * sound output. Running VLC on the guest will tell! + */ + const bool fWalClkSet = hdaR3WalClkSet(pThis, + hdaWalClkGetCurrent(pThis) + + hdaR3StreamPeriodFramesToWalClk(pPeriod, + pStream->State.cbTransferProcessed + / pStream->State.Mapping.cbFrameSize), + false /* fForce */); + RT_NOREF(fWalClkSet); + } + + /* Does the period have any interrupts outstanding? */ + if (pStream->State.cTransferPendingInterrupts) + { + Log3Func(("[SD%RU8] Scheduling interrupt\n", pStream->u8SD)); + + /* + * Set the stream's BCIS bit. + * + * Note: This only must be done if the whole period is complete, and not if only + * one specific BDL entry is complete (if it has the IOC bit set). + * + * This will otherwise confuses the guest when it 1) deasserts the interrupt, + * 2) reads SDSTS (with BCIS set) and then 3) too early reads a (wrong) WALCLK value. + * + * snd_hda_intel on Linux will tell. + */ + HDA_STREAM_REG(pThis, STS, pStream->u8SD) |= HDA_SDSTS_BCIS; + + /* Trigger an interrupt first and let hdaRegWriteSDSTS() deal with + * ending / beginning a period. */ +#ifndef LOG_ENABLED + hdaProcessInterrupt(pThis); +#else + hdaProcessInterrupt(pThis, __FUNCTION__); +#endif + } + else /* Transfer still in-flight -- schedule the next timing slot. */ + { + uint32_t cbTransferNext = cbTransferLeft; + + /* No data left to transfer anymore or do we have more data left + * than we can transfer per timing slot? Clamp. */ + if ( !cbTransferNext + || cbTransferNext > pStream->State.cbTransferChunk) + { + cbTransferNext = pStream->State.cbTransferChunk; + } + + tsTransferNext = tsNow + (cbTransferNext * pStream->State.cTicksPerByte); + + /* + * If the current transfer is complete, reset our counter. + * + * This can happen for examlpe if the guest OS (like macOS) sets up + * big BDLEs without IOC bits set (but for the last one) and the + * transfer is complete before we reach such a BDL entry. + */ + if (fTransferComplete) + pStream->State.cbTransferProcessed = 0; + } + + /* If we need to do another transfer, (re-)arm the device timer. */ + if (tsTransferNext) /* Can be 0 if no next transfer is needed. */ + { + Log3Func(("[SD%RU8] Scheduling timer\n", pStream->u8SD)); + + TMTimerUnlock(pStream->pTimer); + + LogFunc(("Timer set SD%RU8\n", pStream->u8SD)); + hdaR3TimerSet(pStream->pHDAState, pStream, tsTransferNext, false /* fForce */); + + TMTimerLock(pStream->pTimer, VINF_SUCCESS); + + pStream->State.tsTransferNext = tsTransferNext; + } + + pStream->State.tsTransferLast = tsNow; + + Log3Func(("[SD%RU8] cbTransferLeft=%RU32 -- %RU32/%RU32\n", + pStream->u8SD, cbTransferLeft, pStream->State.cbTransferProcessed, pStream->State.cbTransferSize)); + Log3Func(("[SD%RU8] fTransferComplete=%RTbool, cTransferPendingInterrupts=%RU8\n", + pStream->u8SD, fTransferComplete, pStream->State.cTransferPendingInterrupts)); + Log3Func(("[SD%RU8] tsNow=%RU64, tsTransferNext=%RU64 (in %RU64 ticks)\n", + pStream->u8SD, tsNow, tsTransferNext, tsTransferNext - tsNow)); + + hdaR3StreamPeriodUnlock(pPeriod); + hdaR3StreamUnlock(pStream); + + return VINF_SUCCESS; +} + +/** + * Updates a HDA stream by doing its required data transfers. + * The host sink(s) set the overall pace. + * + * This routine is called by both, the synchronous and the asynchronous, implementations. + * + * This routine is called by both, the synchronous and the asynchronous + * (VBOX_WITH_AUDIO_HDA_ASYNC_IO), implementations. + * + * When running synchronously, the device DMA transfers *and* the mixer sink + * processing is within the device timer. + * + * When running asynchronously, only the device DMA transfers are done in the + * device timer, whereas the mixer sink processing then is done in the stream's + * own async I/O thread. This thread also will call this function + * (with fInTimer set to @c false). + * + * @param pStream HDA stream to update. + * @param fInTimer Whether to this function was called from the timer + * context or an asynchronous I/O stream thread (if supported). + */ +void hdaR3StreamUpdate(PHDASTREAM pStream, bool fInTimer) +{ + if (!pStream) + return; + + PAUDMIXSINK pSink = NULL; + if ( pStream->pMixSink + && pStream->pMixSink->pMixSink) + { + pSink = pStream->pMixSink->pMixSink; + } + + if (!AudioMixerSinkIsActive(pSink)) /* No sink available? Bail out. */ + return; + + int rc2; + + if (hdaGetDirFromSD(pStream->u8SD) == PDMAUDIODIR_OUT) /* Output (SDO). */ + { + bool fDoRead = false; /* Whether to read from the HDA stream or not. */ + +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + if (fInTimer) +# endif + { + const uint32_t cbStreamFree = hdaR3StreamGetFree(pStream); + if (cbStreamFree) + { + /* Do the DMA transfer. */ + rc2 = hdaR3StreamTransfer(pStream, cbStreamFree); + AssertRC(rc2); + } + + /* Only read from the HDA stream at the given scheduling rate. */ + const uint64_t tsNowNs = RTTimeNanoTS(); + if (tsNowNs - pStream->State.tsLastUpdateNs >= pStream->State.Cfg.Device.uSchedulingHintMs * RT_NS_1MS) + { + fDoRead = true; + pStream->State.tsLastUpdateNs = tsNowNs; + } + } + + Log3Func(("[SD%RU8] fInTimer=%RTbool, fDoRead=%RTbool\n", pStream->u8SD, fInTimer, fDoRead)); + +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + if (fDoRead) + { + rc2 = hdaR3StreamAsyncIONotify(pStream); + AssertRC(rc2); + } +# endif + +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + if (!fInTimer) /* In async I/O thread */ + { +# else + if (fDoRead) + { +# endif + const uint32_t cbSinkWritable = AudioMixerSinkGetWritable(pSink); + const uint32_t cbStreamReadable = hdaR3StreamGetUsed(pStream); + const uint32_t cbToReadFromStream = RT_MIN(cbStreamReadable, cbSinkWritable); + + Log3Func(("[SD%RU8] cbSinkWritable=%RU32, cbStreamReadable=%RU32\n", pStream->u8SD, cbSinkWritable, cbStreamReadable)); + + if (cbToReadFromStream) + { + /* Read (guest output) data and write it to the stream's sink. */ + rc2 = hdaR3StreamRead(pStream, cbToReadFromStream, NULL); + AssertRC(rc2); + } + + /* When running synchronously, update the associated sink here. + * Otherwise this will be done in the async I/O thread. */ + rc2 = AudioMixerSinkUpdate(pSink); + AssertRC(rc2); + } + } + else /* Input (SDI). */ + { +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + if (!fInTimer) + { +# endif + rc2 = AudioMixerSinkUpdate(pSink); + AssertRC(rc2); + + /* Is the sink ready to be read (host input data) from? If so, by how much? */ + uint32_t cbSinkReadable = AudioMixerSinkGetReadable(pSink); + + /* How much (guest input) data is available for writing at the moment for the HDA stream? */ + const uint32_t cbStreamFree = hdaR3StreamGetFree(pStream); + + Log3Func(("[SD%RU8] cbSinkReadable=%RU32, cbStreamFree=%RU32\n", pStream->u8SD, cbSinkReadable, cbStreamFree)); + + /* Do not read more than the HDA stream can hold at the moment. + * The host sets the overall pace. */ + if (cbSinkReadable > cbStreamFree) + cbSinkReadable = cbStreamFree; + + if (cbSinkReadable) + { + uint8_t abFIFO[HDA_FIFO_MAX + 1]; + while (cbSinkReadable) + { + uint32_t cbRead; + rc2 = AudioMixerSinkRead(pSink, AUDMIXOP_COPY, + abFIFO, RT_MIN(cbSinkReadable, (uint32_t)sizeof(abFIFO)), &cbRead); + AssertRCBreak(rc2); + + if (!cbRead) + { + AssertMsgFailed(("Nothing read from sink, even if %RU32 bytes were (still) announced\n", cbSinkReadable)); + break; + } + + /* Write (guest input) data to the stream which was read from stream's sink before. */ + uint32_t cbWritten; + rc2 = hdaR3StreamWrite(pStream, abFIFO, cbRead, &cbWritten); + AssertRCBreak(rc2); + + if (!cbWritten) + { + AssertFailed(); /* Should never happen, as we know how much we can write. */ + break; + } + + Assert(cbSinkReadable >= cbRead); + cbSinkReadable -= cbRead; + } + } +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + } + else /* fInTimer */ + { +# endif + +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + const uint64_t tsNowNs = RTTimeNanoTS(); + if (tsNowNs - pStream->State.tsLastUpdateNs >= pStream->State.Cfg.Device.uSchedulingHintMs * RT_NS_1MS) + { + rc2 = hdaR3StreamAsyncIONotify(pStream); + AssertRC(rc2); + + pStream->State.tsLastUpdateNs = tsNowNs; + } +# endif + const uint32_t cbStreamUsed = hdaR3StreamGetUsed(pStream); + if (cbStreamUsed) + { + rc2 = hdaR3StreamTransfer(pStream, cbStreamUsed); + AssertRC(rc2); + } +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + } +# endif + } +} + +/** + * Locks an HDA stream for serialized access. + * + * @returns IPRT status code. + * @param pStream HDA stream to lock. + */ +void hdaR3StreamLock(PHDASTREAM pStream) +{ + AssertPtrReturnVoid(pStream); + int rc2 = RTCritSectEnter(&pStream->CritSect); + AssertRC(rc2); +} + +/** + * Unlocks a formerly locked HDA stream. + * + * @returns IPRT status code. + * @param pStream HDA stream to unlock. + */ +void hdaR3StreamUnlock(PHDASTREAM pStream) +{ + AssertPtrReturnVoid(pStream); + int rc2 = RTCritSectLeave(&pStream->CritSect); + AssertRC(rc2); +} + +/** + * Updates an HDA stream's current read or write buffer position (depending on the stream type) by + * updating its associated LPIB register and DMA position buffer (if enabled). + * + * @returns Set LPIB value. + * @param pStream HDA stream to update read / write position for. + * @param u32LPIB New LPIB (position) value to set. + */ +uint32_t hdaR3StreamUpdateLPIB(PHDASTREAM pStream, uint32_t u32LPIB) +{ + AssertPtrReturn(pStream, 0); + + AssertMsg(u32LPIB <= pStream->u32CBL, + ("[SD%RU8] New LPIB (%RU32) exceeds CBL (%RU32)\n", pStream->u8SD, u32LPIB, pStream->u32CBL)); + + const PHDASTATE pThis = pStream->pHDAState; + + u32LPIB = RT_MIN(u32LPIB, pStream->u32CBL); + + LogFlowFunc(("[SD%RU8] LPIB=%RU32 (DMA Position Buffer Enabled: %RTbool)\n", + pStream->u8SD, u32LPIB, pThis->fDMAPosition)); + + /* Update LPIB in any case. */ + HDA_STREAM_REG(pThis, LPIB, pStream->u8SD) = u32LPIB; + + /* Do we need to tell the current DMA position? */ + if (pThis->fDMAPosition) + { + int rc2 = PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns), + pThis->u64DPBase + (pStream->u8SD * 2 * sizeof(uint32_t)), + (void *)&u32LPIB, sizeof(uint32_t)); + AssertRC(rc2); + } + + return u32LPIB; +} + +# ifdef HDA_USE_DMA_ACCESS_HANDLER +/** + * Registers access handlers for a stream's BDLE DMA accesses. + * + * @returns true if registration was successful, false if not. + * @param pStream HDA stream to register BDLE access handlers for. + */ +bool hdaR3StreamRegisterDMAHandlers(PHDASTREAM pStream) +{ + /* At least LVI and the BDL base must be set. */ + if ( !pStream->u16LVI + || !pStream->u64BDLBase) + { + return false; + } + + hdaR3StreamUnregisterDMAHandlers(pStream); + + LogFunc(("Registering ...\n")); + + int rc = VINF_SUCCESS; + + /* + * Create BDLE ranges. + */ + + struct BDLERANGE + { + RTGCPHYS uAddr; + uint32_t uSize; + } arrRanges[16]; /** @todo Use a define. */ + + size_t cRanges = 0; + + for (uint16_t i = 0; i < pStream->u16LVI + 1; i++) + { + HDABDLE BDLE; + rc = hdaR3BDLEFetch(pThis, &BDLE, pStream->u64BDLBase, i /* Index */); + if (RT_FAILURE(rc)) + break; + + bool fAddRange = true; + BDLERANGE *pRange; + + if (cRanges) + { + pRange = &arrRanges[cRanges - 1]; + + /* Is the current range a direct neighbor of the current BLDE? */ + if ((pRange->uAddr + pRange->uSize) == BDLE.Desc.u64BufAddr) + { + /* Expand the current range by the current BDLE's size. */ + pRange->uSize += BDLE.Desc.u32BufSize; + + /* Adding a new range in this case is not needed anymore. */ + fAddRange = false; + + LogFunc(("Expanding range %zu by %RU32 (%RU32 total now)\n", cRanges - 1, BDLE.Desc.u32BufSize, pRange->uSize)); + } + } + + /* Do we need to add a new range? */ + if ( fAddRange + && cRanges < RT_ELEMENTS(arrRanges)) + { + pRange = &arrRanges[cRanges]; + + pRange->uAddr = BDLE.Desc.u64BufAddr; + pRange->uSize = BDLE.Desc.u32BufSize; + + LogFunc(("Adding range %zu - 0x%x (%RU32)\n", cRanges, pRange->uAddr, pRange->uSize)); + + cRanges++; + } + } + + LogFunc(("%zu ranges total\n", cRanges)); + + /* + * Register all ranges as DMA access handlers. + */ + + for (size_t i = 0; i < cRanges; i++) + { + BDLERANGE *pRange = &arrRanges[i]; + + PHDADMAACCESSHANDLER pHandler = (PHDADMAACCESSHANDLER)RTMemAllocZ(sizeof(HDADMAACCESSHANDLER)); + if (!pHandler) + { + rc = VERR_NO_MEMORY; + break; + } + + RTListAppend(&pStream->State.lstDMAHandlers, &pHandler->Node); + + pHandler->pStream = pStream; /* Save a back reference to the owner. */ + + char szDesc[32]; + RTStrPrintf(szDesc, sizeof(szDesc), "HDA[SD%RU8 - RANGE%02zu]", pStream->u8SD, i); + + int rc2 = PGMR3HandlerPhysicalTypeRegister(PDMDevHlpGetVM(pStream->pHDAState->pDevInsR3), PGMPHYSHANDLERKIND_WRITE, + hdaDMAAccessHandler, + NULL, NULL, NULL, + NULL, NULL, NULL, + szDesc, &pHandler->hAccessHandlerType); + AssertRCBreak(rc2); + + pHandler->BDLEAddr = pRange->uAddr; + pHandler->BDLESize = pRange->uSize; + + /* Get first and last pages of the BDLE range. */ + RTGCPHYS pgFirst = pRange->uAddr & ~PAGE_OFFSET_MASK; + RTGCPHYS pgLast = RT_ALIGN(pgFirst + pRange->uSize, PAGE_SIZE); + + /* Calculate the region size (in pages). */ + RTGCPHYS regionSize = RT_ALIGN(pgLast - pgFirst, PAGE_SIZE); + + pHandler->GCPhysFirst = pgFirst; + pHandler->GCPhysLast = pHandler->GCPhysFirst + (regionSize - 1); + + LogFunc(("\tRegistering region '%s': 0x%x - 0x%x (region size: %zu)\n", + szDesc, pHandler->GCPhysFirst, pHandler->GCPhysLast, regionSize)); + LogFunc(("\tBDLE @ 0x%x - 0x%x (%RU32)\n", + pHandler->BDLEAddr, pHandler->BDLEAddr + pHandler->BDLESize, pHandler->BDLESize)); + + rc2 = PGMHandlerPhysicalRegister(PDMDevHlpGetVM(pStream->pHDAState->pDevInsR3), + pHandler->GCPhysFirst, pHandler->GCPhysLast, + pHandler->hAccessHandlerType, pHandler, NIL_RTR0PTR, NIL_RTRCPTR, + szDesc); + AssertRCBreak(rc2); + + pHandler->fRegistered = true; + } + + LogFunc(("Registration ended with rc=%Rrc\n", rc)); + + return RT_SUCCESS(rc); +} + +/** + * Unregisters access handlers of a stream's BDLEs. + * + * @param pStream HDA stream to unregister BDLE access handlers for. + */ +void hdaR3StreamUnregisterDMAHandlers(PHDASTREAM pStream) +{ + LogFunc(("\n")); + + PHDADMAACCESSHANDLER pHandler, pHandlerNext; + RTListForEachSafe(&pStream->State.lstDMAHandlers, pHandler, pHandlerNext, HDADMAACCESSHANDLER, Node) + { + if (!pHandler->fRegistered) /* Handler not registered? Skip. */ + continue; + + LogFunc(("Unregistering 0x%x - 0x%x (%zu)\n", + pHandler->GCPhysFirst, pHandler->GCPhysLast, pHandler->GCPhysLast - pHandler->GCPhysFirst)); + + int rc2 = PGMHandlerPhysicalDeregister(PDMDevHlpGetVM(pStream->pHDAState->pDevInsR3), + pHandler->GCPhysFirst); + AssertRC(rc2); + + RTListNodeRemove(&pHandler->Node); + + RTMemFree(pHandler); + pHandler = NULL; + } + + Assert(RTListIsEmpty(&pStream->State.lstDMAHandlers)); +} +# endif /* HDA_USE_DMA_ACCESS_HANDLER */ + +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO +/** + * Asynchronous I/O thread for a HDA stream. + * This will do the heavy lifting work for us as soon as it's getting notified by another thread. + * + * @returns IPRT status code. + * @param hThreadSelf Thread handle. + * @param pvUser User argument. Must be of type PHDASTREAMTHREADCTX. + */ +DECLCALLBACK(int) hdaR3StreamAsyncIOThread(RTTHREAD hThreadSelf, void *pvUser) +{ + PHDASTREAMTHREADCTX pCtx = (PHDASTREAMTHREADCTX)pvUser; + AssertPtr(pCtx); + + PHDASTREAM pStream = pCtx->pStream; + AssertPtr(pStream); + + PHDASTREAMSTATEAIO pAIO = &pCtx->pStream->State.AIO; + + ASMAtomicXchgBool(&pAIO->fStarted, true); + + RTThreadUserSignal(hThreadSelf); + + LogFunc(("[SD%RU8] Started\n", pStream->u8SD)); + + for (;;) + { + int rc2 = RTSemEventWait(pAIO->Event, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc2)) + break; + + if (ASMAtomicReadBool(&pAIO->fShutdown)) + break; + + rc2 = RTCritSectEnter(&pAIO->CritSect); + if (RT_SUCCESS(rc2)) + { + if (!pAIO->fEnabled) + { + RTCritSectLeave(&pAIO->CritSect); + continue; + } + + hdaR3StreamUpdate(pStream, false /* fInTimer */); + + int rc3 = RTCritSectLeave(&pAIO->CritSect); + AssertRC(rc3); + } + + AssertRC(rc2); + } + + LogFunc(("[SD%RU8] Ended\n", pStream->u8SD)); + + ASMAtomicXchgBool(&pAIO->fStarted, false); + + return VINF_SUCCESS; +} + +/** + * Creates the async I/O thread for a specific HDA audio stream. + * + * @returns IPRT status code. + * @param pStream HDA audio stream to create the async I/O thread for. + */ +int hdaR3StreamAsyncIOCreate(PHDASTREAM pStream) +{ + PHDASTREAMSTATEAIO pAIO = &pStream->State.AIO; + + int rc; + + if (!ASMAtomicReadBool(&pAIO->fStarted)) + { + pAIO->fShutdown = false; + pAIO->fEnabled = true; /* Enabled by default. */ + + rc = RTSemEventCreate(&pAIO->Event); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&pAIO->CritSect); + if (RT_SUCCESS(rc)) + { + HDASTREAMTHREADCTX Ctx = { pStream->pHDAState, pStream }; + + char szThreadName[64]; + RTStrPrintf2(szThreadName, sizeof(szThreadName), "hdaAIO%RU8", pStream->u8SD); + + rc = RTThreadCreate(&pAIO->Thread, hdaR3StreamAsyncIOThread, &Ctx, + 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, szThreadName); + if (RT_SUCCESS(rc)) + rc = RTThreadUserWait(pAIO->Thread, 10 * 1000 /* 10s timeout */); + } + } + } + else + rc = VINF_SUCCESS; + + LogFunc(("[SD%RU8] Returning %Rrc\n", pStream->u8SD, rc)); + return rc; +} + +/** + * Destroys the async I/O thread of a specific HDA audio stream. + * + * @returns IPRT status code. + * @param pStream HDA audio stream to destroy the async I/O thread for. + */ +int hdaR3StreamAsyncIODestroy(PHDASTREAM pStream) +{ + PHDASTREAMSTATEAIO pAIO = &pStream->State.AIO; + + if (!ASMAtomicReadBool(&pAIO->fStarted)) + return VINF_SUCCESS; + + ASMAtomicWriteBool(&pAIO->fShutdown, true); + + int rc = hdaR3StreamAsyncIONotify(pStream); + AssertRC(rc); + + int rcThread; + rc = RTThreadWait(pAIO->Thread, 30 * 1000 /* 30s timeout */, &rcThread); + LogFunc(("Async I/O thread ended with %Rrc (%Rrc)\n", rc, rcThread)); + + if (RT_SUCCESS(rc)) + { + rc = RTCritSectDelete(&pAIO->CritSect); + AssertRC(rc); + + rc = RTSemEventDestroy(pAIO->Event); + AssertRC(rc); + + pAIO->fStarted = false; + pAIO->fShutdown = false; + pAIO->fEnabled = false; + } + + LogFunc(("[SD%RU8] Returning %Rrc\n", pStream->u8SD, rc)); + return rc; +} + +/** + * Lets the stream's async I/O thread know that there is some data to process. + * + * @returns IPRT status code. + * @param pStream HDA stream to notify async I/O thread for. + */ +int hdaR3StreamAsyncIONotify(PHDASTREAM pStream) +{ + return RTSemEventSignal(pStream->State.AIO.Event); +} + +/** + * Locks the async I/O thread of a specific HDA audio stream. + * + * @param pStream HDA stream to lock async I/O thread for. + */ +void hdaR3StreamAsyncIOLock(PHDASTREAM pStream) +{ + PHDASTREAMSTATEAIO pAIO = &pStream->State.AIO; + + if (!ASMAtomicReadBool(&pAIO->fStarted)) + return; + + int rc2 = RTCritSectEnter(&pAIO->CritSect); + AssertRC(rc2); +} + +/** + * Unlocks the async I/O thread of a specific HDA audio stream. + * + * @param pStream HDA stream to unlock async I/O thread for. + */ +void hdaR3StreamAsyncIOUnlock(PHDASTREAM pStream) +{ + PHDASTREAMSTATEAIO pAIO = &pStream->State.AIO; + + if (!ASMAtomicReadBool(&pAIO->fStarted)) + return; + + int rc2 = RTCritSectLeave(&pAIO->CritSect); + AssertRC(rc2); +} + +/** + * Enables (resumes) or disables (pauses) the async I/O thread. + * + * @param pStream HDA stream to enable/disable async I/O thread for. + * @param fEnable Whether to enable or disable the I/O thread. + * + * @remarks Does not do locking. + */ +void hdaR3StreamAsyncIOEnable(PHDASTREAM pStream, bool fEnable) +{ + PHDASTREAMSTATEAIO pAIO = &pStream->State.AIO; + ASMAtomicXchgBool(&pAIO->fEnabled, fEnable); +} +# endif /* VBOX_WITH_AUDIO_HDA_ASYNC_IO */ + +#endif /* IN_RING3 */ + diff --git a/src/VBox/Devices/Audio/HDAStream.h b/src/VBox/Devices/Audio/HDAStream.h new file mode 100644 index 00000000..92cefcdf --- /dev/null +++ b/src/VBox/Devices/Audio/HDAStream.h @@ -0,0 +1,309 @@ +/* $Id: HDAStream.h $ */ +/** @file + * HDAStream.h - Stream functions for HD Audio. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_HDAStream_h +#define VBOX_INCLUDED_SRC_Audio_HDAStream_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "DevHDACommon.h" + +#include "HDAStreamMap.h" +#include "HDAStreamPeriod.h" + + +typedef struct HDAMIXERSINK *PHDAMIXERSINK; + +#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO +/** + * Structure keeping the HDA stream's state for asynchronous I/O. + */ +typedef struct HDASTREAMSTATEAIO +{ + /** Thread handle for the actual I/O thread. */ + RTTHREAD Thread; + /** Event for letting the thread know there is some data to process. */ + RTSEMEVENT Event; + /** Critical section for synchronizing access. */ + RTCRITSECT CritSect; + /** Started indicator. */ + volatile bool fStarted; + /** Shutdown indicator. */ + volatile bool fShutdown; + /** Whether the thread should do any data processing or not. */ + volatile bool fEnabled; + uint32_t Padding1; +} HDASTREAMSTATEAIO, *PHDASTREAMSTATEAIO; +#endif + +/** + * Structure containing HDA stream debug stuff, configurable at runtime. + */ +typedef struct HDASTREAMDBGINFORT +{ + /** Whether debugging is enabled or not. */ + bool fEnabled; + uint8_t Padding[7]; + /** File for dumping stream reads / writes. + * For input streams, this dumps data being written to the device FIFO, + * whereas for output streams this dumps data being read from the device FIFO. */ + R3PTRTYPE(PPDMAUDIOFILE) pFileStream; + /** File for dumping raw DMA reads / writes. + * For input streams, this dumps data being written to the device DMA, + * whereas for output streams this dumps data being read from the device DMA. */ + R3PTRTYPE(PPDMAUDIOFILE) pFileDMARaw; + /** File for dumping mapped (that is, extracted) DMA reads / writes. */ + R3PTRTYPE(PPDMAUDIOFILE) pFileDMAMapped; +} HDASTREAMDBGINFORT, *PHDASTREAMDBGINFORT; + +/** + * Structure containing HDA stream debug information. + */ +typedef struct HDASTREAMDBGINFO +{ +#ifdef DEBUG + /** Critical section to serialize access if needed. */ + RTCRITSECT CritSect; + uint32_t Padding0[2]; + /** Number of total read accesses. */ + uint64_t cReadsTotal; + /** Number of total DMA bytes read. */ + uint64_t cbReadTotal; + /** Timestamp (in ns) of last read access. */ + uint64_t tsLastReadNs; + /** Number of total write accesses. */ + uint64_t cWritesTotal; + /** Number of total DMA bytes written. */ + uint64_t cbWrittenTotal; + /** Number of total write accesses since last iteration (Hz). */ + uint64_t cWritesHz; + /** Number of total DMA bytes written since last iteration (Hz). */ + uint64_t cbWrittenHz; + /** Timestamp (in ns) of beginning a new write slot. */ + uint64_t tsWriteSlotBegin; + /** Number of current silence samples in a (consecutive) row. */ + uint64_t csSilence; + /** Number of silent samples in a row to consider an audio block as audio gap (silence). */ + uint64_t cSilenceThreshold; + /** How many bytes to skip in an audio stream before detecting silence. + * (useful for intros and silence at the beginning of a song). */ + uint64_t cbSilenceReadMin; +#endif + /** Runtime debug info. */ + HDASTREAMDBGINFORT Runtime; +} HDASTREAMDBGINFO ,*PHDASTREAMDBGINFO; + +/** + * Internal state of a HDA stream. + */ +typedef struct HDASTREAMSTATE +{ + /** Current BDLE to use. Wraps around to 0 if + * maximum (cBDLE) is reached. */ + uint16_t uCurBDLE; + /** Flag indicating whether this stream currently is + * in reset mode and therefore not acccessible by the guest. */ + volatile bool fInReset; + /** Flag indicating if the stream is in running state or not. */ + volatile bool fRunning; + /** Unused, padding. */ + uint8_t Padding0[4]; +#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO + /** Asynchronous I/O state members. */ + HDASTREAMSTATEAIO AIO; +#endif + /** This stream's data mapping. */ + HDASTREAMMAP Mapping; + /** Current BDLE (Buffer Descriptor List Entry). */ + HDABDLE BDLE; + /** Circular buffer (FIFO) for holding DMA'ed data. */ + R3PTRTYPE(PRTCIRCBUF) pCircBuf; +#if HC_ARCH_BITS == 32 + RTR3PTR Padding1; +#endif + /** Timestamp of the last DMA data transfer. */ + uint64_t tsTransferLast; + /** Timestamp of the next DMA data transfer. + * Next for determining the next scheduling window. + * Can be 0 if no next transfer is scheduled. */ + uint64_t tsTransferNext; + /** Total transfer size (in bytes) of a transfer period. */ + uint32_t cbTransferSize; + /** Transfer chunk size (in bytes) of a transfer period. */ + uint32_t cbTransferChunk; + /** How many bytes already have been processed in within + * the current transfer period. */ + uint32_t cbTransferProcessed; + /** How many interrupts are pending due to + * BDLE interrupt-on-completion (IOC) bits set. */ + uint8_t cTransferPendingInterrupts; + uint8_t Padding2[2]; + /** The stream's timer Hz rate. + * This value can can be different from the device's default Hz rate, + * depending on the rate the stream expects (e.g. for 5.1 speaker setups). + * Set in hdaR3StreamInit(). */ + uint16_t uTimerHz; + /** Number of audio data frames for the position adjustment. + * 0 if no position adjustment is needed. */ + uint16_t cfPosAdjustDefault; + /** How many audio data frames are left to be processed + * for the position adjustment handling. + * + * 0 if position adjustment handling is done or inactive. */ + uint16_t cfPosAdjustLeft; + /** (Virtual) clock ticks per byte. */ + uint64_t cTicksPerByte; + /** (Virtual) clock ticks per transfer. */ + uint64_t cTransferTicks; + /** The stream's period. Need for timing. */ + HDASTREAMPERIOD Period; + /** The stream's current configuration. + * Should match SDFMT. */ + PDMAUDIOSTREAMCFG Cfg; + uint32_t Padding4; +#ifdef HDA_USE_DMA_ACCESS_HANDLER + /** List of DMA handlers. */ + RTLISTANCHORR3 lstDMAHandlers; +#endif + /** Timestamp (in ns) of last stream update. */ + uint64_t tsLastUpdateNs; +} HDASTREAMSTATE; +AssertCompileSizeAlignment(HDASTREAMSTATE, 8); +typedef HDASTREAMSTATE *PHDASTREAMSTATE; + +/** + * Structure for keeping a HDA stream (SDI / SDO). + * + * Note: This HDA stream has nothing to do with a regular audio stream handled + * by the audio connector or the audio mixer. This HDA stream is a serial data in/out + * stream (SDI/SDO) defined in hardware and can contain multiple audio streams + * in one single SDI/SDO (interleaving streams). + * + * How a specific SDI/SDO is mapped to our internal audio streams relies on the + * stream channel mappings. + * + * Contains only register values which do *not* change until a + * stream reset occurs. + */ +typedef struct HDASTREAM +{ + /** Stream descriptor number (SDn). */ + uint8_t u8SD; + /** Current channel index. + * For a stereo stream, this is u8Channel + 1. */ + uint8_t u8Channel; + uint8_t Padding0[6]; + /** DMA base address (SDnBDPU - SDnBDPL). + * Will be updated in hdaR3StreamInit(). */ + uint64_t u64BDLBase; + /** Cyclic Buffer Length (SDnCBL). + * Represents the size of the ring buffer. + * Will be updated in hdaR3StreamInit(). */ + uint32_t u32CBL; + /** Format (SDnFMT). + * Will be updated in hdaR3StreamInit(). */ + uint16_t u16FMT; + /** FIFO Size (FIFOS). + * Maximum number of bytes that may have been DMA'd into + * memory but not yet transmitted on the link. + * + * Will be updated in hdaR3StreamInit(). */ + uint16_t u16FIFOS; + /** FIFO Watermark. */ + uint16_t u16FIFOW; + /** Last Valid Index (SDnLVI). + * Will be updated in hdaR3StreamInit(). */ + uint16_t u16LVI; + uint16_t Padding1[2]; + /** Pointer to the HDA state this stream is attached to. */ + R3PTRTYPE(PHDASTATE) pHDAState; + /** Pointer to HDA sink this stream is attached to. */ + R3PTRTYPE(PHDAMIXERSINK) pMixSink; + /** The stream'S critical section to serialize access. */ + RTCRITSECT CritSect; + /** Pointer to the stream's timer. */ + PTMTIMERR3 pTimer; + /** Internal state of this stream. */ + HDASTREAMSTATE State; + /** Debug information. */ + HDASTREAMDBGINFO Dbg; +} HDASTREAM, *PHDASTREAM; + +#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO +/** + * Structure for keeping a HDA stream thread context. + */ +typedef struct HDASTREAMTHREADCTX +{ + PHDASTATE pThis; + PHDASTREAM pStream; +} HDASTREAMTHREADCTX, *PHDASTREAMTHREADCTX; +#endif + +#ifdef IN_RING3 + +/** @name Stream functions. + * @{ + */ +int hdaR3StreamCreate(PHDASTREAM pStream, PHDASTATE pThis, uint8_t u8SD); +void hdaR3StreamDestroy(PHDASTREAM pStream); +int hdaR3StreamInit(PHDASTREAM pStream, uint8_t uSD); +void hdaR3StreamReset(PHDASTATE pThis, PHDASTREAM pStream, uint8_t uSD); +int hdaR3StreamEnable(PHDASTREAM pStream, bool fEnable); +uint32_t hdaR3StreamGetPosition(PHDASTATE pThis, PHDASTREAM pStream); +void hdaR3StreamSetPosition(PHDASTREAM pStream, uint32_t u32LPIB); +uint32_t hdaR3StreamGetFree(PHDASTREAM pStream); +uint32_t hdaR3StreamGetUsed(PHDASTREAM pStream); +bool hdaR3StreamTransferIsScheduled(PHDASTREAM pStream); +uint64_t hdaR3StreamTransferGetNext(PHDASTREAM pStream); +int hdaR3StreamTransfer(PHDASTREAM pStream, uint32_t cbToProcessMax); +void hdaR3StreamLock(PHDASTREAM pStream); +void hdaR3StreamUnlock(PHDASTREAM pStream); +int hdaR3StreamRead(PHDASTREAM pStream, uint32_t cbToRead, uint32_t *pcbRead); +int hdaR3StreamWrite(PHDASTREAM pStream, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten); +void hdaR3StreamUpdate(PHDASTREAM pStream, bool fAsync); +# ifdef HDA_USE_DMA_ACCESS_HANDLER +bool hdaR3StreamRegisterDMAHandlers(PHDASTREAM pStream); +void hdaR3StreamUnregisterDMAHandlers(PHDASTREAM pStream); +# endif /* HDA_USE_DMA_ACCESS_HANDLER */ +/** @} */ + +/** @name Timer functions. + * @{ + */ +DECLCALLBACK(void) hdaR3StreamTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser); +/** @} */ + + +/** @name Async I/O stream functions. + * @{ + */ +# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO +DECLCALLBACK(int) hdaR3StreamAsyncIOThread(RTTHREAD hThreadSelf, void *pvUser); +int hdaR3StreamAsyncIOCreate(PHDASTREAM pStream); +int hdaR3StreamAsyncIODestroy(PHDASTREAM pStream); +int hdaR3StreamAsyncIONotify(PHDASTREAM pStream); +void hdaR3StreamAsyncIOLock(PHDASTREAM pStream); +void hdaR3StreamAsyncIOUnlock(PHDASTREAM pStream); +void hdaR3StreamAsyncIOEnable(PHDASTREAM pStream, bool fEnable); +# endif /* VBOX_WITH_AUDIO_HDA_ASYNC_IO */ +/** @} */ + +#endif /* IN_RING3 */ +#endif /* !VBOX_INCLUDED_SRC_Audio_HDAStream_h */ + diff --git a/src/VBox/Devices/Audio/HDAStreamChannel.cpp b/src/VBox/Devices/Audio/HDAStreamChannel.cpp new file mode 100644 index 00000000..3e2d254e --- /dev/null +++ b/src/VBox/Devices/Audio/HDAStreamChannel.cpp @@ -0,0 +1,101 @@ +/* $Id: HDAStreamChannel.cpp $ */ +/** @file + * HDAStreamChannel.cpp - Stream channel functions for HD Audio. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_HDA +#include <VBox/log.h> + +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include "HDAStreamChannel.h" + +/** + * Initializes a stream channel data structure. + * + * @returns IPRT status code. + * @param pChanData Channel data to initialize. + * @param fFlags + */ +int hdaR3StreamChannelDataInit(PPDMAUDIOSTREAMCHANNELDATA pChanData, uint32_t fFlags) +{ + int rc = RTCircBufCreate(&pChanData->pCircBuf, 256); /** @todo Make this configurable? */ + if (RT_SUCCESS(rc)) + { + pChanData->fFlags = fFlags; + } + + return rc; +} + +/** + * Destroys a stream channel data structure. + * + * @param pChanData Channel data to destroy. + */ +void hdaR3StreamChannelDataDestroy(PPDMAUDIOSTREAMCHANNELDATA pChanData) +{ + if (!pChanData) + return; + + if (pChanData->pCircBuf) + { + RTCircBufDestroy(pChanData->pCircBuf); + pChanData->pCircBuf = NULL; + } + + pChanData->fFlags = PDMAUDIOSTREAMCHANNELDATA_FLAG_NONE; +} + +/** + * Acquires (reads) audio channel data. + * Must be released when done with hdaR3StreamChannelReleaseData(). + * + * @returns IPRT status code. + * @param pChanData Channel data to acquire audio channel data from. + * @param ppvData Where to store the pointer to the acquired data. + * @param pcbData Size (in bytes) of acquired data. + */ +int hdaR3StreamChannelAcquireData(PPDMAUDIOSTREAMCHANNELDATA pChanData, void **ppvData, size_t *pcbData) +{ + AssertPtrReturn(pChanData, VERR_INVALID_POINTER); + AssertPtrReturn(ppvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbData, VERR_INVALID_POINTER); + + RTCircBufAcquireReadBlock(pChanData->pCircBuf, 256 /** @todo Make this configurarble? */, ppvData, &pChanData->cbAcq); + + *pcbData = pChanData->cbAcq; + return VINF_SUCCESS; +} + +/** + * Releases formerly acquired data by hdaR3StreamChannelAcquireData(). + * + * @returns IPRT status code. + * @param pChanData Channel data to release formerly acquired data for. + */ +int hdaR3StreamChannelReleaseData(PPDMAUDIOSTREAMCHANNELDATA pChanData) +{ + AssertPtrReturn(pChanData, VERR_INVALID_POINTER); + RTCircBufReleaseReadBlock(pChanData->pCircBuf, pChanData->cbAcq); + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Devices/Audio/HDAStreamChannel.h b/src/VBox/Devices/Audio/HDAStreamChannel.h new file mode 100644 index 00000000..3b19a789 --- /dev/null +++ b/src/VBox/Devices/Audio/HDAStreamChannel.h @@ -0,0 +1,30 @@ +/* $Id: HDAStreamChannel.h $ */ +/** @file + * HDAStreamChannel.h - Stream channel functions for HD Audio. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_HDAStreamChannel_h +#define VBOX_INCLUDED_SRC_Audio_HDAStreamChannel_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +int hdaR3StreamChannelDataInit(PPDMAUDIOSTREAMCHANNELDATA pChanData, uint32_t fFlags); +void hdaR3StreamChannelDataDestroy(PPDMAUDIOSTREAMCHANNELDATA pChanData); +int hdaR3StreamChannelAcquireData(PPDMAUDIOSTREAMCHANNELDATA pChanData, void *ppvData, size_t *pcbData); +int hdaR3StreamChannelReleaseData(PPDMAUDIOSTREAMCHANNELDATA pChanData); + +#endif /* !VBOX_INCLUDED_SRC_Audio_HDAStreamChannel_h */ + diff --git a/src/VBox/Devices/Audio/HDAStreamMap.cpp b/src/VBox/Devices/Audio/HDAStreamMap.cpp new file mode 100644 index 00000000..27845e39 --- /dev/null +++ b/src/VBox/Devices/Audio/HDAStreamMap.cpp @@ -0,0 +1,174 @@ +/* $Id: HDAStreamMap.cpp $ */ +/** @file + * HDAStreamMap.cpp - Stream mapping functions for HD Audio. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_HDA +#include <VBox/log.h> + +#include <iprt/mem.h> + +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include "DrvAudio.h" + +#include "HDAStreamChannel.h" +#include "HDAStreamMap.h" + +#ifdef IN_RING3 + +static int hdaR3StreamMapSetup(PHDASTREAMMAP pMap, PPDMAUDIOPCMPROPS pProps); + +/** + * Initializes a stream mapping structure according to the given PCM properties. + * + * @return IPRT status code. + * @param pMap Pointer to mapping to initialize. + * @param pProps Pointer to PCM properties to use for initialization. + */ +int hdaR3StreamMapInit(PHDASTREAMMAP pMap, PPDMAUDIOPCMPROPS pProps) +{ + AssertPtrReturn(pMap, VERR_INVALID_POINTER); + AssertPtrReturn(pProps, VERR_INVALID_POINTER); + + if (!DrvAudioHlpPCMPropsAreValid(pProps)) + return VERR_INVALID_PARAMETER; + + hdaR3StreamMapReset(pMap); + + int rc = hdaR3StreamMapSetup(pMap, pProps); + if (RT_FAILURE(rc)) + return rc; + +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + if ( RT_SUCCESS(rc) + /* Create circular buffer if not created yet. */ + && !pMap->pCircBuf) + { + rc = RTCircBufCreate(&pMap->pCircBuf, _4K); /** @todo Make size configurable? */ + } +#endif + + if (RT_SUCCESS(rc)) + { + pMap->cbFrameSize = pProps->cChannels * pProps->cBytes; + + LogFunc(("cChannels=%RU8, cBytes=%RU8 -> cbFrameSize=%RU32\n", + pProps->cChannels, pProps->cBytes, pMap->cbFrameSize)); + + Assert(pMap->cbFrameSize); /* Frame size must not be 0. */ + + pMap->enmLayout = PDMAUDIOSTREAMLAYOUT_INTERLEAVED; + } + + return rc; +} + + +/** + * Destroys a given stream mapping. + * + * @param pMap Pointer to mapping to destroy. + */ +void hdaR3StreamMapDestroy(PHDASTREAMMAP pMap) +{ + hdaR3StreamMapReset(pMap); + +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + if (pMap->pCircBuf) + { + RTCircBufDestroy(pMap->pCircBuf); + pMap->pCircBuf = NULL; + } +#endif +} + + +/** + * Resets a given stream mapping. + * + * @param pMap Pointer to mapping to reset. + */ +void hdaR3StreamMapReset(PHDASTREAMMAP pMap) +{ + AssertPtrReturnVoid(pMap); + + pMap->enmLayout = PDMAUDIOSTREAMLAYOUT_UNKNOWN; + + if (pMap->paMappings) + { + for (uint8_t i = 0; i < pMap->cMappings; i++) + hdaR3StreamChannelDataDestroy(&pMap->paMappings[i].Data); + + RTMemFree(pMap->paMappings); + pMap->paMappings = NULL; + + pMap->cMappings = 0; + } +} + + +/** + * Sets up a stream mapping according to the given properties / configuration. + * + * @return VBox status code, or VERR_NOT_SUPPORTED if the channel setup is not supported (yet). + * @param pMap Pointer to mapping to set up. + * @param pProps PCM audio properties to use for lookup. + */ +static int hdaR3StreamMapSetup(PHDASTREAMMAP pMap, PPDMAUDIOPCMPROPS pProps) +{ + int rc; + + /** @todo We ASSUME that all channels in a stream ... + * - have the same format + * - are in a consecutive order with no gaps in between + * - have a simple (raw) data layout + * - work in a non-striped fashion, e.g. interleaved (only on one SDn, not spread over multiple SDns) */ + if ( pProps->cChannels == 1 /* Mono */ + || pProps->cChannels == 2 /* Stereo */ + || pProps->cChannels == 4 /* Quadrophonic */ + || pProps->cChannels == 6) /* Surround (5.1) */ + { + /* For now we don't have anything other as mono / stereo channels being covered by the backends. + * So just set up one channel covering those and skipping the rest (like configured rear or center/LFE outputs). */ + pMap->cMappings = 1; + pMap->paMappings = (PPDMAUDIOSTREAMMAP)RTMemAlloc(sizeof(PDMAUDIOSTREAMMAP) * pMap->cMappings); + if (!pMap->paMappings) + return VERR_NO_MEMORY; + + PPDMAUDIOSTREAMMAP pMapLR = &pMap->paMappings[0]; + + pMapLR->aID[0] = PDMAUDIOSTREAMCHANNELID_FRONT_LEFT; + pMapLR->aID[1] = PDMAUDIOSTREAMCHANNELID_FRONT_RIGHT; + pMapLR->cbFrame = pProps->cBytes * pProps->cChannels; + pMapLR->cbSize = pProps->cBytes * 2 /* Front left + Front right channels */; + pMapLR->cbFirst = 0; + pMapLR->cbOff = pMapLR->cbFirst; + + rc = hdaR3StreamChannelDataInit(&pMapLR->Data, PDMAUDIOSTREAMCHANNELDATA_FLAG_NONE); + AssertRC(rc); + } + else + rc = VERR_NOT_SUPPORTED; /** @todo r=andy Support more setups. */ + + return rc; +} +#endif /* IN_RING3 */ + diff --git a/src/VBox/Devices/Audio/HDAStreamMap.h b/src/VBox/Devices/Audio/HDAStreamMap.h new file mode 100644 index 00000000..f0e6e62e --- /dev/null +++ b/src/VBox/Devices/Audio/HDAStreamMap.h @@ -0,0 +1,61 @@ +/* $Id: HDAStreamMap.h $ */ +/** @file + * HDAStreamMap.h - Stream map functions for HD Audio. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_HDAStreamMap_h +#define VBOX_INCLUDED_SRC_Audio_HDAStreamMap_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/** + * Structure for keeping an audio stream data mapping. + */ +typedef struct HDASTREAMMAP +{ + /** The stream's layout. */ + PDMAUDIOSTREAMLAYOUT enmLayout; + uint8_t cbFrameSize; + /** Number of mappings in paMappings. */ + uint8_t cMappings; + uint8_t aPadding[2]; + /** Array of stream mappings. + * Note: The mappings *must* be layed out in an increasing order, e.g. + * how the data appears in the given data block. */ + R3PTRTYPE(PPDMAUDIOSTREAMMAP) paMappings; +#if HC_ARCH_BITS == 32 + RTR3PTR Padding1; +#endif +#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND + /** Circular buffer holding for holding audio data for this mapping. */ + R3PTRTYPE(PRTCIRCBUF) pCircBuf; +#endif +} HDASTREAMMAP; +AssertCompileSizeAlignment(HDASTREAMMAP, 8); +typedef HDASTREAMMAP *PHDASTREAMMAP; + +/** @name Stream mapping functions. + * @{ + */ +#ifdef IN_RING3 +int hdaR3StreamMapInit(PHDASTREAMMAP pMapping, PPDMAUDIOPCMPROPS pProps); +void hdaR3StreamMapDestroy(PHDASTREAMMAP pMapping); +void hdaR3StreamMapReset(PHDASTREAMMAP pMapping); +#endif /* IN_RING3 */ +/** @} */ + +#endif /* !VBOX_INCLUDED_SRC_Audio_HDAStreamMap_h */ + diff --git a/src/VBox/Devices/Audio/HDAStreamPeriod.cpp b/src/VBox/Devices/Audio/HDAStreamPeriod.cpp new file mode 100644 index 00000000..558e8a99 --- /dev/null +++ b/src/VBox/Devices/Audio/HDAStreamPeriod.cpp @@ -0,0 +1,431 @@ +/* $Id: HDAStreamPeriod.cpp $ */ +/** @file + * HDAStreamPeriod.cpp - Stream period functions for HD Audio. + * + * Utility functions for handling HDA audio stream periods. Stream period + * handling is needed in order to keep track of a stream's timing + * and processed audio data. + * + * As the HDA device only has one bit clock (WALCLK) but audio streams can be + * processed at certain points in time, these functions can be used to estimate + * and schedule the wall clock (WALCLK) for all streams accordingly. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_HDA +#include <VBox/log.h> + +#include <iprt/asm-math.h> /* For ASMMultU64ByU32DivByU32(). */ + +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdmaudioifs.h> + +#include "DrvAudio.h" +#include "HDAStreamPeriod.h" + + +#ifdef IN_RING3 /* entire file currently */ + +/** + * Creates a stream period. + * + * @return IPRT status code. + * @param pPeriod Stream period to initialize. + */ +int hdaR3StreamPeriodCreate(PHDASTREAMPERIOD pPeriod) +{ + Assert(!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_VALID)); + + int rc = RTCritSectInit(&pPeriod->CritSect); + AssertRCReturnStmt(rc, pPeriod->fStatus = 0, rc); + pPeriod->fStatus = HDASTREAMPERIOD_FLAG_VALID; + + return VINF_SUCCESS; +} + +/** + * Destroys a formerly created stream period. + * + * @param pPeriod Stream period to destroy. + */ +void hdaR3StreamPeriodDestroy(PHDASTREAMPERIOD pPeriod) +{ + if (pPeriod->fStatus & HDASTREAMPERIOD_FLAG_VALID) + { + RTCritSectDelete(&pPeriod->CritSect); + + pPeriod->fStatus = HDASTREAMPERIOD_FLAG_NONE; + } +} + +/** + * Initializes a given stream period with needed parameters. + * + * @return VBox status code. + * @param pPeriod Stream period to (re-)initialize. Must be created with hdaR3StreamPeriodCreate() first. + * @param u8SD Stream descriptor (serial data #) number to assign this stream period to. + * @param u16LVI The HDA stream's LVI value to use for the period calculation. + * @param u32CBL The HDA stream's CBL value to use for the period calculation. + * @param pStreamCfg Audio stream configuration to use for this period. + */ +int hdaR3StreamPeriodInit(PHDASTREAMPERIOD pPeriod, + uint8_t u8SD, uint16_t u16LVI, uint32_t u32CBL, PPDMAUDIOSTREAMCFG pStreamCfg) +{ + if ( !u16LVI + || !u32CBL + || !DrvAudioHlpPCMPropsAreValid(&pStreamCfg->Props)) + { + return VERR_INVALID_PARAMETER; + } + + /* + * Linux guests (at least Ubuntu): + * 17632 bytes (CBL) / 4 (frame size) = 4408 frames / 4 (LVI) = 1102 frames per period + * + * Windows guests (Win10 AU): + * 3584 bytes (CBL) / 4 (frame size) = 896 frames / 2 (LVI) = 448 frames per period + */ + unsigned cTotalPeriods = u16LVI + 1; + + if (cTotalPeriods <= 1) + cTotalPeriods = 2; /* At least two periods *must* be present (LVI >= 1). */ + + uint32_t framesToTransfer = (u32CBL / 4 /** @todo Define frame size? */) / cTotalPeriods; + + pPeriod->u8SD = u8SD; + pPeriod->u64StartWalClk = 0; + pPeriod->u32Hz = pStreamCfg->Props.uHz; + pPeriod->u64DurationWalClk = hdaR3StreamPeriodFramesToWalClk(pPeriod, framesToTransfer); + pPeriod->u64ElapsedWalClk = 0; + pPeriod->i64DelayWalClk = 0; + pPeriod->framesToTransfer = framesToTransfer; + pPeriod->framesTransferred = 0; + pPeriod->cIntPending = 0; + + Log3Func(("[SD%RU8] %RU64 long, Hz=%RU32, CBL=%RU32, LVI=%RU16 -> %u periods, %RU32 frames each\n", + pPeriod->u8SD, pPeriod->u64DurationWalClk, pPeriod->u32Hz, u32CBL, u16LVI, + cTotalPeriods, pPeriod->framesToTransfer)); + + return VINF_SUCCESS; +} + +/** + * Resets a stream period to its initial state. + * + * @param pPeriod Stream period to reset. + */ +void hdaR3StreamPeriodReset(PHDASTREAMPERIOD pPeriod) +{ + Log3Func(("[SD%RU8]\n", pPeriod->u8SD)); + + if (pPeriod->cIntPending) + LogRelMax(50, ("HDA: Warning: %RU8 interrupts for stream #%RU8 still pending -- so a period reset might trigger audio hangs\n", + pPeriod->cIntPending, pPeriod->u8SD)); + + pPeriod->fStatus &= ~HDASTREAMPERIOD_FLAG_ACTIVE; + pPeriod->u64StartWalClk = 0; + pPeriod->u64ElapsedWalClk = 0; + pPeriod->framesTransferred = 0; + pPeriod->cIntPending = 0; +# ifdef LOG_ENABLED + pPeriod->Dbg.tsStartNs = 0; +# endif +} + +/** + * Begins a new period life span of a given period. + * + * @return IPRT status code. + * @param pPeriod Stream period to begin new life span for. + * @param u64WalClk Wall clock (WALCLK) value to set for the period's starting point. + */ +int hdaR3StreamPeriodBegin(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk) +{ + Assert(!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE)); /* No nested calls. */ + + pPeriod->fStatus |= HDASTREAMPERIOD_FLAG_ACTIVE; + pPeriod->u64StartWalClk = u64WalClk; + pPeriod->u64ElapsedWalClk = 0; + pPeriod->framesTransferred = 0; + pPeriod->cIntPending = 0; +# ifdef LOG_ENABLED + pPeriod->Dbg.tsStartNs = RTTimeNanoTS(); +# endif + + Log3Func(("[SD%RU8] Starting @ %RU64 (%RU64 long)\n", pPeriod->u8SD, pPeriod->u64StartWalClk, pPeriod->u64DurationWalClk)); + return VINF_SUCCESS; +} + +/** + * Ends a formerly begun period life span. + * + * @param pPeriod Stream period to end life span for. + */ +void hdaR3StreamPeriodEnd(PHDASTREAMPERIOD pPeriod) +{ + Log3Func(("[SD%RU8] Took %zuus\n", pPeriod->u8SD, (RTTimeNanoTS() - pPeriod->Dbg.tsStartNs) / 1000)); + + if (!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE)) + return; + + /* Sanity. */ + AssertMsg(pPeriod->cIntPending == 0, + ("%RU8 interrupts for stream #%RU8 still pending -- so ending a period might trigger audio hangs\n", + pPeriod->cIntPending, pPeriod->u8SD)); + Assert(hdaR3StreamPeriodIsComplete(pPeriod)); + + pPeriod->fStatus &= ~HDASTREAMPERIOD_FLAG_ACTIVE; +} + +/** + * Pauses a period. All values remain intact. + * + * @param pPeriod Stream period to pause. + */ +void hdaR3StreamPeriodPause(PHDASTREAMPERIOD pPeriod) +{ + AssertMsg((pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE), ("Period %p already in inactive state\n", pPeriod)); + + pPeriod->fStatus &= ~HDASTREAMPERIOD_FLAG_ACTIVE; + + Log3Func(("[SD%RU8]\n", pPeriod->u8SD)); +} + +/** + * Resumes a formerly paused period. + * + * @param pPeriod Stream period to resume. + */ +void hdaR3StreamPeriodResume(PHDASTREAMPERIOD pPeriod) +{ + AssertMsg(!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE), ("Period %p already in active state\n", pPeriod)); + + pPeriod->fStatus |= HDASTREAMPERIOD_FLAG_ACTIVE; + + Log3Func(("[SD%RU8]\n", pPeriod->u8SD)); +} + +/** + * Locks a stream period for serializing access. + * + * @return true if locking was successful, false if not. + * @param pPeriod Stream period to lock. + */ +bool hdaR3StreamPeriodLock(PHDASTREAMPERIOD pPeriod) +{ + return RT_SUCCESS(RTCritSectEnter(&pPeriod->CritSect)); +} + +/** + * Unlocks a formerly locked stream period. + * + * @param pPeriod Stream period to unlock. + */ +void hdaR3StreamPeriodUnlock(PHDASTREAMPERIOD pPeriod) +{ + int rc2 = RTCritSectLeave(&pPeriod->CritSect); + AssertRC(rc2); +} + +/** + * Returns the wall clock (WALCLK) value for a given amount of stream period audio frames. + * + * @return Calculated wall clock value. + * @param pPeriod Stream period to calculate wall clock value for. + * @param uFrames Number of audio frames to calculate wall clock value for. + * + * @remark Calculation depends on the given stream period and assumes a 24 MHz wall clock counter (WALCLK). + */ +uint64_t hdaR3StreamPeriodFramesToWalClk(PHDASTREAMPERIOD pPeriod, uint32_t uFrames) +{ + /* Prevent division by zero. */ + const uint32_t uHz = pPeriod->u32Hz ? pPeriod->u32Hz : 1; + + /* 24 MHz wall clock (WALCLK): 42ns resolution. */ + return ASMMultU64ByU32DivByU32(uFrames, 24000000, uHz); +} + +/** + * Returns the absolute wall clock (WALCLK) value for the already elapsed time of + * a given stream period. + * + * @return Absolute elapsed time as wall clock (WALCLK) value. + * @param pPeriod Stream period to use. + */ +uint64_t hdaR3StreamPeriodGetAbsElapsedWalClk(PHDASTREAMPERIOD pPeriod) +{ + return pPeriod->u64StartWalClk + + pPeriod->u64ElapsedWalClk + + pPeriod->i64DelayWalClk; +} + +/** + * Returns the absolute wall clock (WALCLK) value for the calculated end time of + * a given stream period. + * + * @return Absolute end time as wall clock (WALCLK) value. + * @param pPeriod Stream period to use. + */ +uint64_t hdaR3StreamPeriodGetAbsEndWalClk(PHDASTREAMPERIOD pPeriod) +{ + return pPeriod->u64StartWalClk + pPeriod->u64DurationWalClk; +} + +/** + * Returns the remaining audio frames to process for a given stream period. + * + * @return Number of remaining audio frames to process. 0 if all were processed. + * @param pPeriod Stream period to return value for. + */ +uint32_t hdaR3StreamPeriodGetRemainingFrames(PHDASTREAMPERIOD pPeriod) +{ + Assert(pPeriod->framesToTransfer >= pPeriod->framesTransferred); + return pPeriod->framesToTransfer - pPeriod->framesTransferred; +} + +/** + * Tells whether a given stream period has elapsed (time-wise) or not. + * + * @return true if the stream period has elapsed, false if not. + * @param pPeriod Stream period to get status for. + */ +bool hdaR3StreamPeriodHasElapsed(PHDASTREAMPERIOD pPeriod) +{ + return (pPeriod->u64ElapsedWalClk >= pPeriod->u64DurationWalClk); +} + +/** + * Tells whether a given stream period has passed the given absolute wall clock (WALCLK) + * time or not + * + * @return true if the stream period has passed the given time, false if not. + * @param pPeriod Stream period to get status for. + * @param u64WalClk Absolute wall clock (WALCLK) time to check for. + */ +bool hdaR3StreamPeriodHasPassedAbsWalClk(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk) +{ + /* Period not in use? */ + if (!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE)) + return true; /* ... implies that it has passed. */ + + if (hdaR3StreamPeriodHasElapsed(pPeriod)) + return true; /* Period already has elapsed. */ + + return (pPeriod->u64StartWalClk + pPeriod->u64ElapsedWalClk) >= u64WalClk; +} + +/** + * Tells whether a given stream period has some required interrupts pending or not. + * + * @return true if period has interrupts pending, false if not. + * @param pPeriod Stream period to get status for. + */ +bool hdaR3StreamPeriodNeedsInterrupt(PHDASTREAMPERIOD pPeriod) +{ + return pPeriod->cIntPending > 0; +} + +/** + * Acquires (references) an (pending) interrupt for a given stream period. + * + * @param pPeriod Stream period to acquire interrupt for. + * + * @remark This routine does not do any actual interrupt processing; it only + * keeps track of the required (pending) interrupts for a stream period. + */ +void hdaR3StreamPeriodAcquireInterrupt(PHDASTREAMPERIOD pPeriod) +{ + uint32_t cIntPending = pPeriod->cIntPending; + if (cIntPending) + { + Log3Func(("[SD%RU8] Already pending\n", pPeriod->u8SD)); + return; + } + + pPeriod->cIntPending++; + + Log3Func(("[SD%RU8] %RU32\n", pPeriod->u8SD, pPeriod->cIntPending)); +} + +/** + * Releases (dereferences) a pending interrupt. + * + * @param pPeriod Stream period to release pending interrupt for. + */ +void hdaR3StreamPeriodReleaseInterrupt(PHDASTREAMPERIOD pPeriod) +{ + Assert(pPeriod->cIntPending); + pPeriod->cIntPending--; + + Log3Func(("[SD%RU8] %RU32\n", pPeriod->u8SD, pPeriod->cIntPending)); +} + +/** + * Adds an amount of (processed) audio frames to a given stream period. + * + * @return IPRT status code. + * @param pPeriod Stream period to add audio frames to. + * @param framesInc Audio frames to add. + */ +void hdaR3StreamPeriodInc(PHDASTREAMPERIOD pPeriod, uint32_t framesInc) +{ + pPeriod->framesTransferred += framesInc; + Assert(pPeriod->framesTransferred <= pPeriod->framesToTransfer); + + pPeriod->u64ElapsedWalClk = hdaR3StreamPeriodFramesToWalClk(pPeriod, pPeriod->framesTransferred); + Assert(pPeriod->u64ElapsedWalClk <= pPeriod->u64DurationWalClk); + + Log3Func(("[SD%RU8] cbTransferred=%RU32, u64ElapsedWalClk=%RU64\n", + pPeriod->u8SD, pPeriod->framesTransferred, pPeriod->u64ElapsedWalClk)); +} + +/** + * Tells whether a given stream period is considered as complete or not. + * + * @return true if stream period is complete, false if not. + * @param pPeriod Stream period to report status for. + * + * @remark A stream period is considered complete if it has 1) passed (elapsed) its calculated period time + * and 2) processed all required audio frames. + */ +bool hdaR3StreamPeriodIsComplete(PHDASTREAMPERIOD pPeriod) +{ + const bool fIsComplete = /* Has the period elapsed time-wise? */ + hdaR3StreamPeriodHasElapsed(pPeriod) + /* All frames transferred? */ + && pPeriod->framesTransferred >= pPeriod->framesToTransfer; +# ifdef VBOX_STRICT + if (fIsComplete) + { + Assert(pPeriod->framesTransferred == pPeriod->framesToTransfer); + Assert(pPeriod->u64ElapsedWalClk == pPeriod->u64DurationWalClk); + } +# endif + + Log3Func(("[SD%RU8] Period %s - runtime %RU64 / %RU64 (abs @ %RU64, starts @ %RU64, ends @ %RU64), %RU8 IRQs pending\n", + pPeriod->u8SD, + fIsComplete ? "COMPLETE" : "NOT COMPLETE YET", + pPeriod->u64ElapsedWalClk, pPeriod->u64DurationWalClk, + hdaR3StreamPeriodGetAbsElapsedWalClk(pPeriod), pPeriod->u64StartWalClk, + hdaR3StreamPeriodGetAbsEndWalClk(pPeriod), pPeriod->cIntPending)); + + return fIsComplete; +} + +#endif /* IN_RING3 */ + diff --git a/src/VBox/Devices/Audio/HDAStreamPeriod.h b/src/VBox/Devices/Audio/HDAStreamPeriod.h new file mode 100644 index 00000000..a782a3bc --- /dev/null +++ b/src/VBox/Devices/Audio/HDAStreamPeriod.h @@ -0,0 +1,114 @@ +/* $Id: HDAStreamPeriod.h $ */ +/** @file + * HDAStreamPeriod.h - Stream period functions for HD Audio. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_HDAStreamPeriod_h +#define VBOX_INCLUDED_SRC_Audio_HDAStreamPeriod_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/critsect.h> +#ifdef DEBUG +# include <iprt/time.h> +#endif +#include <VBox/log.h> /* LOG_ENABLED */ + +struct HDASTREAM; +typedef HDASTREAM *PHDASTREAM; + +#ifdef LOG_ENABLED +/** + * Structure for debug information of an HDA stream's period. + */ +typedef struct HDASTREAMPERIODDBGINFO +{ + /** Host start time (in ns) of the period. */ + uint64_t tsStartNs; +} HDASTREAMPERIODDBGINFO, *PHDASTREAMPERIODDBGINFO; +#endif + +/** No flags set. */ +#define HDASTREAMPERIOD_FLAG_NONE 0 +/** The stream period has been initialized and is in a valid state. */ +#define HDASTREAMPERIOD_FLAG_VALID RT_BIT(0) +/** The stream period is active. */ +#define HDASTREAMPERIOD_FLAG_ACTIVE RT_BIT(1) + +/** + * Structure for keeping an HDA stream's (time) period. + * This is needed in order to keep track of stream timing and interrupt delivery. + */ +typedef struct HDASTREAMPERIOD +{ + /** Critical section for serializing access. */ + RTCRITSECT CritSect; + /** Associated HDA stream descriptor (SD) number. */ + uint8_t u8SD; + /** The period's status flags. */ + uint8_t fStatus; + /** Number of pending interrupts required for this period. */ + uint8_t cIntPending; + uint8_t bPadding0; + /** Hertz (Hz) rate this period runs with. */ + uint32_t u32Hz; + /** Period start time (in wall clock counts). */ + uint64_t u64StartWalClk; + /** Period duration (in wall clock counts). */ + uint64_t u64DurationWalClk; + /** The period's (relative) elapsed time (in wall clock counts). */ + uint64_t u64ElapsedWalClk; + /** Delay (in wall clock counts) for tweaking the period timing. Optional. */ + int64_t i64DelayWalClk; + /** Number of audio frames to transfer for this period. */ + uint32_t framesToTransfer; + /** Number of audio frames already transfered. */ + uint32_t framesTransferred; +#ifdef LOG_ENABLED + /** Debugging information. */ + HDASTREAMPERIODDBGINFO Dbg; +#endif +} HDASTREAMPERIOD; +AssertCompileSizeAlignment(HDASTREAMPERIOD, 8); +/** Pointer to a HDA stream's time period keeper. */ +typedef HDASTREAMPERIOD *PHDASTREAMPERIOD; + +#ifdef IN_RING3 +int hdaR3StreamPeriodCreate(PHDASTREAMPERIOD pPeriod); +void hdaR3StreamPeriodDestroy(PHDASTREAMPERIOD pPeriod); +int hdaR3StreamPeriodInit(PHDASTREAMPERIOD pPeriod, uint8_t u8SD, uint16_t u16LVI, uint32_t u32CBL, PPDMAUDIOSTREAMCFG pStreamCfg); +void hdaR3StreamPeriodReset(PHDASTREAMPERIOD pPeriod); +int hdaR3StreamPeriodBegin(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk); +void hdaR3StreamPeriodEnd(PHDASTREAMPERIOD pPeriod); +void hdaR3StreamPeriodPause(PHDASTREAMPERIOD pPeriod); +void hdaR3StreamPeriodResume(PHDASTREAMPERIOD pPeriod); +bool hdaR3StreamPeriodLock(PHDASTREAMPERIOD pPeriod); +void hdaR3StreamPeriodUnlock(PHDASTREAMPERIOD pPeriod); +uint64_t hdaR3StreamPeriodFramesToWalClk(PHDASTREAMPERIOD pPeriod, uint32_t uFrames); +uint64_t hdaR3StreamPeriodGetAbsEndWalClk(PHDASTREAMPERIOD pPeriod); +uint64_t hdaR3StreamPeriodGetAbsElapsedWalClk(PHDASTREAMPERIOD pPeriod); +uint32_t hdaR3StreamPeriodGetRemainingFrames(PHDASTREAMPERIOD pPeriod); +bool hdaR3StreamPeriodHasElapsed(PHDASTREAMPERIOD pPeriod); +bool hdaR3StreamPeriodHasPassedAbsWalClk(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk); +bool hdaR3StreamPeriodNeedsInterrupt(PHDASTREAMPERIOD pPeriod); +void hdaR3StreamPeriodAcquireInterrupt(PHDASTREAMPERIOD pPeriod); +void hdaR3StreamPeriodReleaseInterrupt(PHDASTREAMPERIOD pPeriod); +void hdaR3StreamPeriodInc(PHDASTREAMPERIOD pPeriod, uint32_t framesInc); +bool hdaR3StreamPeriodIsComplete(PHDASTREAMPERIOD pPeriod); +#endif /* IN_RING3 */ + +#endif /* !VBOX_INCLUDED_SRC_Audio_HDAStreamPeriod_h */ + diff --git a/src/VBox/Devices/Audio/Makefile.kup b/src/VBox/Devices/Audio/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/Audio/Makefile.kup diff --git a/src/VBox/Devices/Audio/VBoxMMNotificationClient.cpp b/src/VBox/Devices/Audio/VBoxMMNotificationClient.cpp new file mode 100644 index 00000000..92ae2119 --- /dev/null +++ b/src/VBox/Devices/Audio/VBoxMMNotificationClient.cpp @@ -0,0 +1,246 @@ +/* $Id: VBoxMMNotificationClient.cpp $ */ +/** @file + * VBoxMMNotificationClient.cpp - Implementation of the IMMNotificationClient interface + * to detect audio endpoint changes. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "VBoxMMNotificationClient.h" + +#include <iprt/win/windows.h> + +#pragma warning(push) +#pragma warning(disable: 4201) +#include <mmdeviceapi.h> +#include <endpointvolume.h> +#pragma warning(pop) + +#ifdef LOG_GROUP +# undef LOG_GROUP +#endif +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <VBox/log.h> + +VBoxMMNotificationClient::VBoxMMNotificationClient(void) + : m_fRegisteredClient(false) + , m_cRef(1) +{ +} + +VBoxMMNotificationClient::~VBoxMMNotificationClient(void) +{ +} + +/** + * Uninitializes the mulitmedia notification client implementation. + */ +void VBoxMMNotificationClient::Dispose(void) +{ + DetachFromEndpoint(); + + if (m_fRegisteredClient) + { + m_pEnum->UnregisterEndpointNotificationCallback(this); + + m_fRegisteredClient = false; + } +} + +/** + * Initializes the mulitmedia notification client implementation. + * + * @return HRESULT + */ +HRESULT VBoxMMNotificationClient::Initialize(void) +{ + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), + (void **)&m_pEnum); + if (SUCCEEDED(hr)) + { + hr = m_pEnum->RegisterEndpointNotificationCallback(this); + if (SUCCEEDED(hr)) + { + m_fRegisteredClient = true; + + hr = AttachToDefaultEndpoint(); + } + } + + LogFunc(("Returning %Rhrc\n", hr)); + return hr; +} + +/** + * Registration callback implementation for storing our (required) contexts. + * + * @return IPRT status code. + * @param pDrvIns Driver instance to register the notification client to. + * @param pfnCallback Audio callback to call by the notification client in case of new events. + */ +int VBoxMMNotificationClient::RegisterCallback(PPDMDRVINS pDrvIns, PFNPDMHOSTAUDIOCALLBACK pfnCallback) +{ + this->m_pDrvIns = pDrvIns; + this->m_pfnCallback = pfnCallback; + + return VINF_SUCCESS; +} + +/** + * Unregistration callback implementation for cleaning up our mess when we're done handling + * with notifications. + */ +void VBoxMMNotificationClient::UnregisterCallback(void) +{ + this->m_pDrvIns = NULL; + this->m_pfnCallback = NULL; +} + +/** + * Stub being called when attaching to the default audio endpoint. + * Does nothing at the moment. + */ +HRESULT VBoxMMNotificationClient::AttachToDefaultEndpoint(void) +{ + return S_OK; +} + +/** + * Stub being called when detaching from the default audio endpoint. + * Does nothing at the moment. + */ +void VBoxMMNotificationClient::DetachFromEndpoint(void) +{ + +} + +/** + * Handler implementation which is called when an audio device state + * has been changed. + * + * @return HRESULT + * @param pwstrDeviceId Device ID the state is announced for. + * @param dwNewState New state the device is now in. + */ +STDMETHODIMP VBoxMMNotificationClient::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) +{ + char *pszState = "unknown"; + + switch (dwNewState) + { + case DEVICE_STATE_ACTIVE: + pszState = "active"; + break; + case DEVICE_STATE_DISABLED: + pszState = "disabled"; + break; + case DEVICE_STATE_NOTPRESENT: + pszState = "not present"; + break; + case DEVICE_STATE_UNPLUGGED: + pszState = "unplugged"; + break; + default: + break; + } + + LogRel2(("Audio: Device '%ls' has changed state to '%s'\n", pwstrDeviceId, pszState)); + +#ifdef VBOX_WITH_AUDIO_CALLBACKS + AssertPtr(this->m_pDrvIns); + AssertPtr(this->m_pfnCallback); + + if (this->m_pfnCallback) + /* Ignore rc */ this->m_pfnCallback(this->m_pDrvIns, PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED, NULL, 0); +#endif + + return S_OK; +} + +/** + * Handler implementation which is called when a new audio device has been added. + * + * @return HRESULT + * @param pwstrDeviceId Device ID which has been added. + */ +STDMETHODIMP VBoxMMNotificationClient::OnDeviceAdded(LPCWSTR pwstrDeviceId) +{ + RT_NOREF(pwstrDeviceId); + LogFunc(("%ls\n", pwstrDeviceId)); + return S_OK; +} + +/** + * Handler implementation which is called when an audio device has been removed. + * + * @return HRESULT + * @param pwstrDeviceId Device ID which has been removed. + */ +STDMETHODIMP VBoxMMNotificationClient::OnDeviceRemoved(LPCWSTR pwstrDeviceId) +{ + RT_NOREF(pwstrDeviceId); + LogFunc(("%ls\n", pwstrDeviceId)); + return S_OK; +} + +/** + * Handler implementation which is called when the device audio device has been + * changed. + * + * @return HRESULT + * @param eFlow Flow direction of the new default device. + * @param eRole Role of the new default device. + * @param pwstrDefaultDeviceId ID of the new default device. + */ +STDMETHODIMP VBoxMMNotificationClient::OnDefaultDeviceChanged(EDataFlow eFlow, ERole eRole, LPCWSTR pwstrDefaultDeviceId) +{ + RT_NOREF(eFlow, eRole, pwstrDefaultDeviceId); + + if (eFlow == eRender) + { + + } + + return S_OK; +} + +STDMETHODIMP VBoxMMNotificationClient::QueryInterface(REFIID interfaceID, void **ppvInterface) +{ + const IID IID_IMMNotificationClient = __uuidof(IMMNotificationClient); + + if ( IsEqualIID(interfaceID, IID_IUnknown) + || IsEqualIID(interfaceID, IID_IMMNotificationClient)) + { + *ppvInterface = static_cast<IMMNotificationClient*>(this); + AddRef(); + return S_OK; + } + + *ppvInterface = NULL; + return E_NOINTERFACE; +} + +STDMETHODIMP_(ULONG) VBoxMMNotificationClient::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) VBoxMMNotificationClient::Release(void) +{ + long lRef = InterlockedDecrement(&m_cRef); + if (lRef == 0) + delete this; + + return lRef; +} + diff --git a/src/VBox/Devices/Audio/VBoxMMNotificationClient.h b/src/VBox/Devices/Audio/VBoxMMNotificationClient.h new file mode 100644 index 00000000..6d63174c --- /dev/null +++ b/src/VBox/Devices/Audio/VBoxMMNotificationClient.h @@ -0,0 +1,91 @@ +/* $Id: VBoxMMNotificationClient.h $ */ +/** @file + * VBoxMMNotificationClient.h - Implementation of the IMMNotificationClient interface + * to detect audio endpoint changes. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_VBoxMMNotificationClient_h +#define VBOX_INCLUDED_SRC_Audio_VBoxMMNotificationClient_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/critsect.h> +#include <iprt/win/windows.h> + +/* Should fix warning in include\ks.h. */ +#ifndef _WIN64 +# ifdef RT_ARCH_X86 +# define _WIN64 1 +# else +# define _WIN64 0 +# endif +#endif + +#include <Mmdeviceapi.h> + +#include "DrvAudio.h" + +class VBoxMMNotificationClient : IMMNotificationClient +{ +public: + + VBoxMMNotificationClient(); + virtual ~VBoxMMNotificationClient(); + + HRESULT Initialize(); + int RegisterCallback(PPDMDRVINS pDrvIns, PFNPDMHOSTAUDIOCALLBACK pfnCallback); + void UnregisterCallback(void); + void Dispose(); + + /** @name IUnknown interface + * @{ */ + IFACEMETHODIMP_(ULONG) Release(); + /** @} */ + +private: + + bool m_fRegisteredClient; + IMMDeviceEnumerator *m_pEnum; + IMMDevice *m_pEndpoint; + + long m_cRef; + + PPDMDRVINS m_pDrvIns; + PFNPDMHOSTAUDIOCALLBACK m_pfnCallback; + + HRESULT AttachToDefaultEndpoint(); + void DetachFromEndpoint(); + + /** @name IMMNotificationClient interface + * @{ */ + IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState); + IFACEMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId); + IFACEMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId); + IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId); + IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) { return S_OK; } + IFACEMETHODIMP OnDeviceQueryRemove() { return S_OK; } + IFACEMETHODIMP OnDeviceQueryRemoveFailed() { return S_OK; } + IFACEMETHODIMP OnDeviceRemovePending() { return S_OK; } + /** @} */ + + /** @name IUnknown interface + * @{ */ + IFACEMETHODIMP QueryInterface(const IID& iid, void** ppUnk); + IFACEMETHODIMP_(ULONG) AddRef(); + /** @} */ +}; +#endif /* !VBOX_INCLUDED_SRC_Audio_VBoxMMNotificationClient_h */ + diff --git a/src/VBox/Devices/Audio/alsa_mangling.h b/src/VBox/Devices/Audio/alsa_mangling.h new file mode 100644 index 00000000..118fea8f --- /dev/null +++ b/src/VBox/Devices/Audio/alsa_mangling.h @@ -0,0 +1,72 @@ +/* $Id: alsa_mangling.h $ */ +/** @file + * Mangle libasound symbols. + * + * This is necessary on hosts which don't support the -fvisibility gcc switch. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_alsa_mangling_h +#define VBOX_INCLUDED_SRC_Audio_alsa_mangling_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define ALSA_MANGLER(symbol) VBox_##symbol + +#define snd_lib_error_set_handler ALSA_MANGLER(snd_lib_error_set_handler) +#define snd_strerror ALSA_MANGLER(snd_strerror) + +#define snd_device_name_hint ALSA_MANGLER(snd_device_name_hint) +#define snd_device_name_get_hint ALSA_MANGLER(snd_device_name_get_hint) +#define snd_device_name_free_hint ALSA_MANGLER(snd_device_name_free_hint) + +#define snd_pcm_avail_update ALSA_MANGLER(snd_pcm_avail_update) +#define snd_pcm_close ALSA_MANGLER(snd_pcm_close) +#define snd_pcm_delay ALSA_MANGLER(snd_pcm_delay) +#define snd_pcm_drain ALSA_MANGLER(snd_pcm_drain) +#define snd_pcm_drop ALSA_MANGLER(snd_pcm_drop) +#define snd_pcm_nonblock ALSA_MANGLER(snd_pcm_nonblock) +#define snd_pcm_open ALSA_MANGLER(snd_pcm_open) +#define snd_pcm_prepare ALSA_MANGLER(snd_pcm_prepare) +#define snd_pcm_readi ALSA_MANGLER(snd_pcm_readi) +#define snd_pcm_resume ALSA_MANGLER(snd_pcm_resume) +#define snd_pcm_start ALSA_MANGLER(snd_pcm_start) +#define snd_pcm_state ALSA_MANGLER(snd_pcm_state) +#define snd_pcm_writei ALSA_MANGLER(snd_pcm_writei) + +#define snd_pcm_hw_params ALSA_MANGLER(snd_pcm_hw_params) +#define snd_pcm_hw_params_any ALSA_MANGLER(snd_pcm_hw_params_any) +#define snd_pcm_hw_params_sizeof ALSA_MANGLER(snd_pcm_hw_params_sizeof) +#define snd_pcm_hw_params_get_buffer_size ALSA_MANGLER(snd_pcm_hw_params_get_buffer_size) +#define snd_pcm_hw_params_get_period_size_min ALSA_MANGLER(snd_pcm_hw_params_get_period_size_min) +#define snd_pcm_hw_params_set_rate_near ALSA_MANGLER(snd_pcm_hw_params_set_rate_near) +#define snd_pcm_hw_params_set_access ALSA_MANGLER(snd_pcm_hw_params_set_access) +#define snd_pcm_hw_params_set_buffer_time_near ALSA_MANGLER(snd_pcm_hw_params_set_buffer_time_near) +#define snd_pcm_hw_params_set_buffer_size_near ALSA_MANGLER(snd_pcm_hw_params_set_buffer_size_near) +#define snd_pcm_hw_params_get_buffer_size_min ALSA_MANGLER(snd_pcm_hw_params_get_buffer_size_min) +#define snd_pcm_hw_params_set_channels_near ALSA_MANGLER(snd_pcm_hw_params_set_channels_near) +#define snd_pcm_hw_params_set_format ALSA_MANGLER(snd_pcm_hw_params_set_format) +#define snd_pcm_hw_params_get_period_size ALSA_MANGLER(snd_pcm_hw_params_get_period_size) +#define snd_pcm_hw_params_set_period_size_near ALSA_MANGLER(snd_pcm_hw_params_set_period_size_near) +#define snd_pcm_hw_params_set_period_time_near ALSA_MANGLER(snd_pcm_hw_params_set_period_time_near) + +#define snd_pcm_sw_params ALSA_MANGLER(snd_pcm_sw_params) +#define snd_pcm_sw_params_current ALSA_MANGLER(snd_pcm_sw_params_current) +#define snd_pcm_sw_params_get_start_threshold ALSA_MANGLER(snd_pcm_sw_params_get_start_threshold) +#define snd_pcm_sw_params_set_avail_min ALSA_MANGLER(snd_pcm_sw_params_set_avail_min) +#define snd_pcm_sw_params_set_start_threshold ALSA_MANGLER(snd_pcm_sw_params_set_start_threshold) +#define snd_pcm_sw_params_sizeof ALSA_MANGLER(snd_pcm_sw_params_sizeof) + +#endif /* !VBOX_INCLUDED_SRC_Audio_alsa_mangling_h */ diff --git a/src/VBox/Devices/Audio/alsa_stubs.c b/src/VBox/Devices/Audio/alsa_stubs.c new file mode 100644 index 00000000..db40bc45 --- /dev/null +++ b/src/VBox/Devices/Audio/alsa_stubs.c @@ -0,0 +1,243 @@ +/* $Id: alsa_stubs.c $ */ +/** @file + * Stubs for libasound. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <iprt/assert.h> +#include <iprt/ldr.h> +#include <VBox/log.h> +#include <iprt/errcore.h> + +#include <alsa/asoundlib.h> + +#include "alsa_stubs.h" + +#define VBOX_ALSA_LIB "libasound.so.2" + +#define PROXY_STUB(function, rettype, signature, shortsig) \ + static rettype (*pfn_ ## function) signature; \ + \ + rettype VBox_##function signature; \ + rettype VBox_##function signature \ + { \ + return pfn_ ## function shortsig; \ + } + +PROXY_STUB(snd_lib_error_set_handler, int, (snd_lib_error_handler_t handler), + (handler)) +PROXY_STUB(snd_strerror, const char *, (int errnum), (errnum)) + +PROXY_STUB(snd_device_name_hint, int, + (int card, const char *iface, void ***hints), + (card, iface, hints)) +PROXY_STUB(snd_device_name_free_hint, int, + (void **hints), + (hints)) +PROXY_STUB(snd_device_name_get_hint, char *, + (const void *hint, const char *id), + (hint, id)) + +/* + * PCM + */ + +PROXY_STUB(snd_pcm_avail_update, snd_pcm_sframes_t, (snd_pcm_t *pcm), + (pcm)) +PROXY_STUB(snd_pcm_close, int, (snd_pcm_t *pcm), (pcm)) +PROXY_STUB(snd_pcm_delay, int, (snd_pcm_t *pcm, snd_pcm_sframes_t *frames), + (pcm, frames)) +PROXY_STUB(snd_pcm_nonblock, int, (snd_pcm_t *pcm, int *onoff), + (pcm, onoff)) +PROXY_STUB(snd_pcm_drain, int, (snd_pcm_t *pcm), + (pcm)) +PROXY_STUB(snd_pcm_drop, int, (snd_pcm_t *pcm), (pcm)) +PROXY_STUB(snd_pcm_open, int, + (snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode), + (pcm, name, stream, mode)) +PROXY_STUB(snd_pcm_prepare, int, (snd_pcm_t *pcm), (pcm)) +PROXY_STUB(snd_pcm_readi, snd_pcm_sframes_t, + (snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size), + (pcm, buffer, size)) +PROXY_STUB(snd_pcm_resume, int, (snd_pcm_t *pcm), (pcm)) +PROXY_STUB(snd_pcm_state, snd_pcm_state_t, (snd_pcm_t *pcm), (pcm)) +PROXY_STUB(snd_pcm_writei, snd_pcm_sframes_t, + (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size), + (pcm, buffer, size)) +PROXY_STUB(snd_pcm_start, int, (snd_pcm_t *pcm), (pcm)) + +/* + * HW + */ + +PROXY_STUB(snd_pcm_hw_params, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params), + (pcm, params)) +PROXY_STUB(snd_pcm_hw_params_any, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params), + (pcm, params)) +PROXY_STUB(snd_pcm_hw_params_get_buffer_size, int, + (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val), + (params, val)) +PROXY_STUB(snd_pcm_hw_params_get_buffer_size_min, int, + (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val), + (params, val)) +PROXY_STUB(snd_pcm_hw_params_get_period_size, int, + (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir), + (params, frames, dir)) +PROXY_STUB(snd_pcm_hw_params_get_period_size_min, int, + (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir), + (params, frames, dir)) +PROXY_STUB(snd_pcm_hw_params_set_rate_near, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir), + (pcm, params, val, dir)) +PROXY_STUB(snd_pcm_hw_params_set_access, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access), + (pcm, params, _access)) +PROXY_STUB(snd_pcm_hw_params_set_buffer_time_near, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir), + (pcm, params, val, dir)) +PROXY_STUB(snd_pcm_hw_params_set_buffer_size_near, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val), + (pcm, params, val)) +PROXY_STUB(snd_pcm_hw_params_set_channels_near, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val), + (pcm, params, val)) +PROXY_STUB(snd_pcm_hw_params_set_period_size_near, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir), + (pcm, params, val, dir)) +PROXY_STUB(snd_pcm_hw_params_set_period_time_near, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir), + (pcm, params, val, dir)) +PROXY_STUB(snd_pcm_hw_params_sizeof, size_t, (void), ()) +PROXY_STUB(snd_pcm_hw_params_set_format, int, + (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val), + (pcm, params, val)) + +/* + * SW + */ + +PROXY_STUB(snd_pcm_sw_params, int, + (snd_pcm_t *pcm, snd_pcm_sw_params_t *params), + (pcm, params)) +PROXY_STUB(snd_pcm_sw_params_current, int, + (snd_pcm_t *pcm, snd_pcm_sw_params_t *params), + (pcm, params)) +PROXY_STUB(snd_pcm_sw_params_get_start_threshold, int, + (const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val), + (params, val)) +PROXY_STUB(snd_pcm_sw_params_set_avail_min, int, + (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val), + (pcm, params, val)) +PROXY_STUB(snd_pcm_sw_params_set_start_threshold, int, + (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val), + (pcm, params, val)) +PROXY_STUB(snd_pcm_sw_params_sizeof, size_t, (void), ()) + +typedef struct +{ + const char *name; + void (**fn)(void); +} SHARED_FUNC; + +#define ELEMENT(function) { #function , (void (**)(void)) & pfn_ ## function } +static SHARED_FUNC SharedFuncs[] = +{ + ELEMENT(snd_lib_error_set_handler), + ELEMENT(snd_strerror), + + ELEMENT(snd_device_name_hint), + ELEMENT(snd_device_name_get_hint), + ELEMENT(snd_device_name_free_hint), + + ELEMENT(snd_pcm_avail_update), + ELEMENT(snd_pcm_close), + ELEMENT(snd_pcm_delay), + ELEMENT(snd_pcm_drain), + ELEMENT(snd_pcm_drop), + ELEMENT(snd_pcm_nonblock), + ELEMENT(snd_pcm_open), + ELEMENT(snd_pcm_prepare), + ELEMENT(snd_pcm_resume), + ELEMENT(snd_pcm_state), + + ELEMENT(snd_pcm_readi), + ELEMENT(snd_pcm_start), + ELEMENT(snd_pcm_writei), + + ELEMENT(snd_pcm_hw_params), + ELEMENT(snd_pcm_hw_params_any), + ELEMENT(snd_pcm_hw_params_sizeof), + ELEMENT(snd_pcm_hw_params_get_buffer_size), + ELEMENT(snd_pcm_hw_params_get_buffer_size_min), + ELEMENT(snd_pcm_hw_params_get_period_size_min), + ELEMENT(snd_pcm_hw_params_set_access), + ELEMENT(snd_pcm_hw_params_set_buffer_size_near), + ELEMENT(snd_pcm_hw_params_set_buffer_time_near), + ELEMENT(snd_pcm_hw_params_set_channels_near), + ELEMENT(snd_pcm_hw_params_set_format), + ELEMENT(snd_pcm_hw_params_get_period_size), + ELEMENT(snd_pcm_hw_params_set_period_size_near), + ELEMENT(snd_pcm_hw_params_set_period_time_near), + ELEMENT(snd_pcm_hw_params_set_rate_near), + + ELEMENT(snd_pcm_sw_params), + ELEMENT(snd_pcm_sw_params_current), + ELEMENT(snd_pcm_sw_params_get_start_threshold), + ELEMENT(snd_pcm_sw_params_set_avail_min), + ELEMENT(snd_pcm_sw_params_set_start_threshold), + ELEMENT(snd_pcm_sw_params_sizeof), +}; +#undef ELEMENT + +/** + * Try to dynamically load the ALSA libraries. This function is not + * thread-safe, and should be called before attempting to use any of the + * ALSA functions. + * + * @returns iprt status code + */ +int audioLoadAlsaLib(void) +{ + int rc = VINF_SUCCESS; + unsigned i; + static enum { NO = 0, YES, FAIL } isLibLoaded = NO; + RTLDRMOD hLib; + + LogFlowFunc(("\n")); + /* If this is not NO then the function has obviously been called twice, + which is likely to be a bug. */ + if (NO != isLibLoaded) + { + AssertMsgFailed(("isLibLoaded == %s\n", YES == isLibLoaded ? "YES" : "NO")); + return YES == isLibLoaded ? VINF_SUCCESS : VERR_NOT_SUPPORTED; + } + isLibLoaded = FAIL; + rc = RTLdrLoad(VBOX_ALSA_LIB, &hLib); + if (RT_FAILURE(rc)) + { + LogRelFunc(("Failed to load library %s\n", VBOX_ALSA_LIB)); + return rc; + } + for (i=0; i<RT_ELEMENTS(SharedFuncs); i++) + { + rc = RTLdrGetSymbol(hLib, SharedFuncs[i].name, (void**)SharedFuncs[i].fn); + if (RT_FAILURE(rc)) + return rc; + } + isLibLoaded = YES; + return rc; +} + diff --git a/src/VBox/Devices/Audio/alsa_stubs.h b/src/VBox/Devices/Audio/alsa_stubs.h new file mode 100644 index 00000000..cf8fc47e --- /dev/null +++ b/src/VBox/Devices/Audio/alsa_stubs.h @@ -0,0 +1,25 @@ +/* $Id: alsa_stubs.h $ */ +/** @file + * Stubs for libasound. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_alsa_stubs_h +#define VBOX_INCLUDED_SRC_Audio_alsa_stubs_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif +extern int audioLoadAlsaLib(void); +#endif /* !VBOX_INCLUDED_SRC_Audio_alsa_stubs_h */ + diff --git a/src/VBox/Devices/Audio/pulse_mangling.h b/src/VBox/Devices/Audio/pulse_mangling.h new file mode 100644 index 00000000..7d2bf60d --- /dev/null +++ b/src/VBox/Devices/Audio/pulse_mangling.h @@ -0,0 +1,93 @@ +/* $Id: pulse_mangling.h $ */ +/** @file + * Mangle libpulse symbols. + * + * This is necessary on hosts which don't support the -fvisibility gcc switch. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_pulse_mangling_h +#define VBOX_INCLUDED_SRC_Audio_pulse_mangling_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define PULSE_MANGLER(symbol) VBox_##symbol + +#define pa_bytes_per_second PULSE_MANGLER(pa_bytes_per_second) +#define pa_bytes_to_usec PULSE_MANGLER(pa_bytes_to_usec) +#define pa_channel_map_init_auto PULSE_MANGLER(pa_channel_map_init_auto) + +#define pa_context_connect PULSE_MANGLER(pa_context_connect) +#define pa_context_disconnect PULSE_MANGLER(pa_context_disconnect) +#define pa_context_get_server_info PULSE_MANGLER(pa_context_get_server_info) +#define pa_context_get_sink_info_by_name PULSE_MANGLER(pa_context_get_sink_info_by_name) +#define pa_context_get_source_info_by_name PULSE_MANGLER(pa_context_get_source_info_by_name) +#define pa_context_get_state PULSE_MANGLER(pa_context_get_state) +#define pa_context_unref PULSE_MANGLER(pa_context_unref) +#define pa_context_errno PULSE_MANGLER(pa_context_errno) +#define pa_context_new PULSE_MANGLER(pa_context_new) +#define pa_context_set_state_callback PULSE_MANGLER(pa_context_set_state_callback) + +#define pa_frame_size PULSE_MANGLER(pa_frame_size) +#define pa_get_library_version PULSE_MANGLER(pa_get_library_version) +#define pa_operation_unref PULSE_MANGLER(pa_operation_unref) +#define pa_operation_get_state PULSE_MANGLER(pa_operation_get_state) +#define pa_operation_cancel PULSE_MANGLER(pa_operation_cancel) +#define pa_rtclock_now PULSE_MANGLER(pa_rtclock_now) +#define pa_sample_format_to_string PULSE_MANGLER(pa_sample_format_to_string) +#define pa_sample_spec_valid PULSE_MANGLER(pa_sample_spec_valid) + +#define pa_stream_connect_playback PULSE_MANGLER(pa_stream_connect_playback) +#define pa_stream_connect_record PULSE_MANGLER(pa_stream_connect_record) +#define pa_stream_cork PULSE_MANGLER(pa_stream_cork) +#define pa_stream_disconnect PULSE_MANGLER(pa_stream_disconnect) +#define pa_stream_drop PULSE_MANGLER(pa_stream_drop) +#define pa_stream_get_sample_spec PULSE_MANGLER(pa_stream_get_sample_spec) +#define pa_stream_set_latency_update_callback PULSE_MANGLER(pa_stream_set_latency_update_callback) +#define pa_stream_write PULSE_MANGLER(pa_stream_write) +#define pa_stream_unref PULSE_MANGLER(pa_stream_unref) +#define pa_stream_get_state PULSE_MANGLER(pa_stream_get_state) +#define pa_stream_get_latency PULSE_MANGLER(pa_stream_get_latency) +#define pa_stream_get_timing_info PULSE_MANGLER(pa_stream_get_timing_info) +#define pa_stream_set_buffer_attr PULSE_MANGLER(pa_stream_set_buffer_attr) +#define pa_stream_set_state_callback PULSE_MANGLER(pa_stream_set_state_callback) +#define pa_stream_set_underflow_callback PULSE_MANGLER(pa_stream_set_underflow_callback) +#define pa_stream_set_overflow_callback PULSE_MANGLER(pa_stream_set_overflow_callback) +#define pa_stream_set_write_callback PULSE_MANGLER(pa_stream_set_write_callback) +#define pa_stream_flush PULSE_MANGLER(pa_stream_flush) +#define pa_stream_drain PULSE_MANGLER(pa_stream_drain) +#define pa_stream_trigger PULSE_MANGLER(pa_stream_trigger) +#define pa_stream_new PULSE_MANGLER(pa_stream_new) +#define pa_stream_get_buffer_attr PULSE_MANGLER(pa_stream_get_buffer_attr) +#define pa_stream_peek PULSE_MANGLER(pa_stream_peek) +#define pa_stream_readable_size PULSE_MANGLER(pa_stream_readable_size) +#define pa_stream_writable_size PULSE_MANGLER(pa_stream_writable_size) + +#define pa_strerror PULSE_MANGLER(pa_strerror) + +#define pa_threaded_mainloop_stop PULSE_MANGLER(pa_threaded_mainloop_stop) +#define pa_threaded_mainloop_get_api PULSE_MANGLER(pa_threaded_mainloop_get_api) +#define pa_threaded_mainloop_free PULSE_MANGLER(pa_threaded_mainloop_free) +#define pa_threaded_mainloop_signal PULSE_MANGLER(pa_threaded_mainloop_signal) +#define pa_threaded_mainloop_unlock PULSE_MANGLER(pa_threaded_mainloop_unlock) +#define pa_threaded_mainloop_new PULSE_MANGLER(pa_threaded_mainloop_new) +#define pa_threaded_mainloop_wait PULSE_MANGLER(pa_threaded_mainloop_wait) +#define pa_threaded_mainloop_start PULSE_MANGLER(pa_threaded_mainloop_start) +#define pa_threaded_mainloop_lock PULSE_MANGLER(pa_threaded_mainloop_lock) + +#define pa_usec_to_bytes PULSE_MANGLER(pa_usec_to_bytes) + +#endif /* !VBOX_INCLUDED_SRC_Audio_pulse_mangling_h */ + diff --git a/src/VBox/Devices/Audio/pulse_stubs.c b/src/VBox/Devices/Audio/pulse_stubs.c new file mode 100644 index 00000000..0427c3c5 --- /dev/null +++ b/src/VBox/Devices/Audio/pulse_stubs.c @@ -0,0 +1,348 @@ +/* $Id: pulse_stubs.c $ */ +/** @file + * Stubs for libpulse. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <iprt/assert.h> +#include <iprt/ldr.h> +#include <VBox/log.h> +#include <iprt/errcore.h> + +#include <pulse/pulseaudio.h> + +#include "pulse_stubs.h" + +#define VBOX_PULSE_LIB "libpulse.so.0" + +#define PROXY_STUB(function, rettype, signature, shortsig) \ + static rettype (*g_pfn_ ## function) signature; \ + \ + rettype VBox_##function signature; \ + rettype VBox_##function signature \ + { \ + return g_pfn_ ## function shortsig; \ + } + +#define PROXY_STUB_VOID(function, signature, shortsig) \ + static void (*g_pfn_ ## function) signature; \ + \ + void VBox_##function signature; \ + void VBox_##function signature \ + { \ + g_pfn_ ## function shortsig; \ + } + +PROXY_STUB (pa_bytes_per_second, size_t, + (const pa_sample_spec *spec), + (spec)) +PROXY_STUB (pa_bytes_to_usec, pa_usec_t, + (uint64_t l, const pa_sample_spec *spec), + (l, spec)) +PROXY_STUB (pa_channel_map_init_auto, pa_channel_map*, + (pa_channel_map *m, unsigned channels, pa_channel_map_def_t def), + (m, channels, def)) + +PROXY_STUB (pa_context_connect, int, + (pa_context *c, const char *server, pa_context_flags_t flags, + const pa_spawn_api *api), + (c, server, flags, api)) +PROXY_STUB_VOID(pa_context_disconnect, + (pa_context *c), + (c)) +PROXY_STUB (pa_context_get_server_info, pa_operation*, + (pa_context *c, pa_server_info_cb_t cb, void *userdata), + (c, cb, userdata)) +PROXY_STUB (pa_context_get_sink_info_by_name, pa_operation*, + (pa_context *c, const char *name, pa_sink_info_cb_t cb, void *userdata), + (c, name, cb, userdata)) +PROXY_STUB (pa_context_get_source_info_by_name, pa_operation*, + (pa_context *c, const char *name, pa_source_info_cb_t cb, void *userdata), + (c, name, cb, userdata)) +PROXY_STUB (pa_context_get_state, pa_context_state_t, + (pa_context *c), + (c)) +PROXY_STUB_VOID(pa_context_unref, + (pa_context *c), + (c)) +PROXY_STUB (pa_context_errno, int, + (pa_context *c), + (c)) +PROXY_STUB (pa_context_new, pa_context*, + (pa_mainloop_api *mainloop, const char *name), + (mainloop, name)) +PROXY_STUB_VOID(pa_context_set_state_callback, + (pa_context *c, pa_context_notify_cb_t cb, void *userdata), + (c, cb, userdata)) + +PROXY_STUB (pa_frame_size, size_t, + (const pa_sample_spec *spec), + (spec)) +PROXY_STUB (pa_get_library_version, const char *, (void), ()) +PROXY_STUB_VOID(pa_operation_unref, + (pa_operation *o), + (o)) +PROXY_STUB (pa_operation_get_state, pa_operation_state_t, + (pa_operation *o), + (o)) +PROXY_STUB_VOID(pa_operation_cancel, + (pa_operation *o), + (o)) + +PROXY_STUB (pa_rtclock_now, pa_usec_t, + (void), + ()) +PROXY_STUB (pa_sample_format_to_string, const char*, + (pa_sample_format_t f), + (f)) +PROXY_STUB (pa_sample_spec_valid, int, + (const pa_sample_spec *spec), + (spec)) +PROXY_STUB (pa_strerror, const char*, + (int error), + (error)) + +#if PA_PROTOCOL_VERSION >= 16 +PROXY_STUB (pa_stream_connect_playback, int, + (pa_stream *s, const char *dev, const pa_buffer_attr *attr, + pa_stream_flags_t flags, const pa_cvolume *volume, pa_stream *sync_stream), + (s, dev, attr, flags, volume, sync_stream)) +#else +PROXY_STUB (pa_stream_connect_playback, int, + (pa_stream *s, const char *dev, const pa_buffer_attr *attr, + pa_stream_flags_t flags, pa_cvolume *volume, pa_stream *sync_stream), + (s, dev, attr, flags, volume, sync_stream)) +#endif +PROXY_STUB (pa_stream_connect_record, int, + (pa_stream *s, const char *dev, const pa_buffer_attr *attr, + pa_stream_flags_t flags), + (s, dev, attr, flags)) +PROXY_STUB (pa_stream_disconnect, int, + (pa_stream *s), + (s)) +PROXY_STUB (pa_stream_get_sample_spec, const pa_sample_spec*, + (pa_stream *s), + (s)) +PROXY_STUB_VOID(pa_stream_set_latency_update_callback, + (pa_stream *p, pa_stream_notify_cb_t cb, void *userdata), + (p, cb, userdata)) +PROXY_STUB (pa_stream_write, int, + (pa_stream *p, const void *data, size_t bytes, pa_free_cb_t free_cb, + int64_t offset, pa_seek_mode_t seek), + (p, data, bytes, free_cb, offset, seek)) +PROXY_STUB_VOID(pa_stream_unref, + (pa_stream *s), + (s)) +PROXY_STUB (pa_stream_get_state, pa_stream_state_t, + (pa_stream *p), + (p)) +PROXY_STUB (pa_stream_get_latency, int, + (pa_stream *s, pa_usec_t *r_usec, int *negative), + (s, r_usec, negative)) +PROXY_STUB (pa_stream_get_timing_info, pa_timing_info*, + (pa_stream *s), + (s)) +PROXY_STUB (pa_stream_readable_size, size_t, + (pa_stream *p), + (p)) +PROXY_STUB (pa_stream_set_buffer_attr, pa_operation *, + (pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata), + (s, attr, cb, userdata)) +PROXY_STUB_VOID(pa_stream_set_state_callback, + (pa_stream *s, pa_stream_notify_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB_VOID(pa_stream_set_underflow_callback, + (pa_stream *s, pa_stream_notify_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB_VOID(pa_stream_set_overflow_callback, + (pa_stream *s, pa_stream_notify_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB_VOID(pa_stream_set_write_callback, + (pa_stream *s, pa_stream_request_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB (pa_stream_flush, pa_operation*, + (pa_stream *s, pa_stream_success_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB (pa_stream_drain, pa_operation*, + (pa_stream *s, pa_stream_success_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB (pa_stream_trigger, pa_operation*, + (pa_stream *s, pa_stream_success_cb_t cb, void *userdata), + (s, cb, userdata)) +PROXY_STUB (pa_stream_new, pa_stream*, + (pa_context *c, const char *name, const pa_sample_spec *ss, + const pa_channel_map *map), + (c, name, ss, map)) +PROXY_STUB (pa_stream_get_buffer_attr, const pa_buffer_attr*, + (pa_stream *s), + (s)) +PROXY_STUB (pa_stream_peek, int, + (pa_stream *p, const void **data, size_t *bytes), + (p, data, bytes)) +PROXY_STUB (pa_stream_cork, pa_operation*, + (pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata), + (s, b, cb, userdata)) +PROXY_STUB (pa_stream_drop, int, + (pa_stream *p), + (p)) +PROXY_STUB (pa_stream_writable_size, size_t, + (pa_stream *p), + (p)) + +PROXY_STUB_VOID(pa_threaded_mainloop_stop, + (pa_threaded_mainloop *m), + (m)) +PROXY_STUB (pa_threaded_mainloop_get_api, pa_mainloop_api*, + (pa_threaded_mainloop *m), + (m)) +PROXY_STUB_VOID(pa_threaded_mainloop_free, + (pa_threaded_mainloop* m), + (m)) +PROXY_STUB_VOID(pa_threaded_mainloop_signal, + (pa_threaded_mainloop *m, int wait_for_accept), + (m, wait_for_accept)) +PROXY_STUB_VOID(pa_threaded_mainloop_unlock, + (pa_threaded_mainloop *m), + (m)) +PROXY_STUB (pa_threaded_mainloop_new, pa_threaded_mainloop *, + (void), + ()) +PROXY_STUB_VOID(pa_threaded_mainloop_wait, + (pa_threaded_mainloop *m), + (m)) +PROXY_STUB (pa_threaded_mainloop_start, int, + (pa_threaded_mainloop *m), + (m)) +PROXY_STUB_VOID(pa_threaded_mainloop_lock, + (pa_threaded_mainloop *m), + (m)) + +PROXY_STUB (pa_usec_to_bytes, size_t, + (pa_usec_t t, const pa_sample_spec *spec), + (t, spec)) + +typedef struct +{ + const char *name; + void (**fn)(void); +} SHARED_FUNC; + +#define ELEMENT(function) { #function , (void (**)(void)) & g_pfn_ ## function } +static SHARED_FUNC SharedFuncs[] = +{ + ELEMENT(pa_bytes_per_second), + ELEMENT(pa_bytes_to_usec), + ELEMENT(pa_channel_map_init_auto), + + ELEMENT(pa_context_connect), + ELEMENT(pa_context_disconnect), + ELEMENT(pa_context_get_server_info), + ELEMENT(pa_context_get_sink_info_by_name), + ELEMENT(pa_context_get_source_info_by_name), + ELEMENT(pa_context_get_state), + ELEMENT(pa_context_unref), + ELEMENT(pa_context_errno), + ELEMENT(pa_context_new), + ELEMENT(pa_context_set_state_callback), + + ELEMENT(pa_frame_size), + ELEMENT(pa_get_library_version), + ELEMENT(pa_operation_unref), + ELEMENT(pa_operation_get_state), + ELEMENT(pa_operation_cancel), + ELEMENT(pa_rtclock_now), + ELEMENT(pa_sample_format_to_string), + ELEMENT(pa_sample_spec_valid), + ELEMENT(pa_strerror), + + ELEMENT(pa_stream_connect_playback), + ELEMENT(pa_stream_connect_record), + ELEMENT(pa_stream_disconnect), + ELEMENT(pa_stream_get_sample_spec), + ELEMENT(pa_stream_set_latency_update_callback), + ELEMENT(pa_stream_write), + ELEMENT(pa_stream_unref), + ELEMENT(pa_stream_get_state), + ELEMENT(pa_stream_get_latency), + ELEMENT(pa_stream_get_timing_info), + ELEMENT(pa_stream_readable_size), + ELEMENT(pa_stream_set_buffer_attr), + ELEMENT(pa_stream_set_state_callback), + ELEMENT(pa_stream_set_underflow_callback), + ELEMENT(pa_stream_set_overflow_callback), + ELEMENT(pa_stream_set_write_callback), + ELEMENT(pa_stream_flush), + ELEMENT(pa_stream_drain), + ELEMENT(pa_stream_trigger), + ELEMENT(pa_stream_new), + ELEMENT(pa_stream_get_buffer_attr), + ELEMENT(pa_stream_peek), + ELEMENT(pa_stream_cork), + ELEMENT(pa_stream_drop), + ELEMENT(pa_stream_writable_size), + + ELEMENT(pa_threaded_mainloop_stop), + ELEMENT(pa_threaded_mainloop_get_api), + ELEMENT(pa_threaded_mainloop_free), + ELEMENT(pa_threaded_mainloop_signal), + ELEMENT(pa_threaded_mainloop_unlock), + ELEMENT(pa_threaded_mainloop_new), + ELEMENT(pa_threaded_mainloop_wait), + ELEMENT(pa_threaded_mainloop_start), + ELEMENT(pa_threaded_mainloop_lock), + + ELEMENT(pa_usec_to_bytes) +}; +#undef ELEMENT + +/** + * Try to dynamically load the PulseAudio libraries. This function is not + * thread-safe, and should be called before attempting to use any of the + * PulseAudio functions. + * + * @returns iprt status code + */ +int audioLoadPulseLib(void) +{ + int rc = VINF_SUCCESS; + unsigned i; + static enum { NO = 0, YES, FAIL } isLibLoaded = NO; + RTLDRMOD hLib; + + LogFlowFunc(("\n")); + /* If this is not NO then the function has obviously been called twice, + which is likely to be a bug. */ + if (NO != isLibLoaded) + { + AssertMsgFailed(("isLibLoaded == %s\n", YES == isLibLoaded ? "YES" : "NO")); + return YES == isLibLoaded ? VINF_SUCCESS : VERR_NOT_SUPPORTED; + } + isLibLoaded = FAIL; + rc = RTLdrLoad(VBOX_PULSE_LIB, &hLib); + if (RT_FAILURE(rc)) + { + LogRelFunc(("Failed to load library %s\n", VBOX_PULSE_LIB)); + return rc; + } + for (i=0; i<RT_ELEMENTS(SharedFuncs); i++) + { + rc = RTLdrGetSymbol(hLib, SharedFuncs[i].name, (void**)SharedFuncs[i].fn); + if (RT_FAILURE(rc)) + return rc; + } + isLibLoaded = YES; + return rc; +} + diff --git a/src/VBox/Devices/Audio/pulse_stubs.h b/src/VBox/Devices/Audio/pulse_stubs.h new file mode 100644 index 00000000..abc59bdc --- /dev/null +++ b/src/VBox/Devices/Audio/pulse_stubs.h @@ -0,0 +1,25 @@ +/* $Id: pulse_stubs.h $ */ +/** @file + * Stubs for libpulse. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_Audio_pulse_stubs_h +#define VBOX_INCLUDED_SRC_Audio_pulse_stubs_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif +extern int audioLoadPulseLib(void); +#endif /* !VBOX_INCLUDED_SRC_Audio_pulse_stubs_h */ + diff --git a/src/VBox/Devices/Audio/testcase/Makefile.kmk b/src/VBox/Devices/Audio/testcase/Makefile.kmk new file mode 100644 index 00000000..1cdbcbea --- /dev/null +++ b/src/VBox/Devices/Audio/testcase/Makefile.kmk @@ -0,0 +1,42 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the audio testcases. +# + +# +# Copyright (C) 2014-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) + + PROGRAMS += tstAudioMixBuffer + TESTING += $(tstAudioMixBuffer_0_OUTDIR)/tstAudioMixBuffer.run + + tstAudioMixBuffer_TEMPLATE = VBOXR3TSTEXE + tstAudioMixBuffer_DEFS = TESTCASE + tstAudioMixBuffer_DEFS.debug = VBOX_WITH_EF_WRAPS + tstAudioMixBuffer_SOURCES = \ + tstAudioMixBuffer.cpp \ + ../AudioMixBuffer.cpp \ + ../DrvAudioCommon.cpp + tstAudioMixBuffer_LIBS = $(LIB_RUNTIME) + + $$(tstAudioMixBuffer_0_OUTDIR)/tstAudioMixBuffer.run: $$(tstAudioMixBuffer_1_STAGE_TARGET) + export VBOX_LOG_DEST=nofile; $(tstAudioMixBuffer_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + +endif + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Devices/Audio/testcase/tstAudioMixBuffer.cpp b/src/VBox/Devices/Audio/testcase/tstAudioMixBuffer.cpp new file mode 100644 index 00000000..a8566678 --- /dev/null +++ b/src/VBox/Devices/Audio/testcase/tstAudioMixBuffer.cpp @@ -0,0 +1,651 @@ +/* $Id: tstAudioMixBuffer.cpp $ */ +/** @file + * Audio testcase - Mixing buffer. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/rand.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> + + +#include "../AudioMixBuffer.h" +#include "../DrvAudio.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +static int tstSingle(RTTEST hTest) +{ + RTTestSubF(hTest, "Single buffer"); + + /* 44100Hz, 2 Channels, S16 */ + PDMAUDIOPCMPROPS config = PDMAUDIOPCMPROPS_INITIALIZOR( + 2, /* Bytes */ + true, /* Signed */ + 2, /* Channels */ + 44100, /* Hz */ + PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */ + false /* Swap Endian */ + ); + + RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&config)); + + uint32_t cBufSize = _1K; + + /* + * General stuff. + */ + PDMAUDIOMIXBUF mb; + RTTESTI_CHECK_RC_OK(AudioMixBufInit(&mb, "Single", &config, cBufSize)); + RTTESTI_CHECK(AudioMixBufSize(&mb) == cBufSize); + RTTESTI_CHECK(AUDIOMIXBUF_B2F(&mb, AudioMixBufSizeBytes(&mb)) == cBufSize); + RTTESTI_CHECK(AUDIOMIXBUF_F2B(&mb, AudioMixBufSize(&mb)) == AudioMixBufSizeBytes(&mb)); + RTTESTI_CHECK(AudioMixBufFree(&mb) == cBufSize); + RTTESTI_CHECK(AUDIOMIXBUF_F2B(&mb, AudioMixBufFree(&mb)) == AudioMixBufFreeBytes(&mb)); + + /* + * Absolute writes. + */ + uint32_t cFramesRead = 0, cFramesWritten = 0, cFramesWrittenAbs = 0; + int8_t aFrames8 [2] = { 0x12, 0x34 }; + int16_t aFrames16[2] = { 0xAA, 0xBB }; + int32_t aFrames32[2] = { 0xCC, 0xDD }; + + RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 0 /* Offset */, &aFrames8, sizeof(aFrames8), &cFramesWritten)); + RTTESTI_CHECK(cFramesWritten == 0 /* Frames */); + RTTESTI_CHECK(AudioMixBufUsed(&mb) == 0); + + RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 0 /* Offset */, &aFrames16, sizeof(aFrames16), &cFramesWritten)); + RTTESTI_CHECK(cFramesWritten == 1 /* Frames */); + RTTESTI_CHECK(AudioMixBufUsed(&mb) == 1); + + RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 2 /* Offset */, &aFrames32, sizeof(aFrames32), &cFramesWritten)); + RTTESTI_CHECK(cFramesWritten == 2 /* Frames */); + RTTESTI_CHECK(AudioMixBufUsed(&mb) == 2); + + /* Beyond buffer. */ + RTTESTI_CHECK_RC(AudioMixBufWriteAt(&mb, AudioMixBufSize(&mb) + 1, &aFrames16, sizeof(aFrames16), + &cFramesWritten), VERR_BUFFER_OVERFLOW); + + /* Offset wrap-around: When writing as much (or more) frames the mixing buffer can hold. */ + uint32_t cbSamples = cBufSize * sizeof(int16_t) * 2 /* Channels */; + RTTESTI_CHECK(cbSamples); + uint16_t *paSamples = (uint16_t *)RTMemAlloc(cbSamples); + RTTESTI_CHECK(paSamples); + RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 0 /* Offset */, paSamples, cbSamples, &cFramesWritten)); + RTTESTI_CHECK(cFramesWritten == cBufSize /* Frames */); + RTTESTI_CHECK(AudioMixBufUsed(&mb) == cBufSize); + RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 0); + RTTESTI_CHECK(AudioMixBufWritePos(&mb) == 0); + RTMemFree(paSamples); + cbSamples = 0; + + /* + * Circular writes. + */ + AudioMixBufReset(&mb); + + RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 2 /* Offset */, &aFrames32, sizeof(aFrames32), &cFramesWritten)); + RTTESTI_CHECK(cFramesWritten == 2 /* Frames */); + RTTESTI_CHECK(AudioMixBufUsed(&mb) == 2); + + cFramesWrittenAbs = AudioMixBufUsed(&mb); + + uint32_t cToWrite = AudioMixBufSize(&mb) - cFramesWrittenAbs - 1; /* -1 as padding plus -2 frames for above. */ + for (uint32_t i = 0; i < cToWrite; i++) + { + RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&mb, &aFrames16, sizeof(aFrames16), &cFramesWritten)); + RTTESTI_CHECK(cFramesWritten == 1); + } + RTTESTI_CHECK(!AudioMixBufIsEmpty(&mb)); + RTTESTI_CHECK(AudioMixBufFree(&mb) == 1); + RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, 1U)); + RTTESTI_CHECK(AudioMixBufUsed(&mb) == cToWrite + cFramesWrittenAbs /* + last absolute write */); + + RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&mb, &aFrames16, sizeof(aFrames16), &cFramesWritten)); + RTTESTI_CHECK(cFramesWritten == 1); + RTTESTI_CHECK(AudioMixBufFree(&mb) == 0); + RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, 0U)); + RTTESTI_CHECK(AudioMixBufUsed(&mb) == cBufSize); + + /* Circular reads. */ + uint32_t cToRead = AudioMixBufSize(&mb) - cFramesWrittenAbs - 1; + for (uint32_t i = 0; i < cToRead; i++) + { + RTTESTI_CHECK_RC_OK(AudioMixBufAcquireReadBlock(&mb, &aFrames16, sizeof(aFrames16), &cFramesRead)); + RTTESTI_CHECK(cFramesRead == 1); + AudioMixBufReleaseReadBlock(&mb, cFramesRead); + AudioMixBufFinish(&mb, cFramesRead); + } + RTTESTI_CHECK(!AudioMixBufIsEmpty(&mb)); + RTTESTI_CHECK(AudioMixBufFree(&mb) == AudioMixBufSize(&mb) - cFramesWrittenAbs - 1); + RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, cBufSize - cFramesWrittenAbs - 1)); + RTTESTI_CHECK(AudioMixBufUsed(&mb) == cBufSize - cToRead); + + RTTESTI_CHECK_RC_OK(AudioMixBufAcquireReadBlock(&mb, &aFrames16, sizeof(aFrames16), &cFramesRead)); + RTTESTI_CHECK(cFramesRead == 1); + AudioMixBufReleaseReadBlock(&mb, cFramesRead); + AudioMixBufFinish(&mb, cFramesRead); + RTTESTI_CHECK(AudioMixBufFree(&mb) == cBufSize - cFramesWrittenAbs); + RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, cBufSize - cFramesWrittenAbs)); + RTTESTI_CHECK(AudioMixBufUsed(&mb) == cFramesWrittenAbs); + + AudioMixBufDestroy(&mb); + + return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS; +} + +static int tstParentChild(RTTEST hTest) +{ + uint32_t cParentBufSize = RTRandU32Ex(_1K /* Min */, _16K /* Max */); /* Enough room for random sizes */ + + /* 44100Hz, 2 Channels, S16 */ + PDMAUDIOPCMPROPS cfg_p = PDMAUDIOPCMPROPS_INITIALIZOR( + 2, /* Bytes */ + true, /* Signed */ + 2, /* Channels */ + 44100, /* Hz */ + PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */ + false /* Swap Endian */ + ); + + RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_p)); + + PDMAUDIOMIXBUF parent; + RTTESTI_CHECK_RC_OK(AudioMixBufInit(&parent, "Parent", &cfg_p, cParentBufSize)); + + /* 22050Hz, 2 Channels, S16 */ + PDMAUDIOPCMPROPS cfg_c1 = PDMAUDIOPCMPROPS_INITIALIZOR(/* Upmixing to parent */ + 2, /* Bytes */ + true, /* Signed */ + 2, /* Channels */ + 22050, /* Hz */ + PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */ + false /* Swap Endian */ + ); + + RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_c1)); + + uint32_t cFrames = 16; + uint32_t cChildBufSize = RTRandU32Ex(cFrames /* Min */, 64 /* Max */); + + PDMAUDIOMIXBUF child1; + RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child1, "Child1", &cfg_c1, cChildBufSize)); + RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child1, &parent)); + + /* 48000Hz, 2 Channels, S16 */ + PDMAUDIOPCMPROPS cfg_c2 = PDMAUDIOPCMPROPS_INITIALIZOR(/* Downmixing to parent */ + 2, /* Bytes */ + true, /* Signed */ + 2, /* Channels */ + 48000, /* Hz */ + PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */ + false /* Swap Endian */ + ); + + RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_c2)); + + PDMAUDIOMIXBUF child2; + RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child2, "Child2", &cfg_c2, cChildBufSize)); + RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child2, &parent)); + + /* + * Writing + mixing from child/children -> parent, sequential. + */ + uint32_t cbBuf = _1K; + char pvBuf[_1K]; + int16_t aFrames16[32] = { 0xAA, 0xBB }; + uint32_t cFramesRead, cFramesWritten, cFramesMixed; + + uint32_t cFramesChild1 = cFrames; + uint32_t cFramesChild2 = cFrames; + + uint32_t t = RTRandU32() % 32; + + RTTestPrintf(hTest, RTTESTLVL_DEBUG, + "cParentBufSize=%RU32, cChildBufSize=%RU32, %RU32 frames -> %RU32 iterations total\n", + cParentBufSize, cChildBufSize, cFrames, t); + + /* + * Using AudioMixBufWriteAt for writing to children. + */ + RTTestSubF(hTest, "2 Children -> Parent (AudioMixBufWriteAt)"); + + uint32_t cChildrenSamplesMixedTotal = 0; + + for (uint32_t i = 0; i < t; i++) + { + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "i=%RU32\n", i); + + uint32_t cChild1Writes = RTRandU32() % 8; + + for (uint32_t c1 = 0; c1 < cChild1Writes; c1++) + { + /* Child 1. */ + RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufWriteAt(&child1, 0, &aFrames16, sizeof(aFrames16), &cFramesWritten)); + RTTESTI_CHECK_MSG_BREAK(cFramesWritten == cFramesChild1, ("Child1: Expected %RU32 written frames, got %RU32\n", cFramesChild1, cFramesWritten)); + RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufMixToParent(&child1, cFramesWritten, &cFramesMixed)); + + cChildrenSamplesMixedTotal += cFramesMixed; + + RTTESTI_CHECK_MSG_BREAK(cFramesWritten == cFramesMixed, ("Child1: Expected %RU32 mixed frames, got %RU32\n", cFramesWritten, cFramesMixed)); + RTTESTI_CHECK_MSG_BREAK(AudioMixBufUsed(&child1) == 0, ("Child1: Expected %RU32 used frames, got %RU32\n", 0, AudioMixBufUsed(&child1))); + } + + uint32_t cChild2Writes = RTRandU32() % 8; + + for (uint32_t c2 = 0; c2 < cChild2Writes; c2++) + { + /* Child 2. */ + RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufWriteAt(&child2, 0, &aFrames16, sizeof(aFrames16), &cFramesWritten)); + RTTESTI_CHECK_MSG_BREAK(cFramesWritten == cFramesChild2, ("Child2: Expected %RU32 written frames, got %RU32\n", cFramesChild2, cFramesWritten)); + RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufMixToParent(&child2, cFramesWritten, &cFramesMixed)); + + cChildrenSamplesMixedTotal += cFramesMixed; + + RTTESTI_CHECK_MSG_BREAK(cFramesWritten == cFramesMixed, ("Child2: Expected %RU32 mixed frames, got %RU32\n", cFramesWritten, cFramesMixed)); + RTTESTI_CHECK_MSG_BREAK(AudioMixBufUsed(&child2) == 0, ("Child2: Expected %RU32 used frames, got %RU32\n", 0, AudioMixBufUsed(&child2))); + } + + /* + * Read out all frames from the parent buffer and also mark the just-read frames as finished + * so that both connected children buffers can keep track of their stuff. + */ + uint32_t cParentSamples = AudioMixBufUsed(&parent); + while (cParentSamples) + { + RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, pvBuf, cbBuf, &cFramesRead)); + if (!cFramesRead) + break; + + AudioMixBufReleaseReadBlock(&parent, cFramesRead); + AudioMixBufFinish(&parent, cFramesRead); + + RTTESTI_CHECK(cParentSamples >= cFramesRead); + cParentSamples -= cFramesRead; + } + + RTTESTI_CHECK(cParentSamples == 0); + } + + RTTESTI_CHECK(AudioMixBufUsed(&parent) == 0); + RTTESTI_CHECK(AudioMixBufLive(&child1) == 0); + RTTESTI_CHECK(AudioMixBufLive(&child2) == 0); + + AudioMixBufDestroy(&parent); + AudioMixBufDestroy(&child1); + AudioMixBufDestroy(&child2); + + return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS; +} + +/* Test 8-bit sample conversion (8-bit -> internal -> 8-bit). */ +static int tstConversion8(RTTEST hTest) +{ + unsigned i; + uint32_t cBufSize = 256; + + RTTestSubF(hTest, "Sample conversion (U8)"); + + /* 44100Hz, 1 Channel, U8 */ + PDMAUDIOPCMPROPS cfg_p = PDMAUDIOPCMPROPS_INITIALIZOR( + 1, /* Bytes */ + false, /* Signed */ + 1, /* Channels */ + 44100, /* Hz */ + PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(1 /* Bytes */, 1 /* Channels */), /* Shift */ + false /* Swap Endian */ + ); + + RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_p)); + + PDMAUDIOMIXBUF parent; + RTTESTI_CHECK_RC_OK(AudioMixBufInit(&parent, "Parent", &cfg_p, cBufSize)); + + /* Child uses half the sample rate; that ensures the mixing engine can't + * take shortcuts and performs conversion. Because conversion to double + * the sample rate effectively inserts one additional sample between every + * two source frames, N source frames will be converted to N * 2 - 1 + * frames. However, the last source sample will be saved for later + * interpolation and not immediately output. + */ + + /* 22050Hz, 1 Channel, U8 */ + PDMAUDIOPCMPROPS cfg_c = PDMAUDIOPCMPROPS_INITIALIZOR( /* Upmixing to parent */ + 1, /* Bytes */ + false, /* Signed */ + 1, /* Channels */ + 22050, /* Hz */ + PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(1 /* Bytes */, 1 /* Channels */), /* Shift */ + false /* Swap Endian */ + ); + + RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_c)); + + PDMAUDIOMIXBUF child; + RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child, "Child", &cfg_c, cBufSize)); + RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child, &parent)); + + /* 8-bit unsigned frames. Often used with SB16 device. */ + uint8_t aFrames8U[16] = { 0xAA, 0xBB, 0, 1, 43, 125, 126, 127, + 128, 129, 130, 131, 132, UINT8_MAX - 1, UINT8_MAX, 0 }; + + /* + * Writing + mixing from child -> parent, sequential. + */ + uint32_t cbBuf = 256; + char achBuf[256]; + uint32_t cFramesRead, cFramesWritten, cFramesMixed; + + uint32_t cFramesChild = 16; + uint32_t cFramesParent = cFramesChild * 2 - 2; + uint32_t cFramesTotalRead = 0; + + /**** 8-bit unsigned samples ****/ + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Conversion test %uHz %uch 8-bit\n", cfg_c.uHz, cfg_c.cChannels); + RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&child, &aFrames8U, sizeof(aFrames8U), &cFramesWritten)); + RTTESTI_CHECK_MSG(cFramesWritten == cFramesChild, ("Child: Expected %RU32 written frames, got %RU32\n", cFramesChild, cFramesWritten)); + RTTESTI_CHECK_RC_OK(AudioMixBufMixToParent(&child, cFramesWritten, &cFramesMixed)); + uint32_t cFrames = AudioMixBufUsed(&parent); + RTTESTI_CHECK_MSG(AudioMixBufLive(&child) == cFrames, ("Child: Expected %RU32 mixed frames, got %RU32\n", AudioMixBufLive(&child), cFrames)); + + RTTESTI_CHECK(AudioMixBufUsed(&parent) == AudioMixBufLive(&child)); + + for (;;) + { + RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, achBuf, cbBuf, &cFramesRead)); + if (!cFramesRead) + break; + cFramesTotalRead += cFramesRead; + AudioMixBufReleaseReadBlock(&parent, cFramesRead); + AudioMixBufFinish(&parent, cFramesRead); + } + + RTTESTI_CHECK_MSG(cFramesTotalRead == cFramesParent, ("Parent: Expected %RU32 mixed frames, got %RU32\n", cFramesParent, cFramesTotalRead)); + + /* Check that the frames came out unharmed. Every other sample is interpolated and we ignore it. */ + /* NB: This also checks that the default volume setting is 0dB attenuation. */ + uint8_t *pSrc8 = &aFrames8U[0]; + uint8_t *pDst8 = (uint8_t *)achBuf; + + for (i = 0; i < cFramesChild - 1; ++i) + { + RTTESTI_CHECK_MSG(*pSrc8 == *pDst8, ("index %u: Dst=%d, Src=%d\n", i, *pDst8, *pSrc8)); + pSrc8 += 1; + pDst8 += 2; + } + + RTTESTI_CHECK(AudioMixBufUsed(&parent) == 0); + RTTESTI_CHECK(AudioMixBufLive(&child) == 0); + + AudioMixBufDestroy(&parent); + AudioMixBufDestroy(&child); + + return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS; +} + +/* Test 16-bit sample conversion (16-bit -> internal -> 16-bit). */ +static int tstConversion16(RTTEST hTest) +{ + unsigned i; + uint32_t cBufSize = 256; + + RTTestSubF(hTest, "Sample conversion (S16)"); + + /* 44100Hz, 1 Channel, S16 */ + PDMAUDIOPCMPROPS cfg_p = PDMAUDIOPCMPROPS_INITIALIZOR( + 2, /* Bytes */ + true, /* Signed */ + 1, /* Channels */ + 44100, /* Hz */ + PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 1 /* Channels */), /* Shift */ + false /* Swap Endian */ + ); + + RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_p)); + + PDMAUDIOMIXBUF parent; + RTTESTI_CHECK_RC_OK(AudioMixBufInit(&parent, "Parent", &cfg_p, cBufSize)); + + /* 22050Hz, 1 Channel, S16 */ + PDMAUDIOPCMPROPS cfg_c = PDMAUDIOPCMPROPS_INITIALIZOR( /* Upmixing to parent */ + 2, /* Bytes */ + true, /* Signed */ + 1, /* Channels */ + 22050, /* Hz */ + PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 1 /* Channels */), /* Shift */ + false /* Swap Endian */ + ); + + RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_c)); + + PDMAUDIOMIXBUF child; + RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child, "Child", &cfg_c, cBufSize)); + RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child, &parent)); + + /* 16-bit signed. More or less exclusively used as output, and usually as input, too. */ + int16_t aFrames16S[16] = { 0xAA, 0xBB, INT16_MIN, INT16_MIN + 1, INT16_MIN / 2, -3, -2, -1, + 0, 1, 2, 3, INT16_MAX / 2, INT16_MAX - 1, INT16_MAX, 0 }; + + /* + * Writing + mixing from child -> parent, sequential. + */ + uint32_t cbBuf = 256; + char achBuf[256]; + uint32_t cFramesRead, cFramesWritten, cFramesMixed; + + uint32_t cFramesChild = 16; + uint32_t cFramesParent = cFramesChild * 2 - 2; + uint32_t cFramesTotalRead = 0; + + /**** 16-bit signed samples ****/ + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Conversion test %uHz %uch 16-bit\n", cfg_c.uHz, cfg_c.cChannels); + RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&child, &aFrames16S, sizeof(aFrames16S), &cFramesWritten)); + RTTESTI_CHECK_MSG(cFramesWritten == cFramesChild, ("Child: Expected %RU32 written frames, got %RU32\n", cFramesChild, cFramesWritten)); + RTTESTI_CHECK_RC_OK(AudioMixBufMixToParent(&child, cFramesWritten, &cFramesMixed)); + uint32_t cFrames = AudioMixBufUsed(&parent); + RTTESTI_CHECK_MSG(AudioMixBufLive(&child) == cFrames, ("Child: Expected %RU32 mixed frames, got %RU32\n", AudioMixBufLive(&child), cFrames)); + + RTTESTI_CHECK(AudioMixBufUsed(&parent) == AudioMixBufLive(&child)); + + for (;;) + { + RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, achBuf, cbBuf, &cFramesRead)); + if (!cFramesRead) + break; + cFramesTotalRead += cFramesRead; + AudioMixBufReleaseReadBlock(&parent, cFramesRead); + AudioMixBufFinish(&parent, cFramesRead); + } + RTTESTI_CHECK_MSG(cFramesTotalRead == cFramesParent, ("Parent: Expected %RU32 mixed frames, got %RU32\n", cFramesParent, cFramesTotalRead)); + + /* Check that the frames came out unharmed. Every other sample is interpolated and we ignore it. */ + /* NB: This also checks that the default volume setting is 0dB attenuation. */ + int16_t *pSrc16 = &aFrames16S[0]; + int16_t *pDst16 = (int16_t *)achBuf; + + for (i = 0; i < cFramesChild - 1; ++i) + { + RTTESTI_CHECK_MSG(*pSrc16 == *pDst16, ("index %u: Dst=%d, Src=%d\n", i, *pDst16, *pSrc16)); + pSrc16 += 1; + pDst16 += 2; + } + + RTTESTI_CHECK(AudioMixBufUsed(&parent) == 0); + RTTESTI_CHECK(AudioMixBufLive(&child) == 0); + + AudioMixBufDestroy(&parent); + AudioMixBufDestroy(&child); + + return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS; +} + +/* Test volume control. */ +static int tstVolume(RTTEST hTest) +{ + unsigned i; + uint32_t cBufSize = 256; + + RTTestSubF(hTest, "Volume control"); + + /* Same for parent/child. */ + /* 44100Hz, 2 Channels, S16 */ + PDMAUDIOPCMPROPS cfg = PDMAUDIOPCMPROPS_INITIALIZOR( + 2, /* Bytes */ + true, /* Signed */ + 2, /* Channels */ + 44100, /* Hz */ + PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */ + false /* Swap Endian */ + ); + + RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg)); + + PDMAUDIOVOLUME vol = { false, 0, 0 }; /* Not muted. */ + PDMAUDIOMIXBUF parent; + RTTESTI_CHECK_RC_OK(AudioMixBufInit(&parent, "Parent", &cfg, cBufSize)); + + PDMAUDIOMIXBUF child; + RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child, "Child", &cfg, cBufSize)); + RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child, &parent)); + + /* A few 16-bit signed samples. */ + int16_t aFrames16S[16] = { INT16_MIN, INT16_MIN + 1, -128, -64, -4, -1, 0, 1, + 2, 255, 256, INT16_MAX / 2, INT16_MAX - 2, INT16_MAX - 1, INT16_MAX, 0 }; + + /* + * Writing + mixing from child -> parent. + */ + uint32_t cbBuf = 256; + char achBuf[256]; + uint32_t cFramesRead, cFramesWritten, cFramesMixed; + + uint32_t cFramesChild = 8; + uint32_t cFramesParent = cFramesChild; + uint32_t cFramesTotalRead; + int16_t *pSrc16; + int16_t *pDst16; + + /**** Volume control test ****/ + RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Volume control test %uHz %uch \n", cfg.uHz, cfg.cChannels); + + /* 1) Full volume/0dB attenuation (255). */ + vol.uLeft = vol.uRight = 255; + AudioMixBufSetVolume(&child, &vol); + + RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&child, &aFrames16S, sizeof(aFrames16S), &cFramesWritten)); + RTTESTI_CHECK_MSG(cFramesWritten == cFramesChild, ("Child: Expected %RU32 written frames, got %RU32\n", cFramesChild, cFramesWritten)); + RTTESTI_CHECK_RC_OK(AudioMixBufMixToParent(&child, cFramesWritten, &cFramesMixed)); + + cFramesTotalRead = 0; + for (;;) + { + RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, achBuf, cbBuf, &cFramesRead)); + if (!cFramesRead) + break; + cFramesTotalRead += cFramesRead; + AudioMixBufReleaseReadBlock(&parent, cFramesRead); + AudioMixBufFinish(&parent, cFramesRead); + } + RTTESTI_CHECK_MSG(cFramesTotalRead == cFramesParent, ("Parent: Expected %RU32 mixed frames, got %RU32\n", cFramesParent, cFramesTotalRead)); + + /* Check that at 0dB the frames came out unharmed. */ + pSrc16 = &aFrames16S[0]; + pDst16 = (int16_t *)achBuf; + + for (i = 0; i < cFramesParent * 2 /* stereo */; ++i) + { + RTTESTI_CHECK_MSG(*pSrc16 == *pDst16, ("index %u: Dst=%d, Src=%d\n", i, *pDst16, *pSrc16)); + ++pSrc16; + ++pDst16; + } + AudioMixBufReset(&child); + + /* 2) Half volume/-6dB attenuation (16 steps down). */ + vol.uLeft = vol.uRight = 255 - 16; + AudioMixBufSetVolume(&child, &vol); + + RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&child, &aFrames16S, sizeof(aFrames16S), &cFramesWritten)); + RTTESTI_CHECK_MSG(cFramesWritten == cFramesChild, ("Child: Expected %RU32 written frames, got %RU32\n", cFramesChild, cFramesWritten)); + RTTESTI_CHECK_RC_OK(AudioMixBufMixToParent(&child, cFramesWritten, &cFramesMixed)); + + cFramesTotalRead = 0; + for (;;) + { + RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, achBuf, cbBuf, &cFramesRead)); + if (!cFramesRead) + break; + cFramesTotalRead += cFramesRead; + AudioMixBufReleaseReadBlock(&parent, cFramesRead); + AudioMixBufFinish(&parent, cFramesRead); + } + RTTESTI_CHECK_MSG(cFramesTotalRead == cFramesParent, ("Parent: Expected %RU32 mixed frames, got %RU32\n", cFramesParent, cFramesTotalRead)); + + /* Check that at -6dB the sample values are halved. */ + pSrc16 = &aFrames16S[0]; + pDst16 = (int16_t *)achBuf; + + for (i = 0; i < cFramesParent * 2 /* stereo */; ++i) + { + /* Watch out! For negative values, x >> 1 is not the same as x / 2. */ + RTTESTI_CHECK_MSG(*pSrc16 >> 1 == *pDst16, ("index %u: Dst=%d, Src=%d\n", i, *pDst16, *pSrc16)); + ++pSrc16; + ++pDst16; + } + + AudioMixBufDestroy(&parent); + AudioMixBufDestroy(&child); + + return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS; +} + +int main(int argc, char **argv) +{ + RTR3InitExe(argc, &argv, 0); + + /* + * Initialize IPRT and create the test. + */ + RTTEST hTest; + int rc = RTTestInitAndCreate("tstAudioMixBuffer", &hTest); + if (rc) + return rc; + RTTestBanner(hTest); + + rc = tstSingle(hTest); + if (RT_SUCCESS(rc)) + rc = tstParentChild(hTest); + if (RT_SUCCESS(rc)) + rc = tstConversion8(hTest); + if (RT_SUCCESS(rc)) + rc = tstConversion16(hTest); + if (RT_SUCCESS(rc)) + rc = tstVolume(hTest); + + /* + * Summary + */ + return RTTestSummaryAndDestroy(hTest); +} |