/* $Id: DevHDA.cpp 88228 2021-03-22 09:28:22Z vboxsync $ */ /** @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-2020 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 #include #include #include #ifdef HDA_DEBUG_GUEST_RIP # include #endif #include #include #include #include #include #include #include # include #ifdef IN_RING3 # include # include # include #endif #include "VBoxDD.h" #include "AudioMixBuffer.h" #include "AudioMixer.h" #include "DevHda.h" #include "DevHdaCommon.h" #include "DevHdaCodec.h" #include "DevHdaStream.h" #include "DevHdaStreamMap.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 #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_pDevIns, a_pThis) \ do { \ int rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, VERR_IGNORED); \ AssertRC(rcLock); \ } while (0) /** * Acquires the HDA lock or returns. */ #define DEVHDA_LOCK_RETURN(a_pDevIns, a_pThis, a_rcBusy) \ do { \ int rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, a_rcBusy); \ if (rcLock == VINF_SUCCESS) \ { /* likely */ } \ else \ { \ AssertRC(rcLock); \ return rcLock; \ } \ } while (0) /** * Acquires the HDA lock or returns. */ # define DEVHDA_LOCK_RETURN_VOID(a_pDevIns, a_pThis) \ do { \ int rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, VERR_IGNORED); \ if (rcLock == VINF_SUCCESS) \ { /* likely */ } \ else \ { \ AssertRC(rcLock); \ return; \ } \ } while (0) /** * Releases the HDA lock. */ #define DEVHDA_UNLOCK(a_pDevIns, a_pThis) \ do { PDMDevHlpCritSectLeave((a_pDevIns), &(a_pThis)->CritSect); } while (0) /** * Acquires the TM lock and HDA lock, returns on failure. */ #define DEVHDA_LOCK_BOTH_RETURN(a_pDevIns, a_pThis, a_pStream, a_rcBusy) \ do { \ VBOXSTRICTRC rcLock = PDMDevHlpTimerLockClock2(pDevIns, (a_pStream)->hTimer, &(a_pThis)->CritSect, (a_rcBusy)); \ if (RT_LIKELY(rcLock == VINF_SUCCESS)) \ { /* likely */ } \ else \ return VBOXSTRICTRC_TODO(rcLock); \ } 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 shared HDA device state. */ R3PTRTYPE(PHDASTATE) pHDAStateShared; /** Pointer to the ring-3 HDA device state. */ R3PTRTYPE(PHDASTATER3) pHDAStateR3; /** 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 state of this BDLE. * Not part of the actual BDLE registers. * @note Only for saved state. */ typedef struct HDABDLESTATELEGACY { /** 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; } HDABDLESTATELEGACY; /** * BDLE and state. * @note Only for saved state. */ typedef struct HDABDLELEGACY { /** The actual BDL description. */ HDABDLEDESC Desc; HDABDLESTATELEGACY State; } HDABDLELEGACY; AssertCompileSize(HDABDLELEGACY, 32); /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ #ifndef VBOX_DEVICE_STRUCT_TESTCASE #ifdef IN_RING3 static void hdaR3GCTLReset(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC); #endif /** @name Register read/write stubs. * @{ */ static FNHDAREGREAD hdaRegReadUnimpl; static FNHDAREGWRITE hdaRegWriteUnimpl; /** @} */ /** @name Global register set read/write functions. * @{ */ static FNHDAREGWRITE hdaRegWriteGCTL; static FNHDAREGREAD hdaRegReadLPIB; static FNHDAREGREAD hdaRegReadWALCLK; static FNHDAREGWRITE hdaRegWriteCORBWP; static FNHDAREGWRITE hdaRegWriteCORBRP; static FNHDAREGWRITE hdaRegWriteCORBCTL; static FNHDAREGWRITE hdaRegWriteCORBSIZE; static FNHDAREGWRITE hdaRegWriteCORBSTS; static FNHDAREGWRITE hdaRegWriteRINTCNT; static FNHDAREGWRITE hdaRegWriteRIRBWP; static FNHDAREGWRITE hdaRegWriteRIRBSTS; static FNHDAREGWRITE hdaRegWriteSTATESTS; static FNHDAREGWRITE hdaRegWriteIRS; static FNHDAREGREAD hdaRegReadIRS; static FNHDAREGWRITE hdaRegWriteBase; /** @} */ /** @name {IOB}SDn write functions. * @{ */ static FNHDAREGWRITE hdaRegWriteSDCBL; static FNHDAREGWRITE hdaRegWriteSDCTL; static FNHDAREGWRITE hdaRegWriteSDSTS; static FNHDAREGWRITE hdaRegWriteSDLVI; static FNHDAREGWRITE hdaRegWriteSDFIFOW; static FNHDAREGWRITE hdaRegWriteSDFIFOS; static FNHDAREGWRITE hdaRegWriteSDFMT; static FNHDAREGWRITE hdaRegWriteSDBDPL; static FNHDAREGWRITE hdaRegWriteSDBDPU; /** @} */ /** @name Generic register read/write functions. * @{ */ static FNHDAREGREAD hdaRegReadU32; static FNHDAREGWRITE hdaRegWriteU32; static FNHDAREGREAD hdaRegReadU24; #ifdef IN_RING3 static FNHDAREGWRITE hdaRegWriteU24; #endif static FNHDAREGREAD hdaRegReadU16; static FNHDAREGWRITE hdaRegWriteU16; static FNHDAREGREAD hdaRegReadU8; static FNHDAREGWRITE hdaRegWriteU8; /** @} */ /** @name HDA device functions. * @{ */ #ifdef IN_RING3 static int hdaR3AddStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg); static int hdaR3RemoveStream(PHDASTATER3 pThisCC, 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(PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PHDADRIVER pDrv); #endif /** @} */ #ifdef IN_RING3 static FNSSMFIELDGETPUT hdaR3GetPutTrans_HDABDLEDESC_fFlags_6; static FNSSMFIELDGETPUT hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4; #endif /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** No register description (RD) flags defined. */ #define HDA_RD_F_NONE 0 /** Writes to SD are allowed while RUN bit is set. */ #define HDA_RD_F_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_F_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_F_SD_WRITE_RUN, hdaRegReadU8 , hdaRegWriteSDSTS , HDA_REG_IDX_STRM(name, STS) , #name " Status" }, \ /* Offset 0x84 (SD0) */ \ { offset + 0x4, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadLPIB, hdaRegWriteU32 , HDA_REG_IDX_STRM(name, LPIB) , #name " Link Position In Buffer" }, \ /* Offset 0x88 (SD0) */ \ { offset + 0x8, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteSDCBL , HDA_REG_IDX_STRM(name, CBL) , #name " Cyclic Buffer Length" }, \ /* Offset 0x8C (SD0) -- upper 8 bits are reserved */ \ { offset + 0xC, 0x00002, 0x0000FFFF, 0x000000FF, HDA_RD_F_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_F_NONE, hdaRegReadU16 , hdaRegWriteSDFIFOW, HDA_REG_IDX_STRM(name, FIFOW), #name " FIFO Watermark" }, \ /* Offset 0x90 (SD0) */ \ { offset + 0x10, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDFIFOS, HDA_REG_IDX_STRM(name, FIFOS), #name " FIFO Size" }, \ /* Offset 0x92 (SD0) */ \ { offset + 0x12, 0x00002, 0x00007F7F, 0x00007F7F, HDA_RD_F_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_F_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_F_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_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , HDA_REG_IDX(GCAP) }, /* Global Capabilities */ { 0x00002, 0x00001, 0x000000FF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , HDA_REG_IDX(VMIN) }, /* Minor Version */ { 0x00003, 0x00001, 0x000000FF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , HDA_REG_IDX(VMAJ) }, /* Major Version */ { 0x00004, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteU16 , HDA_REG_IDX(OUTPAY) }, /* Output Payload Capabilities */ { 0x00006, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , HDA_REG_IDX(INPAY) }, /* Input Payload Capabilities */ { 0x00008, 0x00004, 0x00000103, 0x00000103, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteGCTL , HDA_REG_IDX(GCTL) }, /* Global Control */ { 0x0000c, 0x00002, 0x00007FFF, 0x00007FFF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteU16 , HDA_REG_IDX(WAKEEN) }, /* Wake Enable */ { 0x0000e, 0x00002, 0x00000007, 0x00000007, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteSTATESTS, HDA_REG_IDX(STATESTS) }, /* State Change Status */ { 0x00010, 0x00002, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadUnimpl, hdaRegWriteUnimpl , HDA_REG_IDX(GSTS) }, /* Global Status */ { 0x00018, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteU16 , HDA_REG_IDX(OUTSTRMPAY) }, /* Output Stream Payload Capability */ { 0x0001A, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , HDA_REG_IDX(INSTRMPAY) }, /* Input Stream Payload Capability */ { 0x00020, 0x00004, 0xC00000FF, 0xC00000FF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteU32 , HDA_REG_IDX(INTCTL) }, /* Interrupt Control */ { 0x00024, 0x00004, 0xC00000FF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , HDA_REG_IDX(INTSTS) }, /* Interrupt Status */ { 0x00030, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadWALCLK, hdaRegWriteUnimpl , HDA_REG_IDX_NOMEM(WALCLK) }, /* Wall Clock Counter */ { 0x00034, 0x00004, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteU32 , HDA_REG_IDX(SSYNC) }, /* Stream Synchronization */ { 0x00040, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(CORBLBASE) }, /* CORB Lower Base Address */ { 0x00044, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(CORBUBASE) }, /* CORB Upper Base Address */ { 0x00048, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteCORBWP , HDA_REG_IDX(CORBWP) }, /* CORB Write Pointer */ { 0x0004A, 0x00002, 0x000080FF, 0x00008000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteCORBRP , HDA_REG_IDX(CORBRP) }, /* CORB Read Pointer */ { 0x0004C, 0x00001, 0x00000003, 0x00000003, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteCORBCTL , HDA_REG_IDX(CORBCTL) }, /* CORB Control */ { 0x0004D, 0x00001, 0x00000001, 0x00000001, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteCORBSTS , HDA_REG_IDX(CORBSTS) }, /* CORB Status */ { 0x0004E, 0x00001, 0x000000F3, 0x00000003, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteCORBSIZE, HDA_REG_IDX(CORBSIZE) }, /* CORB Size */ { 0x00050, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(RIRBLBASE) }, /* RIRB Lower Base Address */ { 0x00054, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(RIRBUBASE) }, /* RIRB Upper Base Address */ { 0x00058, 0x00002, 0x000000FF, 0x00008000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteRIRBWP , HDA_REG_IDX(RIRBWP) }, /* RIRB Write Pointer */ { 0x0005A, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteRINTCNT , HDA_REG_IDX(RINTCNT) }, /* Response Interrupt Count */ { 0x0005C, 0x00001, 0x00000007, 0x00000007, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteU8 , HDA_REG_IDX(RIRBCTL) }, /* RIRB Control */ { 0x0005D, 0x00001, 0x00000005, 0x00000005, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteRIRBSTS , HDA_REG_IDX(RIRBSTS) }, /* RIRB Status */ { 0x0005E, 0x00001, 0x000000F3, 0x00000000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , HDA_REG_IDX(RIRBSIZE) }, /* RIRB Size */ { 0x00060, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteU32 , HDA_REG_IDX(IC) }, /* Immediate Command */ { 0x00064, 0x00004, 0x00000000, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , HDA_REG_IDX(IR) }, /* Immediate Response */ { 0x00068, 0x00002, 0x00000002, 0x00000002, HDA_RD_F_NONE, hdaRegReadIRS , hdaRegWriteIRS , HDA_REG_IDX(IRS) }, /* Immediate Command Status */ { 0x00070, 0x00004, 0xFFFFFFFF, 0xFFFFFF81, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(DPLBASE) }, /* DMA Position Lower Base */ { 0x00074, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_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[] = { { 0x2030, HDA_REG_WALCLK }, { 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() }; /** HDABDLEDESC field descriptors for the v6 saved states. */ static SSMFIELD const g_aSSMBDLEDescFields6[] = { SSMFIELD_ENTRY(HDABDLEDESC, u64BufAddr), SSMFIELD_ENTRY(HDABDLEDESC, u32BufSize), SSMFIELD_ENTRY_CALLBACK(HDABDLEDESC, fFlags, hdaR3GetPutTrans_HDABDLEDESC_fFlags_6), SSMFIELD_ENTRY_TERM() }; /** HDABDLESTATE field descriptors for the v6+ saved state. */ static SSMFIELD const g_aSSMBDLEStateFields6[] = { SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BDLIndex), SSMFIELD_ENTRY(HDABDLESTATELEGACY, cbBelowFIFOW), SSMFIELD_ENTRY_OLD(FIFO, 256), /* Deprecated; now is handled in the stream's circular buffer. */ SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BufOff), SSMFIELD_ENTRY_TERM() }; /** HDABDLESTATE field descriptors for the v7 saved state. */ static SSMFIELD const g_aSSMBDLEStateFields7[] = { SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BDLIndex), SSMFIELD_ENTRY(HDABDLESTATELEGACY, cbBelowFIFOW), SSMFIELD_ENTRY(HDABDLESTATELEGACY, 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_OLD(uCurBDLE, sizeof(uint16_t)), /* We figure it out from LPID */ 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, idxCurBdle), /* For backward compatibility we save this. We use LPIB on restore. */ SSMFIELD_ENTRY_OLD(uCurBDLEHi, sizeof(uint8_t)), /* uCurBDLE was 16-bit for some reason, so store/ignore the zero top byte. */ SSMFIELD_ENTRY(HDASTREAMSTATE, fInReset), SSMFIELD_ENTRY(HDASTREAMSTATE, tsTransferNext), SSMFIELD_ENTRY_TERM() }; /** HDABDLE field descriptors for the v1 thru v4 saved states. */ static SSMFIELD const g_aSSMStreamBdleFields1234[] = { SSMFIELD_ENTRY(HDABDLELEGACY, Desc.u64BufAddr), /* u64BdleCviAddr */ SSMFIELD_ENTRY_OLD(u32BdleMaxCvi, sizeof(uint32_t)), /* u32BdleMaxCvi */ SSMFIELD_ENTRY(HDABDLELEGACY, State.u32BDLIndex), /* u32BdleCvi */ SSMFIELD_ENTRY(HDABDLELEGACY, Desc.u32BufSize), /* u32BdleCviLen */ SSMFIELD_ENTRY(HDABDLELEGACY, State.u32BufOff), /* u32BdleCviPos */ SSMFIELD_ENTRY_CALLBACK(HDABDLELEGACY, Desc.fFlags, hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4), /* fBdleCviIoc */ SSMFIELD_ENTRY(HDABDLELEGACY, State.cbBelowFIFOW), /* cbUnderFifoW */ SSMFIELD_ENTRY_OLD(au8FIFO, 256), /* au8FIFO */ SSMFIELD_ENTRY_TERM() }; #endif /* IN_RING3 */ /** * 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) }; /** * 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; } #endif /* IN_RING3 */ /** * Synchronizes the CORB / RIRB buffers between internal <-> device state. * * @returns IPRT status code. * * @param pDevIns The device instance. * @param pThis The shared HDA device 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 hdaCmdSync(PPDMDEVINS pDevIns, PHDASTATE pThis, bool fLocal) { int rc = VINF_SUCCESS; if (fLocal) { if (pThis->u64CORBBase) { 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(pDevIns, pThis->u64CORBBase, pThis->au32CorbBuf, RT_MIN(pThis->cbCorbBuf, sizeof(pThis->au32CorbBuf))); Log3Func(("CORB: read %RGp LB %#x (%Rrc)\n", pThis->u64CORBBase, pThis->cbCorbBuf, rc)); AssertRCReturn(rc, rc); } } else { if (pThis->u64RIRBBase) { Assert(pThis->cbRirbBuf); rc = PDMDevHlpPCIPhysWrite(pDevIns, pThis->u64RIRBBase, pThis->au64RirbBuf, RT_MIN(pThis->cbRirbBuf, sizeof(pThis->au64RirbBuf))); Log3Func(("RIRB: phys read %RGp LB %#x (%Rrc)\n", pThis->u64RIRBBase, pThis->cbRirbBuf, 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; } #ifdef IN_RING3 /** * Processes the next CORB buffer command in the queue (ring-3). * * Note: This function only will be called when the ring-0 version did not have an appropriate dispatcher. * * This will invoke the HDA codec ring-3 verb dispatcher. * * @returns VBox status code suitable for MMIO write return. * @param pDevIns The device instance. * @param pThis The shared HDA device state. * @param pThisCC The ring-0 HDA device state. */ static int hdaR3CORBCmdProcess(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC) { Log3Func(("ENTER CORB(RP:%x, WP:%x) RIRBWP:%x\n", HDA_REG(pThis, CORBRP), HDA_REG(pThis, CORBWP), HDA_REG(pThis, RIRBWP))); if (!(HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA)) { LogFunc(("CORB DMA not active, skipping\n")); return VINF_SUCCESS; } /* Note: Command buffer syncing was already done in R0. */ Assert(pThis->cbCorbBuf); int rc; /* * Prepare local copies of relevant registers. */ uint16_t cIntCnt = HDA_REG(pThis, RINTCNT) & 0xff; if (!cIntCnt) /* 0 means 256 interrupts. */ cIntCnt = HDA_MAX_RINTCNT; uint32_t const cCorbEntries = RT_MIN(RT_MAX(pThis->cbCorbBuf, 1), sizeof(pThis->au32CorbBuf)) / HDA_CORB_ELEMENT_SIZE; uint8_t const corbWp = HDA_REG(pThis, CORBWP) % cCorbEntries; uint8_t corbRp = HDA_REG(pThis, CORBRP); uint8_t rirbWp = HDA_REG(pThis, RIRBWP); /* * The loop. */ Log3Func(("START CORB(RP:%x, WP:%x) RIRBWP:%x, RINTCNT:%RU8/%RU8\n", corbRp, corbWp, rirbWp, pThis->u16RespIntCnt, cIntCnt)); while (corbRp != corbWp) { /* Fetch the command from the CORB. */ corbRp = (corbRp + 1) /* Advance +1 as the first command(s) are at CORBWP + 1. */ % cCorbEntries; uint32_t const uCmd = pThis->au32CorbBuf[corbRp]; /* * Execute the command. */ uint64_t uResp = 0; rc = pThisCC->pCodec->pfnLookup(&pThis->Codec, pThisCC->pCodec, HDA_CODEC_CMD(uCmd, 0 /* Codec index */), &uResp); if (RT_FAILURE(rc)) /* Can return VERR_NOT_FOUND. */ Log3Func(("Lookup for codec verb %08x failed: %Rrc\n", uCmd, rc)); /* Note: No return here (as we're doing for the ring-0 version); we still need to do the interrupt handling below. */ Log3Func(("Codec verb %08x -> response %016RX64\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 RIRB syncing to guest required in that case? */ /** @todo r=bird: Why isn't RIRBWP updated here. The response might come * after already processing several commands, can't it? (When you think * about it, it is bascially the same question as Andy is asking.) */ return VINF_SUCCESS; } /* * Store the response in the RIRB. */ AssertCompile(HDA_RIRB_SIZE == RT_ELEMENTS(pThis->au64RirbBuf)); rirbWp = (rirbWp + 1) % HDA_RIRB_SIZE; pThis->au64RirbBuf[rirbWp] = uResp; /* * Send interrupt if needed. */ bool fSendInterrupt = false; pThis->u16RespIntCnt++; 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; HDA_PROCESS_INTERRUPT(pDevIns, pThis); } } } /* * Put register locals back. */ 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; /* * Write out the response. */ rc = hdaCmdSync(pDevIns, pThis, false /* Sync to guest */); AssertRC(rc); return rc; } #else /* IN_RING0 */ /** * Processes the next CORB buffer command in the queue (ring-0). * * This will invoke the HDA codec verb dispatcher. * * @returns VBox status code suitable for MMIO write return. * @param pDevIns The device instance. * @param pThis The shared HDA device state. * @param pThisCC The ring-0 HDA device state. */ static int hdaR0CORBCmdProcess(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER0 pThisCC) { Log3Func(("CORB(RP:%x, WP:%x) RIRBWP:%x\n", HDA_REG(pThis, CORBRP), HDA_REG(pThis, CORBWP), HDA_REG(pThis, RIRBWP))); if (!(HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA)) { LogFunc(("CORB DMA not active, skipping\n")); return VINF_SUCCESS; } Assert(pThis->cbCorbBuf); int rc = hdaCmdSync(pDevIns, pThis, true /* Sync from guest */); AssertRCReturn(rc, rc); /* * Prepare local copies of relevant registers. */ uint16_t cIntCnt = HDA_REG(pThis, RINTCNT) & 0xff; if (!cIntCnt) /* 0 means 256 interrupts. */ cIntCnt = HDA_MAX_RINTCNT; uint32_t const cCorbEntries = RT_MIN(RT_MAX(pThis->cbCorbBuf, 1), sizeof(pThis->au32CorbBuf)) / HDA_CORB_ELEMENT_SIZE; uint8_t const corbWp = HDA_REG(pThis, CORBWP) % cCorbEntries; uint8_t corbRp = HDA_REG(pThis, CORBRP); uint8_t rirbWp = HDA_REG(pThis, RIRBWP); /* * The loop. */ Log3Func(("START CORB(RP:%x, WP:%x) RIRBWP:%x, RINTCNT:%RU8/%RU8\n", corbRp, corbWp, rirbWp, pThis->u16RespIntCnt, cIntCnt)); while (corbRp != corbWp) { /* Fetch the command from the CORB. */ corbRp = (corbRp + 1) /* Advance +1 as the first command(s) are at CORBWP + 1. */ % cCorbEntries; uint32_t const uCmd = pThis->au32CorbBuf[corbRp]; /* * Execute the command. */ uint64_t uResp = 0; rc = pThisCC->Codec.pfnLookup(&pThis->Codec, &pThisCC->Codec, HDA_CODEC_CMD(uCmd, 0 /* Codec index */), &uResp); if (RT_FAILURE(rc)) /* Can return VERR_NOT_FOUND. */ { Log3Func(("Lookup for codec verb %08x failed: %Rrc\n", uCmd, rc)); return rc; } Log3Func(("Codec verb %08x -> response %016RX64\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 RIRB syncing to guest required in that case? */ /** @todo r=bird: Why isn't RIRBWP updated here. The response might come * after already processing several commands, can't it? (When you think * about it, it is bascially the same question as Andy is asking.) */ return VINF_SUCCESS; } /* * Store the response in the RIRB. */ AssertCompile(HDA_RIRB_SIZE == RT_ELEMENTS(pThis->au64RirbBuf)); rirbWp = (rirbWp + 1) % HDA_RIRB_SIZE; pThis->au64RirbBuf[rirbWp] = uResp; /* * Send interrupt if needed. */ bool fSendInterrupt = false; pThis->u16RespIntCnt++; 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; HDA_PROCESS_INTERRUPT(pDevIns, pThis); } } } /* * Put register locals back. */ 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; /* * Write out the response. */ rc = hdaCmdSync(pDevIns, pThis, false /* Sync to guest */); AssertRC(rc); return rc; } #endif /* IN_RING3 */ /* Register access handlers. */ static VBOXSTRICTRC hdaRegReadUnimpl(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) { RT_NOREF(pDevIns, pThis, iReg); *pu32Value = 0; return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegWriteUnimpl(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { RT_NOREF(pDevIns, pThis, iReg, u32Value); return VINF_SUCCESS; } /* U8 */ static VBOXSTRICTRC hdaRegReadU8(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) { Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].mem_idx] & g_aHdaRegMap[iReg].readable) & 0xffffff00) == 0); return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value); } static VBOXSTRICTRC hdaRegWriteU8(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { Assert((u32Value & 0xffffff00) == 0); return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); } /* U16 */ static VBOXSTRICTRC hdaRegReadU16(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) { Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].mem_idx] & g_aHdaRegMap[iReg].readable) & 0xffff0000) == 0); return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value); } static VBOXSTRICTRC hdaRegWriteU16(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { Assert((u32Value & 0xffff0000) == 0); return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); } /* U24 */ static VBOXSTRICTRC hdaRegReadU24(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) { Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].mem_idx] & g_aHdaRegMap[iReg].readable) & 0xff000000) == 0); return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value); } #ifdef IN_RING3 static VBOXSTRICTRC hdaRegWriteU24(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { Assert((u32Value & 0xff000000) == 0); return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); } #endif /* U32 */ static VBOXSTRICTRC hdaRegReadU32(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) { RT_NOREF(pDevIns); uint32_t const iRegMem = g_aHdaRegMap[iReg].mem_idx; *pu32Value = pThis->au32Regs[iRegMem] & g_aHdaRegMap[iReg].readable; return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegWriteU32(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { RT_NOREF(pDevIns); uint32_t const iRegMem = g_aHdaRegMap[iReg].mem_idx; pThis->au32Regs[iRegMem] = (u32Value & g_aHdaRegMap[iReg].writable) | (pThis->au32Regs[iRegMem] & ~g_aHdaRegMap[iReg].writable); return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegWriteGCTL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { RT_NOREF(pDevIns, iReg); 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(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3)); #else return 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). */ } return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegWriteSTATESTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { RT_NOREF(pDevIns); 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. */ return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegReadLPIB(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) { RT_NOREF(pDevIns); const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, LPIB, iReg); const uint32_t u32LPIB = HDA_STREAM_REG(pThis, LPIB, uSD); *pu32Value = u32LPIB; LogFlowFunc(("[SD%RU8] LPIB=%RU32, CBL=%RU32\n", uSD, u32LPIB, HDA_STREAM_REG(pThis, CBL, uSD))); return VINF_SUCCESS; } /** * Gets the wall clock. * * Used by hdaRegReadWALCLK() and 'info hda'. * * @returns The full wall clock value * @param pDevIns The device instance. * @param pThis The shared HDA device state. */ static uint64_t hdaGetWallClock(PPDMDEVINS pDevIns, PHDASTATE pThis) { /* * The wall clock is calculated from the virtual sync clock. Since * the clock is supposed to reset to zero on controller reset, a * start offset is subtracted. * * In addition, we hold the clock back when there are active DMA engines * so that the guest won't conclude we've gotten further in the buffer * processing than what we really have. (We generally read a whole buffer * at once when the IOC is due, so we're a lot later than what real * hardware would be in reading/writing the buffers.) * * Here are some old notes from the DMA engine that might be useful even * if a little dated: * * 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! */ uint64_t const uFreq = PDMDevHlpTimerGetFreq(pDevIns, pThis->aStreams[0].hTimer); Assert(uFreq <= UINT32_MAX); uint64_t const tsStart = 0; /** @todo pThis->tsWallClkStart (as it is reset on controller reset) */ uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[0].hTimer); /* Find the oldest DMA transfer timestamp from the active streams. */ int iDmaNow = -1; uint64_t tsDmaNow = tsNow; for (size_t i = 0; i < RT_ELEMENTS(pThis->aStreams); i++) if ( pThis->aStreams[i].State.fRunning && pThis->aStreams[i].State.tsTransferLast < tsDmaNow && pThis->aStreams[i].State.tsTransferLast > tsStart) { tsDmaNow = pThis->aStreams[i].State.tsTransferLast; iDmaNow = (int)i; } /* Convert it to wall clock ticks. */ uint64_t const uWallClkNow = ASMMultU64ByU32DivByU32(tsDmaNow - tsStart, 24000000 /*Wall clock frequency */, uFreq); Log3Func(("Returning %#RX64 - tsNow=%#RX64 tsDmaNow=%#RX64 (%d) -> %#RX64\n", uWallClkNow, tsNow, tsDmaNow, iDmaNow, tsNow - tsDmaNow)); RT_NOREF(iDmaNow); return uWallClkNow; } static VBOXSTRICTRC hdaRegReadWALCLK(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) { RT_NOREF(pDevIns, iReg); *pu32Value = (uint32_t)hdaGetWallClock(pDevIns, pThis); return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegWriteCORBRP(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { RT_NOREF(pDevIns, iReg); if (u32Value & HDA_CORBRP_RST) { /* Do a CORB reset. */ if (pThis->cbCorbBuf) RT_ZERO(pThis->au32CorbBuf); 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. */ return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegWriteCORBCTL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { VBOXSTRICTRC rc = hdaRegWriteU8(pDevIns, pThis, iReg, u32Value); AssertRCSuccess(VBOXSTRICTRC_VAL(rc)); if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* DMA engine started? */ { #ifdef IN_RING3 /* ignore rc */ hdaR3CORBCmdProcess(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3)); #else if (hdaR0CORBCmdProcess(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER0)) == VERR_NOT_FOUND) return VINF_IOM_R3_MMIO_WRITE; /* Try ring-3. */ #endif } else LogFunc(("CORB DMA not running, skipping\n")); return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegWriteCORBSIZE(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { RT_NOREF(pDevIns, iReg); if (!(HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA)) /* Ignore request if CORB DMA engine is (still) running. */ { u32Value = (u32Value & HDA_CORBSIZE_SZ); uint16_t cEntries; 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. */ cEntries = HDA_CORB_SIZE; /* default. */ break; default: LogRel(("HDA: Guest tried to set an invalid CORB size (0x%x), keeping default\n", u32Value)); u32Value = 2; cEntries = HDA_CORB_SIZE; /* Use default size. */ break; } uint32_t cbCorbBuf = cEntries * HDA_CORB_ELEMENT_SIZE; Assert(cbCorbBuf <= sizeof(pThis->au32CorbBuf)); /* paranoia */ if (cbCorbBuf != pThis->cbCorbBuf) { RT_ZERO(pThis->au32CorbBuf); /* 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; } else LogFunc(("CORB DMA is (still) running, skipping\n")); return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegWriteCORBSTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { RT_NOREF(pDevIns, iReg); uint32_t v = HDA_REG(pThis, CORBSTS); HDA_REG(pThis, CORBSTS) &= ~(v & u32Value); return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegWriteCORBWP(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { VBOXSTRICTRC rc = hdaRegWriteU16(pDevIns, pThis, iReg, u32Value); AssertRCSuccess(VBOXSTRICTRC_VAL(rc)); #ifdef IN_RING3 rc = hdaR3CORBCmdProcess(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3)); if (rc == VERR_NOT_FOUND) rc = VINF_SUCCESS; #else rc = hdaR0CORBCmdProcess(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER0)); if (rc == VERR_NOT_FOUND) /* Try ring-3. */ rc = VINF_IOM_R3_MMIO_WRITE; #endif return rc; } static VBOXSTRICTRC hdaRegWriteSDCBL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); } static VBOXSTRICTRC hdaRegWriteSDCTL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { #ifdef IN_RING3 /* Get the stream descriptor number. */ const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, CTL, iReg); AssertReturn(uSD < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */ /* * 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. */ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); uint8_t uTag = (u32Value >> HDA_SDCTL_NUM_SHIFT) & HDA_SDCTL_NUM_MASK; ASSERT_GUEST_MSG_RETURN(uTag < RT_ELEMENTS(pThisCC->aTags), ("SD%RU8: Invalid stream tag %RU8 (u32Value=%#x)!\n", uSD, uTag, u32Value), VINF_SUCCESS /* Always return success to the MMIO handler. */); PHDASTREAM const pStreamShared = &pThis->aStreams[uSD]; PHDASTREAMR3 const pStreamR3 = &pThisCC->aStreams[uSD]; const bool fRun = RT_BOOL(u32Value & HDA_SDCTL_RUN); const bool fReset = RT_BOOL(u32Value & HDA_SDCTL_SRST); /* If the run bit is set, we take the virtual-sync clock lock as well so we can safely update timers via hdaR3TimerSet if necessary. We need to be very careful with the fInReset and fInRun indicators here, as they may change during the relocking if we need to acquire the clock lock. */ const bool fNeedVirtualSyncClockLock = (u32Value & (HDA_SDCTL_RUN | HDA_SDCTL_SRST)) == HDA_SDCTL_RUN && (HDA_REG_IND(pThis, iReg) & HDA_SDCTL_RUN) == 0; if (fNeedVirtualSyncClockLock) { DEVHDA_UNLOCK(pDevIns, pThis); DEVHDA_LOCK_BOTH_RETURN(pDevIns, pThis, pStreamShared, VINF_IOM_R3_MMIO_WRITE); } const bool fInRun = RT_BOOL(HDA_REG_IND(pThis, iReg) & HDA_SDCTL_RUN); 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));*/ if (fInReset) { ASSERT_GUEST(!fReset); ASSERT_GUEST(!fInRun && !fRun); /* Exit reset state. */ ASMAtomicXchgBool(&pStreamShared->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_GUEST(!fInRun && !fRun); LogFunc(("[SD%RU8] Reset enter\n", uSD)); hdaStreamLock(pStreamShared); # ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO hdaR3StreamAsyncIOLock(pStreamR3); # endif /* Deal with reset while running. */ if (pStreamShared->State.fRunning) { int rc2 = hdaR3StreamEnable(pStreamShared, pStreamR3, false /* fEnable */); AssertRC(rc2); Assert(!pStreamShared->State.fRunning); pStreamShared->State.fRunning = false; } hdaR3StreamReset(pThis, pThisCC, pStreamShared, pStreamR3, uSD); # ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO hdaR3StreamAsyncIOUnlock(pStreamR3); # endif hdaStreamUnlock(pStreamShared); } else { /* * We enter here to change DMA states only. */ if (fInRun != fRun) { Assert(!fReset && !fInReset); /* (code change paranoia, currently impossible ) */ LogFunc(("[SD%RU8] State changed (fRun=%RTbool)\n", uSD, fRun)); hdaStreamLock(pStreamShared); int rc2 = VINF_SUCCESS; # ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO if (fRun) rc2 = hdaR3StreamAsyncIOCreate(pStreamR3); hdaR3StreamAsyncIOLock(pStreamR3); # 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)); } /* Assign new values. */ LogFunc(("[SD%RU8] Using stream tag=%RU8\n", uSD, uTag)); PHDATAG pTag = &pThisCC->aTags[uTag]; pTag->uTag = uTag; pTag->pStreamR3 = &pThisCC->aStreams[uSD]; # ifdef LOG_ENABLED PDMAUDIOPCMPROPS Props; rc2 = hdaR3SDFMTToPCMProps(HDA_STREAM_REG(pThis, FMT, uSD), &Props); AssertRC(rc2); LogFunc(("[SD%RU8] %RU32Hz, %RU8bit, %RU8 channel(s)\n", uSD, Props.uHz, Props.cbSample * 8 /* Bit */, Props.cChannels)); # endif /* (Re-)initialize the stream with current values. */ rc2 = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, pStreamR3, uSD); 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. */ /** @todo r=bird: hdaR3StreamSetUp does not return VINF_NO_CHANGE since r142810. */ && rc2 != VINF_NO_CHANGE) { /* Remove the old stream from the device setup. */ rc2 = hdaR3RemoveStream(pThisCC, &pStreamShared->State.Cfg); AssertRC(rc2); /* Add the stream to the device setup. */ rc2 = hdaR3AddStream(pThisCC, &pStreamShared->State.Cfg); AssertRC(rc2); } } if (RT_SUCCESS(rc2)) { /* Enable/disable the stream. */ rc2 = hdaR3StreamEnable(pStreamShared, pStreamR3, fRun /* fEnable */); AssertRC(rc2); if (fRun) { /** @todo move this into a HDAStream.cpp function. */ uint64_t tsNow; if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) { /* Output streams: Avoid going through the timer here by calling the stream's timer function directly. Should speed up starting the stream transfers. */ tsNow = hdaR3StreamTimerMain(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3); } else { /* Input streams: Arm the timer and kick the AIO thread. */ tsNow = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer); pStreamShared->State.tsTransferLast = tsNow; /* for WALCLK */ uint64_t tsTransferNext = tsNow + pStreamShared->State.aSchedule[0].cPeriodTicks; pStreamShared->State.tsTransferNext = tsTransferNext; /* legacy */ pStreamShared->State.cbTransferSize = pStreamShared->State.aSchedule[0].cbPeriod; Log3Func(("[SD%RU8] tsTransferNext=%RU64 (in %RU64)\n", pStreamShared->u8SD, tsTransferNext, tsTransferNext - tsNow)); int rc = PDMDevHlpTimerSet(pDevIns, pStreamShared->hTimer, tsTransferNext); AssertRC(rc); /** @todo we should have a delayed AIO thread kick off, really... */ hdaR3StreamAsyncIONotify(pStreamR3); } hdaR3StreamMarkStarted(pDevIns, pThis, pStreamShared, tsNow); } else hdaR3StreamMarkStopped(pStreamShared); } # ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO hdaR3StreamAsyncIOUnlock(pStreamR3); # endif /* Make sure to leave the lock before (eventually) starting the timer. */ hdaStreamUnlock(pStreamShared); } } if (fNeedVirtualSyncClockLock) PDMDevHlpTimerUnlockClock(pDevIns, pStreamShared->hTimer); /* Caller will unlock pThis->CritSect. */ return hdaRegWriteU24(pDevIns, pThis, iReg, u32Value); #else /* !IN_RING3 */ RT_NOREF(pDevIns, pThis, iReg, u32Value); return VINF_IOM_R3_MMIO_WRITE; #endif /* !IN_RING3 */ } static VBOXSTRICTRC hdaRegWriteSDSTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { 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); HDA_PROCESS_INTERRUPT(pDevIns, pThis); return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegWriteSDLVI(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { const size_t idxStream = HDA_SD_NUM_FROM_REG(pThis, LVI, iReg); AssertReturn(idxStream < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */ #ifdef HDA_USE_DMA_ACCESS_HANDLER if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) { /* Try registering the DMA handlers. * As we can't be sure in which order LVI + BDL base are set, try registering in both routines. */ PHDASTREAM pStream = hdaGetStreamFromSD(pThis, idxStream); if ( pStream && hdaR3StreamRegisterDMAHandlers(pThis, pStream)) LogFunc(("[SD%RU8] DMA logging enabled\n", pStream->u8SD)); } #endif ASSERT_GUEST_LOGREL_MSG(u32Value <= UINT8_MAX, /* Should be covered by the register write mask, but just to make sure. */ ("LVI for stream #%zu must not be bigger than %RU8\n", idxStream, UINT8_MAX - 1)); return hdaRegWriteU16(pDevIns, pThis, iReg, u32Value); } static VBOXSTRICTRC hdaRegWriteSDFIFOW(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { size_t const idxStream = HDA_SD_NUM_FROM_REG(pThis, FIFOW, iReg); AssertReturn(idxStream < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */ if (RT_LIKELY(hdaGetDirFromSD((uint8_t)idxStream) == PDMAUDIODIR_IN)) /* FIFOW for input streams only. */ { /* likely */ } else { #ifndef IN_RING0 LogRel(("HDA: Warning: Guest tried to write read-only FIFOW to output stream #%RU8, ignoring\n", idxStream)); return VINF_SUCCESS; #else return VINF_IOM_R3_MMIO_WRITE; /* (Go to ring-3 for release logging.) */ #endif } uint16_t u16FIFOW = 0; switch (u32Value) { case HDA_SDFIFOW_8B: case HDA_SDFIFOW_16B: case HDA_SDFIFOW_32B: u16FIFOW = RT_LO_U16(u32Value); /* Only bits 2:0 are used; see ICH-6, 18.2.38. */ break; default: ASSERT_GUEST_LOGREL_MSG_FAILED(("Guest tried writing unsupported FIFOW (0x%zx) to stream #%RU8, defaulting to 32 bytes\n", u32Value, idxStream)); u16FIFOW = HDA_SDFIFOW_32B; break; } pThis->aStreams[idxStream].u8FIFOW = hdaSDFIFOWToBytes(u16FIFOW); LogFunc(("[SD%zu] Updating FIFOW to %RU8 bytes\n", idxStream, pThis->aStreams[idxStream].u8FIFOW)); return hdaRegWriteU16(pDevIns, pThis, iReg, u16FIFOW); } /** * @note This method could be called for changing value on Output Streams only (ICH6 datasheet 18.2.39). */ static VBOXSTRICTRC hdaRegWriteSDFIFOS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, FIFOS, iReg); ASSERT_GUEST_LOGREL_MSG_RETURN(hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT, /* FIFOS for output streams only. */ ("Guest tried writing read-only FIFOS to input stream #%RU8, ignoring\n", uSD), 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 writing unsupported FIFOS (0x%x) to stream #%RU8, defaulting to 192 bytes\n", u32Value, uSD)); u32FIFOS = HDA_SDOFIFO_192B; break; } return hdaRegWriteU16(pDevIns, pThis, iReg, u32FIFOS); } #ifdef IN_RING3 /** * Adds an audio output stream to the device setup using the given configuration. * * @returns IPRT status code. * @param pThisCC The ring-3 HDA device state. * @param pCfg Stream configuration to use for adding a stream. */ static int hdaR3AddStreamOut(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg) { 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->u.enmDst = PDMAUDIOPLAYBACKDST_FRONT; pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cbSample, pCfg->Props.cChannels); rc = hdaR3CodecAddStream(pThisCC->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->u.enmDst = PDMAUDIOPLAYBACKDST_CENTER_LFE; pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; pCfg->Props.cChannels = (fUseCenter && fUseLFE) ? 2 : 1; pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cbSample, pCfg->Props.cChannels); rc = hdaR3CodecAddStream(pThisCC->pCodec, PDMAUDIOMIXERCTL_CENTER_LFE, pCfg); } if ( RT_SUCCESS(rc) && fUseRear) { RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Rear"); pCfg->u.enmDst = PDMAUDIOPLAYBACKDST_REAR; pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; pCfg->Props.cChannels = 2; pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cbSample, pCfg->Props.cChannels); rc = hdaR3CodecAddStream(pThisCC->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 pThisCC The ring-3 HDA device state. * @param pCfg Stream configuration to use for adding a stream. */ static int hdaR3AddStreamIn(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg) { AssertPtrReturn(pCfg, VERR_INVALID_POINTER); AssertReturn(pCfg->enmDir == PDMAUDIODIR_IN, VERR_INVALID_PARAMETER); LogFlowFunc(("Stream=%s, Source=%ld\n", pCfg->szName, pCfg->u.enmSrc)); int rc; switch (pCfg->u.enmSrc) { case PDMAUDIORECSRC_LINE: rc = hdaR3CodecAddStream(pThisCC->pCodec, PDMAUDIOMIXERCTL_LINE_IN, pCfg); break; # ifdef VBOX_WITH_AUDIO_HDA_MIC_IN case PDMAUDIORECSRC_MIC: rc = hdaR3CodecAddStream(pThisCC->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 pThisCC The ring-3 HDA device state. * @param pCfg Stream configuration to use for adding a stream. */ static int hdaR3AddStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg) { AssertPtrReturn(pCfg, VERR_INVALID_POINTER); LogFlowFuncEnter(); int rc; switch (pCfg->enmDir) { case PDMAUDIODIR_OUT: rc = hdaR3AddStreamOut(pThisCC, pCfg); break; case PDMAUDIODIR_IN: rc = hdaR3AddStreamIn(pThisCC, 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 pThisCC The ring-3 HDA device state. * @param pCfg Stream configuration to use for removing a stream. */ static int hdaR3RemoveStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg) { 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->u.enmSrc)); switch (pCfg->u.enmSrc) { case PDMAUDIORECSRC_UNKNOWN: break; case PDMAUDIORECSRC_LINE: enmMixerCtl = PDMAUDIOMIXERCTL_LINE_IN; break; # ifdef VBOX_WITH_AUDIO_HDA_MIC_IN case PDMAUDIORECSRC_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->u.enmDst)); switch (pCfg->u.enmDst) { case PDMAUDIOPLAYBACKDST_UNKNOWN: break; case PDMAUDIOPLAYBACKDST_FRONT: enmMixerCtl = PDMAUDIOMIXERCTL_FRONT; break; # ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND case PDMAUDIOPLAYBACKDST_CENTER_LFE: enmMixerCtl = PDMAUDIOMIXERCTL_CENTER_LFE; break; case PDMAUDIOPLAYBACKDST_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 = hdaR3CodecRemoveStream(pThisCC->pCodec, enmMixerCtl); } LogFlowFuncLeaveRC(rc); return rc; } #endif /* IN_RING3 */ static VBOXSTRICTRC hdaRegWriteSDFMT(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { #ifdef IN_RING3 PDMAUDIOPCMPROPS Props; int rc2 = hdaR3SDFMTToPCMProps(RT_LO_U16(u32Value), &Props); AssertRC(rc2); LogFunc(("[SD%RU8] Set to %#x (%RU32Hz, %RU8bit, %RU8 channel(s))\n", HDA_SD_NUM_FROM_REG(pThis, FMT, iReg), u32Value, Props.uHz, Props.cbSample * 8 /* Bit */, Props.cChannels)); /* * 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. */ return hdaRegWriteU16(pDevIns, pThis, iReg, u32Value); #else RT_NOREF(pDevIns, pThis, iReg, u32Value); return VINF_IOM_R3_MMIO_WRITE; #endif } /** * Worker for writes to the BDPL and BDPU registers. */ DECLINLINE(VBOXSTRICTRC) hdaRegWriteSDBDPX(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value, uint8_t uSD) { #ifndef HDA_USE_DMA_ACCESS_HANDLER RT_NOREF(uSD); return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); #else # ifdef IN_RING3 if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) { /* Try registering the DMA handlers. * As we can't be sure in which order LVI + BDL base are set, try registering in both routines. */ PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uSD); if ( pStream && hdaR3StreamRegisterDMAHandlers(pThis, pStream)) LogFunc(("[SD%RU8] DMA logging enabled\n", pStream->u8SD)); } return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); # else RT_NOREF(pDevIns, pThis, iReg, u32Value, uSD); return VINF_IOM_R3_MMIO_WRITE; # endif #endif } static VBOXSTRICTRC hdaRegWriteSDBDPL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { return hdaRegWriteSDBDPX(pDevIns, pThis, iReg, u32Value, HDA_SD_NUM_FROM_REG(pThis, BDPL, iReg)); } static VBOXSTRICTRC hdaRegWriteSDBDPU(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { return hdaRegWriteSDBDPX(pDevIns, pThis, iReg, u32Value, HDA_SD_NUM_FROM_REG(pThis, BDPU, iReg)); } static VBOXSTRICTRC hdaRegReadIRS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value) { /* 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 */ return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value); } static VBOXSTRICTRC hdaRegWriteIRS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { RT_NOREF(pDevIns, iReg); /* * 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)) { /* * 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 */ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); uint64_t uResp = 0; int rc2 = pThisCC->pCodec->pfnLookup(&pThis->Codec, pThisCC->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 */ return VINF_SUCCESS; #else /* !IN_RING3 */ 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); return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegWriteRIRBWP(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { RT_NOREF(pDevIns, iReg); if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Ignore request if CORB DMA engine is (still) running. */ LogFunc(("CORB DMA (still) running, skipping\n")); else { if (u32Value & HDA_RIRBWP_RST) { /* Do a RIRB reset. */ if (pThis->cbRirbBuf) RT_ZERO(pThis->au64RirbBuf); LogRel2(("HDA: RIRB reset\n")); HDA_REG(pThis, RIRBWP) = 0; } /* The remaining bits are O, see 6.2.22. */ } return VINF_SUCCESS; } static VBOXSTRICTRC hdaRegWriteRINTCNT(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { RT_NOREF(pDevIns); 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")); return VINF_SUCCESS; } VBOXSTRICTRC rc = hdaRegWriteU16(pDevIns, pThis, iReg, u32Value); AssertRC(VBOXSTRICTRC_VAL(rc)); /** @todo r=bird: Shouldn't we make sure the HDASTATE::u16RespIntCnt is below * the new RINTCNT value? Or alterantively, make the DMA look take * this into account instead... I'll do the later for now. */ LogFunc(("Response interrupt count is now %RU8\n", HDA_REG(pThis, RINTCNT) & 0xFF)); return rc; } static VBOXSTRICTRC hdaRegWriteBase(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { RT_NOREF(pDevIns); VBOXSTRICTRC rc = hdaRegWriteU32(pDevIns, pThis, iReg, u32Value); AssertRCSuccess(VBOXSTRICTRC_VAL(rc)); uint32_t const iRegMem = g_aHdaRegMap[iReg].mem_idx; 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); #ifndef IN_RING0 LogRel(("HDA: DP base (lower) set: %#RGp\n", pThis->u64DPBase)); LogRel(("HDA: DMA position buffer is %s\n", pThis->fDMAPosition ? "enabled" : "disabled")); #else return VINF_IOM_R3_MMIO_WRITE; /* (Go to ring-3 for release logging.) */ #endif break; case HDA_REG_DPUBASE: pThis->u64DPBase = RT_MAKE_U64(RT_LO_U32(pThis->u64DPBase) & DPBASE_ADDR_MASK, pThis->au32Regs[iRegMem]); #ifndef IN_RING0 LogRel(("HDA: DP base (upper) set: %#RGp\n", pThis->u64DPBase)); #else return VINF_IOM_R3_MMIO_WRITE; /* (Go to ring-3 for release logging.) */ #endif break; default: AssertMsgFailed(("Invalid index\n")); break; } LogFunc(("CORB base:%llx RIRB base: %llx DP base: %llx\n", pThis->u64CORBBase, pThis->u64RIRBBase, pThis->u64DPBase)); return rc; } static VBOXSTRICTRC hdaRegWriteRIRBSTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value) { RT_NOREF(pDevIns, iReg); uint8_t v = HDA_REG(pThis, RIRBSTS); HDA_REG(pThis, RIRBSTS) &= ~(v & u32Value); HDA_PROCESS_INTERRUPT(pDevIns, pThis); return VINF_SUCCESS; } #ifdef IN_RING3 /** * Retrieves a corresponding sink for a given mixer control. * * @return Pointer to the sink, NULL if no sink is found. * @param pThisCC The ring-3 HDA device state. * @param enmMixerCtl Mixer control to get the corresponding sink for. */ static PHDAMIXERSINK hdaR3MixerControlToSink(PHDASTATER3 pThisCC, PDMAUDIOMIXERCTL enmMixerCtl) { PHDAMIXERSINK pSink; switch (enmMixerCtl) { case PDMAUDIOMIXERCTL_VOLUME_MASTER: /* Fall through is intentional. */ case PDMAUDIOMIXERCTL_FRONT: pSink = &pThisCC->SinkFront; break; # ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND case PDMAUDIOMIXERCTL_CENTER_LFE: pSink = &pThisCC->SinkCenterLFE; break; case PDMAUDIOMIXERCTL_REAR: pSink = &pThisCC->SinkRear; break; # endif case PDMAUDIOMIXERCTL_LINE_IN: pSink = &pThisCC->SinkLineIn; break; # ifdef VBOX_WITH_AUDIO_HDA_MIC_IN case PDMAUDIOMIXERCTL_MIC_IN: pSink = &pThisCC->SinkMicIn; break; # endif default: AssertMsgFailed(("Unhandled mixer control\n")); pSink = NULL; break; } return pSink; } /** * Adds a specific HDA driver to the driver chain. * * @return IPRT status code. * @param pThisCC The ring-3 HDA device state. * @param pDrv HDA driver to add. */ static int hdaR3MixerAddDrv(PHDASTATER3 pThisCC, PHDADRIVER pDrv) { int rc = VINF_SUCCESS; PHDASTREAM pStream = hdaR3GetSharedStreamFromSink(&pThisCC->SinkLineIn); if ( pStream && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) { int rc2 = hdaR3MixerAddDrvStream(pThisCC->SinkLineIn.pMixSink, &pStream->State.Cfg, pDrv); if (RT_SUCCESS(rc)) rc = rc2; } # ifdef VBOX_WITH_AUDIO_HDA_MIC_IN pStream = hdaR3GetSharedStreamFromSink(&pThisCC->SinkMicIn); if ( pStream && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) { int rc2 = hdaR3MixerAddDrvStream(pThisCC->SinkMicIn.pMixSink, &pStream->State.Cfg, pDrv); if (RT_SUCCESS(rc)) rc = rc2; } # endif pStream = hdaR3GetSharedStreamFromSink(&pThisCC->SinkFront); if ( pStream && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) { int rc2 = hdaR3MixerAddDrvStream(pThisCC->SinkFront.pMixSink, &pStream->State.Cfg, pDrv); if (RT_SUCCESS(rc)) rc = rc2; } # ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND pStream = hdaR3GetSharedStreamFromSink(&pThisCC->SinkCenterLFE); if ( pStream && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) { int rc2 = hdaR3MixerAddDrvStream(pThisCC->SinkCenterLFE.pMixSink, &pStream->State.Cfg, pDrv); if (RT_SUCCESS(rc)) rc = rc2; } pStream = hdaR3GetSharedStreamFromSink(&pThisCC->SinkRear); if ( pStream && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg)) { int rc2 = hdaR3MixerAddDrvStream(pThisCC->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 pThisCC The ring-3 HDA device state. * @param pDrv HDA driver to remove. */ static void hdaR3MixerRemoveDrv(PHDASTATER3 pThisCC, PHDADRIVER pDrv) { AssertPtrReturnVoid(pDrv); if (pDrv->LineIn.pMixStrm) { if (AudioMixerSinkGetRecordingSource(pThisCC->SinkLineIn.pMixSink) == pDrv->LineIn.pMixStrm) AudioMixerSinkSetRecordingSource(pThisCC->SinkLineIn.pMixSink, NULL); AudioMixerSinkRemoveStream(pThisCC->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(pThisCC->SinkMicIn.pMixSink) == pDrv->MicIn.pMixStrm) AudioMixerSinkSetRecordingSource(&pThisCC->SinkMicIn.pMixSink, NULL); AudioMixerSinkRemoveStream(pThisCC->SinkMicIn.pMixSink, pDrv->MicIn.pMixStrm); AudioMixerStreamDestroy(pDrv->MicIn.pMixStrm); pDrv->MicIn.pMixStrm = NULL; } # endif if (pDrv->Front.pMixStrm) { AudioMixerSinkRemoveStream(pThisCC->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(pThisCC->SinkCenterLFE.pMixSink, pDrv->CenterLFE.pMixStrm); AudioMixerStreamDestroy(pDrv->CenterLFE.pMixStrm); pDrv->CenterLFE.pMixStrm = NULL; } if (pDrv->Rear.pMixStrm) { AudioMixerSinkRemoveStream(pThisCC->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 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(PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PHDADRIVER pDrv) { 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 = PDMAudioStrmCfgDup(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->u.enmSrc)); switch (pStreamCfg->u.enmSrc) { case PDMAUDIORECSRC_LINE: pDrvStream = &pDrv->LineIn; break; # ifdef VBOX_WITH_AUDIO_HDA_MIC_IN case PDMAUDIORECSRC_MIC: pDrvStream = &pDrv->MicIn; break; # endif default: rc = VERR_NOT_SUPPORTED; break; } } else if (pStreamCfg->enmDir == PDMAUDIODIR_OUT) { LogFunc(("enmPlaybackDest=%d\n", pStreamCfg->u.enmDst)); switch (pStreamCfg->u.enmDst) { case PDMAUDIOPLAYBACKDST_FRONT: pDrvStream = &pDrv->Front; break; # ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND case PDMAUDIOPLAYBACKDST_CENTER_LFE: pDrvStream = &pDrv->CenterLFE; break; case PDMAUDIOPLAYBACKDST_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; } PDMAudioStrmCfgFree(pStreamCfg); LogFlowFuncLeaveRC(rc); return rc; } /** * Adds all current driver streams to a specific mixer sink. * * @returns IPRT status code. * @param pThisCC The ring-3 HDA device 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(PHDASTATER3 pThisCC, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg) { 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(&pThisCC->lstDrv, pDrv, HDADRIVER, Node) { int rc2 = hdaR3MixerAddDrvStream(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{HDACODECR3,pfnCbMixerAddStream} */ static DECLCALLBACK(int) hdaR3MixerAddStream(PPDMDEVINS pDevIns, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg) { PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); AssertPtrReturn(pCfg, VERR_INVALID_POINTER); int rc; PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl); if (pSink) { rc = hdaR3MixerAddDrvStreams(pThisCC, pSink->pMixSink, pCfg); AssertPtr(pSink->pMixSink); LogFlowFunc(("Sink=%s, Mixer control=%s\n", pSink->pMixSink->pszName, PDMAudioMixerCtlGetName(enmMixerCtl))); } else rc = VERR_NOT_FOUND; LogFlowFuncLeaveRC(rc); return rc; } /** * @interface_method_impl{HDACODECR3,pfnCbMixerRemoveStream} */ static DECLCALLBACK(int) hdaR3MixerRemoveStream(PPDMDEVINS pDevIns, PDMAUDIOMIXERCTL enmMixerCtl) { PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); int rc; PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl); if (pSink) { PHDADRIVER pDrv; RTListForEach(&pThisCC->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", PDMAudioMixerCtlGetName(enmMixerCtl), rc)); return rc; } /** * @interface_method_impl{HDACODECR3,pfnCbMixerControl} * * @note Is also called directly by the DevHDA code. */ static DECLCALLBACK(int) hdaR3MixerControl(PPDMDEVINS pDevIns, PDMAUDIOMIXERCTL enmMixerCtl, uint8_t uSD, uint8_t uChannel) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); LogFunc(("enmMixerCtl=%s, uSD=%RU8, uChannel=%RU8\n", PDMAudioMixerCtlGetName(enmMixerCtl), uSD, uChannel)); if (uSD == 0) /* Stream number 0 is reserved. */ { Log2Func(("Invalid SDn (%RU8) number for mixer control '%s', ignoring\n", uSD, PDMAudioMixerCtlGetName(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(pThisCC, 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; /* Make 100% sure we got a good stream number before continuing. */ AssertLogRelReturn(uSD < RT_ELEMENTS(pThisCC->aStreams), VERR_NOT_IMPLEMENTED); /* Detach the existing stream from the sink. */ if ( pSink->pStreamShared && pSink->pStreamR3 && ( pSink->pStreamShared->u8SD != uSD || pSink->pStreamShared->u8Channel != uChannel) ) { LogFunc(("Sink '%s' was assigned to stream #%RU8 (channel %RU8) before\n", pSink->pMixSink->pszName, pSink->pStreamShared->u8SD, pSink->pStreamShared->u8Channel)); hdaStreamLock(pSink->pStreamShared); /* Only disable the stream if the stream descriptor # has changed. */ if (pSink->pStreamShared->u8SD != uSD) hdaR3StreamEnable(pSink->pStreamShared, pSink->pStreamR3, false /*fEnable*/); pSink->pStreamR3->pMixSink = NULL; hdaStreamUnlock(pSink->pStreamShared); pSink->pStreamShared = NULL; pSink->pStreamR3 = NULL; } /* Attach the new stream to the sink. * Enabling the stream will be done by the gust via a separate SDnCTL call then. */ if (pSink->pStreamShared == NULL) { LogRel2(("HDA: Setting sink '%s' to stream #%RU8 (channel %RU8), mixer control=%s\n", pSink->pMixSink->pszName, uSD, uChannel, PDMAudioMixerCtlGetName(enmMixerCtl))); PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[uSD]; PHDASTREAM pStreamShared = &pThis->aStreams[uSD]; hdaStreamLock(pStreamShared); pSink->pStreamR3 = pStreamR3; pSink->pStreamShared = pStreamShared; pStreamShared->u8Channel = uChannel; pStreamR3->pMixSink = pSink; hdaStreamUnlock(pStreamShared); rc = VINF_SUCCESS; } } 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, PDMAudioMixerCtlGetName(enmMixerCtl), rc)); LogFlowFuncLeaveRC(rc); return rc; } /** * @interface_method_impl{HDACODECR3,pfnCbMixerSetVolume} */ static DECLCALLBACK(int) hdaR3MixerSetVolume(PPDMDEVINS pDevIns, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOVOLUME pVol) { PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); int rc; PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, 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; } /** * @callback_method_impl{FNTMTIMERDEV, Main routine for the stream's timer.} */ static DECLCALLBACK(void) hdaR3Timer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); uintptr_t idxStream = (uintptr_t)pvUser; AssertReturnVoid(idxStream < RT_ELEMENTS(pThis->aStreams)); PHDASTREAM pStreamShared = &pThis->aStreams[idxStream]; PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[idxStream]; Assert(hTimer == pStreamShared->hTimer); Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect)); Assert(PDMDevHlpTimerIsLockOwner(pDevIns, hTimer)); RT_NOREF(hTimer); hdaR3StreamTimerMain(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3); } # 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 PHDASTREAMDEBUG 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 # ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA 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); } # endif # 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 pDevIns The device instance. * @param pThis The shared HDA device state. * @param pThisCC The ring-3 HDA device state. */ static void hdaR3GCTLReset(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC) { LogFlowFuncEnter(); /* * Make sure all streams have stopped as these have both timers and * asynchronous worker threads that would race us if we delay this work. */ for (size_t idxStream = 0; idxStream < RT_ELEMENTS(pThis->aStreams); idxStream++) { PHDASTREAM const pStreamShared = &pThis->aStreams[idxStream]; hdaStreamLock(pStreamShared); # ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO hdaR3StreamAsyncIOLock(&pThisCC->aStreams[idxStream]); # endif /* We're doing this unconditionally, hope that's not problematic in any way... */ int rc = hdaR3StreamEnable(pStreamShared, &pThisCC->aStreams[idxStream], false /* fEnable */); AssertLogRelMsg(RT_SUCCESS(rc) && !pStreamShared->State.fRunning, ("Disabling stream #%u failed: %Rrc, fRunning=%d\n", idxStream, rc, pStreamShared->State.fRunning)); pStreamShared->State.fRunning = false; hdaR3StreamReset(pThis, pThisCC, pStreamShared, &pThisCC->aStreams[idxStream], (uint8_t)idxStream); # ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO hdaR3StreamAsyncIOUnlock(&pThisCC->aStreams[idxStream]); # endif hdaStreamUnlock(pStreamShared); } /* * Reset registers. */ 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. */ pThisCC->SinkFront.pStreamShared = NULL; pThisCC->SinkFront.pStreamR3 = NULL; if (pThisCC->SinkFront.pMixSink) AudioMixerSinkReset(pThisCC->SinkFront.pMixSink); # ifdef VBOX_WITH_AUDIO_HDA_MIC_IN pThisCC->SinkMicIn.pStreamShared = NULL; pThisCC->SinkMicIn.pStreamR3 = NULL; if (pThisCC->SinkMicIn.pMixSink) AudioMixerSinkReset(pThisCC->SinkMicIn.pMixSink); # endif pThisCC->SinkLineIn.pStreamShared = NULL; pThisCC->SinkLineIn.pStreamR3 = NULL; if (pThisCC->SinkLineIn.pMixSink) AudioMixerSinkReset(pThisCC->SinkLineIn.pMixSink); # ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND pThisCC->SinkCenterLFE = NULL; if (pThisCC->SinkCenterLFE.pMixSink) AudioMixerSinkReset(pThisCC->SinkCenterLFE.pMixSink); pThisCC->SinkRear.pStreamShared = NULL; pThisCC->SinkRear.pStreamR3 = NULL; if (pThisCC->SinkRear.pMixSink) AudioMixerSinkReset(pThisCC->SinkRear.pMixSink); # endif /* * Reset the codec. */ hdaCodecReset(&pThis->Codec); /* * 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. */ ASMCompilerBarrier(); /* paranoia */ # ifdef VBOX_WITH_AUDIO_HDA_MIC_IN hdaR3MixerControl(pDevIns, PDMAUDIOMIXERCTL_MIC_IN , 1 /* SD0 */, 0 /* Channel */); # endif hdaR3MixerControl(pDevIns, PDMAUDIOMIXERCTL_LINE_IN , 1 /* SD0 */, 0 /* Channel */); hdaR3MixerControl(pDevIns, PDMAUDIOMIXERCTL_FRONT , 5 /* SD4 */, 0 /* Channel */); # ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND hdaR3MixerControl(pDevIns, PDMAUDIOMIXERCTL_CENTER_LFE, 5 /* SD4 */, 0 /* Channel */); hdaR3MixerControl(pDevIns, PDMAUDIOMIXERCTL_REAR , 5 /* SD4 */, 0 /* Channel */); # endif ASMCompilerBarrier(); /* paranoia */ /* Reset CORB. */ pThis->cbCorbBuf = HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE; RT_ZERO(pThis->au32CorbBuf); /* Reset RIRB. */ pThis->cbRirbBuf = HDA_RIRB_SIZE * HDA_RIRB_ELEMENT_SIZE; RT_ZERO(pThis->au64RirbBuf); /* Clear our internal response interrupt counter. */ pThis->u16RespIntCnt = 0; /* Clear stream tags <-> objects mapping table. */ RT_ZERO(pThisCC->aTags); /* Emulation of codec "wake up" (HDA spec 5.5.1 and 6.5). */ HDA_REG(pThis, STATESTS) = 0x1; /* Reset the wall clock. */ pThis->tsWalClkStart = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[0].hTimer); LogFlowFuncLeave(); LogRel(("HDA: Reset\n")); } #else /* !IN_RING3 */ /** * Checks if a dword read starting with @a idxRegDsc is safe. * * We can guarentee it only standard reader callbacks are used. * @returns true if it will always succeed, false if it may return back to * ring-3 or we're just not sure. * @param idxRegDsc The first register descriptor in the DWORD being read. */ DECLINLINE(bool) hdaIsMultiReadSafeInRZ(unsigned idxRegDsc) { int32_t cbLeft = 4; /* signed on purpose */ do { if ( g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadU24 || g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadU16 || g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadU8 || g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadUnimpl) { /* okay */ } else { Log4(("hdaIsMultiReadSafeInRZ: idxRegDsc=%u %s\n", idxRegDsc, g_aHdaRegMap[idxRegDsc].abbrev)); return false; } idxRegDsc++; if (idxRegDsc < RT_ELEMENTS(g_aHdaRegMap)) cbLeft -= g_aHdaRegMap[idxRegDsc].offset - g_aHdaRegMap[idxRegDsc - 1].offset; else break; } while (cbLeft > 0); return true; } #endif /* !IN_RING3 */ /* MMIO callbacks */ /** * @callback_method_impl{FNIOMMMIONEWREAD, 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. */ static DECLCALLBACK(VBOXSTRICTRC) hdaMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); VBOXSTRICTRC rc; RT_NOREF_PV(pvUser); Assert(pThis->uAlignmentCheckMagic == HDASTATE_ALIGNMENT_CHECK_MAGIC); /* * Look up and log. */ int idxRegDsc = hdaRegLookup(off); /* Register descriptor index. */ #ifdef LOG_ENABLED unsigned const cbLog = cb; uint32_t offRegLog = (uint32_t)off; # ifdef HDA_DEBUG_GUEST_RIP if (LogIs6Enabled()) { PVMCPU pVCpu = (PVMCPU)PDMDevHlpGetVMCPU(pDevIns); Log6Func(("cs:rip=%04x:%016RX64 rflags=%08RX32\n", CPUMGetGuestCS(pVCpu), CPUMGetGuestRIP(pVCpu), CPUMGetGuestEFlags(pVCpu))); } # endif #endif Log3Func(("off=%#x cb=%#x\n", offRegLog, cb)); Assert(cb == 4); Assert((off & 3) == 0); rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_IOM_R3_MMIO_READ); if (rc == VINF_SUCCESS) { if (!(HDA_REG(pThis, GCTL) & HDA_GCTL_CRST) && idxRegDsc != HDA_REG_GCTL) LogFunc(("Access to registers except GCTL is blocked while resetting\n")); if (idxRegDsc >= 0) { /* ASSUMES gapless DWORD at end of map. */ if (g_aHdaRegMap[idxRegDsc].size == 4) { /* * Straight forward DWORD access. */ rc = g_aHdaRegMap[idxRegDsc].pfnRead(pDevIns, pThis, idxRegDsc, (uint32_t *)pv); Log3Func(("\tRead %s => %x (%Rrc)\n", g_aHdaRegMap[idxRegDsc].abbrev, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc))); STAM_COUNTER_INC(&pThis->aStatRegReads[idxRegDsc]); } #ifndef IN_RING3 else if (!hdaIsMultiReadSafeInRZ(idxRegDsc)) { STAM_COUNTER_INC(&pThis->aStatRegReadsToR3[idxRegDsc]); rc = VINF_IOM_R3_MMIO_READ; } #endif else { /* * Multi register read (unless there are trailing gaps). * ASSUMES that only DWORD reads have sideeffects. */ STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegMultiReads)); Log4(("hdaMmioRead: multi read: %#x LB %#x %s\n", off, cb, g_aHdaRegMap[idxRegDsc].abbrev)); 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(pDevIns, pThis, idxRegDsc, &u32Tmp); Log4Func(("\tRead %s[%db] => %x (%Rrc)*\n", g_aHdaRegMap[idxRegDsc].abbrev, cbReg, u32Tmp, VBOXSTRICTRC_VAL(rc))); STAM_COUNTER_INC(&pThis->aStatRegReads[idxRegDsc]); #ifdef IN_RING3 if (rc != VINF_SUCCESS) break; #else AssertMsgBreak(rc == VINF_SUCCESS, ("rc=%Rrc - impossible, we sanitized the readers!\n", VBOXSTRICTRC_VAL(rc))); #endif u32Value |= (u32Tmp & g_afMasks[cbReg]) << ((4 - cbLeft) * 8); cbLeft -= cbReg; off += cbReg; idxRegDsc++; } while (cbLeft > 0 && g_aHdaRegMap[idxRegDsc].offset == off); if (rc == VINF_SUCCESS) *(uint32_t *)pv = u32Value; else Assert(!IOM_SUCCESS(rc)); } } else { LogRel(("HDA: Invalid read access @0x%x (bytes=%u)\n", (uint32_t)off, cb)); Log3Func(("\tHole at %x is accessed for read\n", offRegLog)); STAM_COUNTER_INC(&pThis->StatRegUnknownReads); rc = VINF_IOM_MMIO_UNUSED_FF; } DEVHDA_UNLOCK(pDevIns, pThis); /* * Log the outcome. */ #ifdef LOG_ENABLED if (cbLog == 4) Log3Func(("\tReturning @%#05x -> %#010x %Rrc\n", offRegLog, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc))); else if (cbLog == 2) Log3Func(("\tReturning @%#05x -> %#06x %Rrc\n", offRegLog, *(uint16_t *)pv, VBOXSTRICTRC_VAL(rc))); else if (cbLog == 1) Log3Func(("\tReturning @%#05x -> %#04x %Rrc\n", offRegLog, *(uint8_t *)pv, VBOXSTRICTRC_VAL(rc))); #endif } else { if (idxRegDsc >= 0) STAM_COUNTER_INC(&pThis->aStatRegReadsToR3[idxRegDsc]); } return rc; } DECLINLINE(VBOXSTRICTRC) hdaWriteReg(PPDMDEVINS pDevIns, PHDASTATE pThis, int idxRegDsc, uint32_t u32Value, char const *pszLog) { DEVHDA_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE); if ( (HDA_REG(pThis, GCTL) & HDA_GCTL_CRST) || idxRegDsc == HDA_REG_GCTL) { /* likely */ } else { 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)); STAM_COUNTER_INC(&pThis->StatRegWritesBlockedByReset); DEVHDA_UNLOCK(pDevIns, 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) { /* * 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. */ const uint32_t uSDCTL = HDA_STREAM_REG(pThis, CTL, HDA_SD_NUM_FROM_REG(pThis, CTL, idxRegDsc)); if ( !(uSDCTL & HDA_SDCTL_RUN) || (g_aHdaRegMap[idxRegDsc].fFlags & HDA_RD_F_SD_WRITE_RUN)) { /* likely */ } else { 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)); STAM_COUNTER_INC(&pThis->StatRegWritesBlockedByRun); DEVHDA_UNLOCK(pDevIns, pThis); return VINF_SUCCESS; } } #ifdef LOG_ENABLED uint32_t const idxRegMem = g_aHdaRegMap[idxRegDsc].mem_idx; uint32_t const u32OldValue = pThis->au32Regs[idxRegMem]; #endif VBOXSTRICTRC rc = g_aHdaRegMap[idxRegDsc].pfnWrite(pDevIns, 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, VBOXSTRICTRC_VAL(rc))); #ifndef IN_RING3 if (rc == VINF_IOM_R3_MMIO_WRITE) STAM_COUNTER_INC(&pThis->aStatRegWritesToR3[idxRegDsc]); else #endif STAM_COUNTER_INC(&pThis->aStatRegWrites[idxRegDsc]); DEVHDA_UNLOCK(pDevIns, pThis); RT_NOREF(pszLog); return rc; } /** * @callback_method_impl{FNIOMMMIONEWWRITE, * Looks up and calls the appropriate handler.} */ static DECLCALLBACK(VBOXSTRICTRC) hdaMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); RT_NOREF_PV(pvUser); Assert(pThis->uAlignmentCheckMagic == HDASTATE_ALIGNMENT_CHECK_MAGIC); /* * Look up and log the access. */ int idxRegDsc = hdaRegLookup(off); #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 ASSERT_GUEST_MSG_FAILED_RETURN(("cb=%u %.*Rhxs\n", cb, cb, pv), PDMDevHlpDBGFStop(pDevIns, RT_SRC_POS, "odd write size: off=%RGp cb=%u\n", off, cb)); /* * The behavior of accesses that aren't aligned on natural boundraries is * undefined. Just reject them outright. */ ASSERT_GUEST_MSG_RETURN((off & (cb - 1)) == 0, ("off=%RGp cb=%u %.*Rhxs\n", off, cb, cb, pv), PDMDevHlpDBGFStop(pDevIns, RT_SRC_POS, "misaligned write access: off=%RGp cb=%u\n", off, cb)); #ifdef LOG_ENABLED uint32_t const u32LogOldValue = idxRegDsc >= 0 ? pThis->au32Regs[idxRegMem] : UINT32_MAX; # ifdef HDA_DEBUG_GUEST_RIP if (LogIs6Enabled()) { PVMCPU pVCpu = (PVMCPU)PDMDevHlpGetVMCPU(pDevIns); Log6Func(("cs:rip=%04x:%016RX64 rflags=%08RX32\n", CPUMGetGuestCS(pVCpu), CPUMGetGuestRIP(pVCpu), CPUMGetGuestEFlags(pVCpu))); } # endif #endif /* * Try for a direct hit first. */ VBOXSTRICTRC rc; if (idxRegDsc >= 0 && g_aHdaRegMap[idxRegDsc].size == cb) { Log3Func(("@%#05x u%u=%#0*RX64 %s\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, g_aHdaRegMap[idxRegDsc].abbrev)); rc = hdaWriteReg(pDevIns, pThis, idxRegDsc, u64Value, ""); Log3Func(("\t%#x -> %#x\n", u32LogOldValue, idxRegMem != UINT32_MAX ? pThis->au32Regs[idxRegMem] : UINT32_MAX)); } /* * Sub-register access. Supply missing bits as needed. */ else if ( idxRegDsc >= 0 && cb < g_aHdaRegMap[idxRegDsc].size) { u64Value |= pThis->au32Regs[g_aHdaRegMap[idxRegDsc].mem_idx] & g_afMasks[g_aHdaRegMap[idxRegDsc].size] & ~g_afMasks[cb]; Log4Func(("@%#05x u%u=%#0*RX64 cb=%#x cbReg=%x %s\n" "\tSupplying missing bits (%#x): %#llx -> %#llx ...\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, cb, g_aHdaRegMap[idxRegDsc].size, g_aHdaRegMap[idxRegDsc].abbrev, g_afMasks[g_aHdaRegMap[idxRegDsc].size] & ~g_afMasks[cb], u64Value & g_afMasks[cb], u64Value)); rc = hdaWriteReg(pDevIns, pThis, idxRegDsc, u64Value, ""); Log4Func(("\t%#x -> %#x\n", u32LogOldValue, idxRegMem != UINT32_MAX ? pThis->au32Regs[idxRegMem] : UINT32_MAX)); STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegSubWrite)); } /* * Partial or multiple register access, loop thru the requested memory. */ else { #ifdef IN_RING3 if (idxRegDsc == -1) Log4Func(("@%#05x u32=%#010x cb=%d\n", (uint32_t)off, *(uint32_t const *)pv, cb)); else if (g_aHdaRegMap[idxRegDsc].size == cb) Log4Func(("@%#05x u%u=%#0*RX64 %s\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, g_aHdaRegMap[idxRegDsc].abbrev)); else Log4Func(("@%#05x u%u=%#0*RX64 %s - mismatch cbReg=%u\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, g_aHdaRegMap[idxRegDsc].abbrev, g_aHdaRegMap[idxRegDsc].size)); /* * 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 < 0) { idxRegDsc = hdaR3RegLookupWithin(off); if (idxRegDsc != -1) { uint32_t const cbBefore = (uint32_t)off - g_aHdaRegMap[idxRegDsc].offset; Assert(cbBefore > 0 && cbBefore < 4); off -= cbBefore; idxRegMem = g_aHdaRegMap[idxRegDsc].mem_idx; u64Value <<= cbBefore * 8; u64Value |= pThis->au32Regs[idxRegMem] & g_afMasks[cbBefore]; Log4Func(("\tWithin register, supplied %u leading bits: %#llx -> %#llx ...\n", cbBefore * 8, ~(uint64_t)g_afMasks[cbBefore] & u64Value, u64Value)); STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegMultiWrites)); } else STAM_COUNTER_INC(&pThis->StatRegUnknownWrites); } else { Log4(("hdaMmioWrite: multi write: %s\n", g_aHdaRegMap[idxRegDsc].abbrev)); STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegMultiWrites)); } /* Loop thru the write area, it may cover multiple registers. */ rc = VINF_SUCCESS; for (;;) { uint32_t cbReg; if (idxRegDsc >= 0) { idxRegMem = g_aHdaRegMap[idxRegDsc].mem_idx; cbReg = g_aHdaRegMap[idxRegDsc].size; if (cb < cbReg) { u64Value |= pThis->au32Regs[idxRegMem] & g_afMasks[cbReg] & ~g_afMasks[cb]; Log4Func(("\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(pDevIns, pThis, idxRegDsc, u64Value & g_afMasks[cbReg], "*"); Log4Func(("\t%#x -> %#x\n", uLogOldVal, pThis->au32Regs[idxRegMem])); } else { LogRel(("HDA: Invalid write access @0x%x\n", (uint32_t)off)); cbReg = 1; } if (rc != VINF_SUCCESS) break; if (cbReg >= cb) break; /* Advance. */ off += cbReg; cb -= cbReg; u64Value >>= cbReg * 8; if (idxRegDsc == -1) idxRegDsc = hdaRegLookup(off); else { idxRegDsc++; if ( (unsigned)idxRegDsc >= RT_ELEMENTS(g_aHdaRegMap) || g_aHdaRegMap[idxRegDsc].offset != off) idxRegDsc = -1; } } #else /* !IN_RING3 */ /* Take the simple way out. */ rc = VINF_IOM_R3_MMIO_WRITE; #endif /* !IN_RING3 */ } return rc; } #ifdef IN_RING3 /********************************************************************************************************************************* * Saved state * *********************************************************************************************************************************/ /** * @callback_method_impl{FNSSMFIELDGETPUT, * Version 6 saves the IOC flag in HDABDLEDESC::fFlags as a bool} */ static DECLCALLBACK(int) hdaR3GetPutTrans_HDABDLEDESC_fFlags_6(PSSMHANDLE pSSM, const struct SSMFIELD *pField, void *pvStruct, uint32_t fFlags, bool fGetOrPut, void *pvUser) { PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser; RT_NOREF(pSSM, pField, pvStruct, fFlags); AssertReturn(fGetOrPut, VERR_INTERNAL_ERROR_4); bool fIoc; int rc = pDevIns->pHlpR3->pfnSSMGetBool(pSSM, &fIoc); if (RT_SUCCESS(rc)) { PHDABDLEDESC pDesc = (PHDABDLEDESC)pvStruct; pDesc->fFlags = fIoc ? HDA_BDLE_F_IOC : 0; } return rc; } /** * @callback_method_impl{FNSSMFIELDGETPUT, * Versions 1 thru 4 save the IOC flag in HDASTREAMSTATE::DescfFlags as a bool} */ static DECLCALLBACK(int) hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4(PSSMHANDLE pSSM, const struct SSMFIELD *pField, void *pvStruct, uint32_t fFlags, bool fGetOrPut, void *pvUser) { PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser; RT_NOREF(pSSM, pField, pvStruct, fFlags); AssertReturn(fGetOrPut, VERR_INTERNAL_ERROR_4); bool fIoc; int rc = pDevIns->pHlpR3->pfnSSMGetBool(pSSM, &fIoc); if (RT_SUCCESS(rc)) { HDABDLELEGACY *pState = (HDABDLELEGACY *)pvStruct; pState->Desc.fFlags = fIoc ? HDA_BDLE_F_IOC : 0; } return rc; } static int hdaR3SaveStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3) { PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; # ifdef LOG_ENABLED PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); # endif Log2Func(("[SD%RU8]\n", pStreamShared->u8SD)); /* Save stream ID. */ Assert(pStreamShared->u8SD < HDA_MAX_STREAMS); int rc = pHlp->pfnSSMPutU8(pSSM, pStreamShared->u8SD); AssertRCReturn(rc, rc); rc = pHlp->pfnSSMPutStructEx(pSSM, &pStreamShared->State, sizeof(pStreamShared->State), 0 /*fFlags*/, g_aSSMStreamStateFields7, NULL); AssertRCReturn(rc, rc); AssertCompile(sizeof(pStreamShared->State.idxCurBdle) == sizeof(uint8_t) && RT_ELEMENTS(pStreamShared->State.aBdl) == 256); HDABDLEDESC TmpDesc = *(HDABDLEDESC *)&pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle]; rc = pHlp->pfnSSMPutStructEx(pSSM, &TmpDesc, sizeof(TmpDesc), 0 /*fFlags*/, g_aSSMBDLEDescFields7, NULL); AssertRCReturn(rc, rc); HDABDLESTATELEGACY TmpState = { pStreamShared->State.idxCurBdle, 0, pStreamShared->State.offCurBdle, 0 }; rc = pHlp->pfnSSMPutStructEx(pSSM, &TmpState, sizeof(TmpState), 0 /*fFlags*/, g_aSSMBDLEStateFields7, NULL); AssertRCReturn(rc, rc); uint32_t cbCircBufSize = 0; uint32_t cbCircBufUsed = 0; if (pStreamR3->State.pCircBuf) { cbCircBufSize = (uint32_t)RTCircBufSize(pStreamR3->State.pCircBuf); cbCircBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf); } rc = pHlp->pfnSSMPutU32(pSSM, cbCircBufSize); AssertRCReturn(rc, rc); rc = pHlp->pfnSSMPutU32(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(pStreamR3->State.pCircBuf); void *pvBuf; size_t cbBuf; RTCircBufAcquireReadBlock(pStreamR3->State.pCircBuf, cbCircBufUsed, &pvBuf, &cbBuf); Assert(cbBuf); if (cbBuf) { rc = pHlp->pfnSSMPutMem(pSSM, pvBuf, cbBuf); AssertRC(rc); if ( RT_SUCCESS(rc) && cbBuf < cbCircBufUsed) { rc = pHlp->pfnSSMPutMem(pSSM, (uint8_t *)pvBuf - offBuf, cbCircBufUsed - cbBuf); } } RTCircBufReleaseReadBlock(pStreamR3->State.pCircBuf, 0 /* Don't advance read pointer -- see comment above */); } Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", pStreamR3->u8SD, HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD), HDA_STREAM_REG(pThis, CBL, pStreamShared->u8SD), HDA_STREAM_REG(pThis, LVI, pStreamShared->u8SD))); #ifdef LOG_ENABLED hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1); #endif return rc; } /** * @callback_method_impl{FNSSMDEVSAVEEXEC} */ static DECLCALLBACK(int) hdaR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; /* Save Codec nodes states. */ hdaCodecSaveState(pDevIns, &pThis->Codec, pSSM); /* Save MMIO registers. */ pHlp->pfnSSMPutU32(pSSM, RT_ELEMENTS(pThis->au32Regs)); pHlp->pfnSSMPutMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs)); /* Save controller-specifc internals. */ pHlp->pfnSSMPutU64(pSSM, pThis->tsWalClkStart); pHlp->pfnSSMPutU8(pSSM, pThis->u8IRQL); /* Save number of streams. */ pHlp->pfnSSMPutU32(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], &pThisCC->aStreams[i]); AssertRCReturn(rc, rc); } return VINF_SUCCESS; } /** * Does tail processing after having loaded our saved state. * * @param pDevIns The device instance. * @param pThis Pointer to the shared HDA state. * @param pThisCC Pointer to the ring-3 HDA state. * @param pSSM The saved state handle. * * @todo r=bird: Replace by a post load callback. */ static int hdaR3LoadExecTail(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, PSSMHANDLE pSSM) { int rc = VINF_SUCCESS; /** @todo r=bird: Really, what's the point of this? */ /* * Enable all previously active streams. */ for (size_t i = 0; i < HDA_MAX_STREAMS; i++) { PHDASTREAM pStreamShared = &pThis->aStreams[i]; bool fActive = RT_BOOL(HDA_STREAM_REG(pThis, CTL, i) & HDA_SDCTL_RUN); if (fActive) { PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[i]; int rc2; #ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO /* Make sure to also create the async I/O thread before actually enabling the stream. */ rc2 = hdaR3StreamAsyncIOCreate(pStreamR3); AssertRC(rc2); /* ... and enabling it. */ hdaR3StreamAsyncIOEnable(pStreamR3, true /* fEnable */); #endif /* (Re-)enable the stream. */ rc2 = hdaR3StreamEnable(pStreamShared, pStreamR3, true /* fEnable */); AssertRC(rc2); /* Add the stream to the device setup. */ rc2 = hdaR3AddStream(pThisCC, &pStreamShared->State.Cfg); AssertRC(rc2); #ifdef HDA_USE_DMA_ACCESS_HANDLER /* (Re-)install the DMA handler. */ hdaR3StreamRegisterDMAHandlers(pThis, pStreamShared); #endif /* Use the LPIB to find the current scheduling position. If this isn't exactly on a scheduling item adjust LPIB down to the start of the current. This isn't entirely ideal, but it avoid the IRQ counting issue if we round it upwards. (it is also a lot simpler) */ uint32_t uLpib = HDA_STREAM_REG(pThis, LPIB, i); AssertLogRelMsgStmt(uLpib < pStreamShared->u32CBL, ("LPIB=%#RX32 CBL=%#RX32\n", uLpib, pStreamShared->u32CBL), HDA_STREAM_REG(pThis, LPIB, i) = uLpib = 0); uint32_t off = 0; for (uint32_t j = 0; j < pStreamShared->State.cSchedule; j++) { AssertReturn(pStreamShared->State.aSchedule[j].cbPeriod >= 1 && pStreamShared->State.aSchedule[j].cLoops >= 1, pDevIns->pHlpR3->pfnSSMSetLoadError(pSSM, VERR_INTERNAL_ERROR_2, RT_SRC_POS, "Stream #%u, sched #%u: cbPeriod=%u cLoops=%u\n", pStreamShared->u8SD, j, pStreamShared->State.aSchedule[j].cbPeriod, pStreamShared->State.aSchedule[j].cLoops)); uint32_t cbCur = pStreamShared->State.aSchedule[j].cbPeriod * pStreamShared->State.aSchedule[j].cLoops; if (uLpib >= off + cbCur) off += cbCur; else { uint32_t const offDelta = uLpib - off; uint32_t idxLoop = offDelta / pStreamShared->State.aSchedule[j].cbPeriod; uint32_t offLoop = offDelta % pStreamShared->State.aSchedule[j].cbPeriod; if (offLoop) { /** @todo somehow bake this into the DMA timer logic. */ LogFunc(("stream #%u: LPIB=%#RX32; adjusting due to scheduling clash: -%#x (j=%u idxLoop=%u cbPeriod=%#x)\n", pStreamShared->u8SD, uLpib, offLoop, j, idxLoop, pStreamShared->State.aSchedule[j].cbPeriod)); uLpib -= offLoop; HDA_STREAM_REG(pThis, LPIB, i) = uLpib; } pStreamShared->State.idxSchedule = (uint16_t)j; pStreamShared->State.idxScheduleLoop = (uint16_t)idxLoop; off = UINT32_MAX; break; } } Assert(off == UINT32_MAX); /* Now figure out the current BDLE and the offset within it. */ off = 0; for (uint32_t j = 0; j < pStreamShared->State.cBdles; j++) if (uLpib >= off + pStreamShared->State.aBdl[j].cb) off += pStreamShared->State.aBdl[j].cb; else { pStreamShared->State.idxCurBdle = j; pStreamShared->State.offCurBdle = uLpib - off; off = UINT32_MAX; break; } AssertReturn(off == UINT32_MAX, pDevIns->pHlpR3->pfnSSMSetLoadError(pSSM, VERR_INTERNAL_ERROR_3, RT_SRC_POS, "Stream #%u: LPIB=%#RX32 not found in loaded BDL\n", pStreamShared->u8SD, uLpib)); /* Avoid going through the timer here by calling the stream's timer function directly. * Should speed up starting the stream transfers. */ uint64_t tsNow = hdaR3StreamTimerMain(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3); hdaR3StreamMarkStarted(pDevIns, pThis, pStreamShared, tsNow); } } LogFlowFuncLeaveRC(rc); return rc; } /** * Handles loading of all saved state versions older than the current one. * * @param pDevIns The device instance. * @param pThis Pointer to the shared HDA state. * @param pThisCC Pointer to the ring-3 HDA state. * @param pSSM The saved state handle. * @param uVersion Saved state version to load. */ static int hdaR3LoadExecLegacy(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, PSSMHANDLE pSSM, uint32_t uVersion) { PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; int rc; /* * Load MMIO registers. */ uint32_t cRegs; switch (uVersion) { case HDA_SAVED_STATE_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 (pHlp->pfnSSMHandleRevision(pSSM) >= 71199) { uint32_t uVer = pHlp->pfnSSMHandleVersion(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_SAVED_STATE_VERSION_2: case HDA_SAVED_STATE_VERSION_3: cRegs = 112; AssertCompile(RT_ELEMENTS(pThis->au32Regs) >= 112); break; /* Since version 4 we store the register count to stay flexible. */ case HDA_SAVED_STATE_VERSION_4: case HDA_SAVED_STATE_VERSION_5: case HDA_SAVED_STATE_VERSION_6: rc = pHlp->pfnSSMGetU32(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: AssertLogRelMsgFailedReturn(("HDA: Internal Error! Didn't expect saved state version %RU32 ending up in hdaR3LoadExecLegacy!\n", uVersion), VERR_INTERNAL_ERROR_5); } if (cRegs >= RT_ELEMENTS(pThis->au32Regs)) { pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs)); pHlp->pfnSSMSkip(pSSM, sizeof(uint32_t) * (cRegs - RT_ELEMENTS(pThis->au32Regs))); } else pHlp->pfnSSMGetMem(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 BDLEs (Buffer Descriptor List Entries) and DMA counters. * * 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). */ switch (uVersion) { case HDA_SAVED_STATE_VERSION_1: case HDA_SAVED_STATE_VERSION_2: case HDA_SAVED_STATE_VERSION_3: case HDA_SAVED_STATE_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! */ HDABDLELEGACY BDLE; /* Output */ PHDASTREAM pStreamShared = &pThis->aStreams[4]; rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[4], 4 /* Stream descriptor, hardcoded */); AssertRCReturn(rc, rc); RT_ZERO(BDLE); rc = pHlp->pfnSSMGetStructEx(pSSM, &BDLE, sizeof(BDLE), 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns); AssertRCReturn(rc, rc); pStreamShared->State.idxCurBdle = (uint8_t)BDLE.State.u32BDLIndex; /* not necessary */ /* Microphone-In */ pStreamShared = &pThis->aStreams[2]; rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[2], 2 /* Stream descriptor, hardcoded */); AssertRCReturn(rc, rc); rc = pHlp->pfnSSMGetStructEx(pSSM, &BDLE, sizeof(BDLE), 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns); AssertRCReturn(rc, rc); pStreamShared->State.idxCurBdle = (uint8_t)BDLE.State.u32BDLIndex; /* not necessary */ /* Line-In */ pStreamShared = &pThis->aStreams[0]; rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[0], 0 /* Stream descriptor, hardcoded */); AssertRCReturn(rc, rc); rc = pHlp->pfnSSMGetStructEx(pSSM, &BDLE, sizeof(BDLE), 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns); AssertRCReturn(rc, rc); pStreamShared->State.idxCurBdle = (uint8_t)BDLE.State.u32BDLIndex; /* not necessary */ break; } /* * v5 & v6 - Since v5 we support flexible stream and BDLE counts. */ default: { /* Stream count. */ uint32_t cStreams; rc = pHlp->pfnSSMGetU32(pSSM, &cStreams); AssertRCReturn(rc, rc); if (cStreams > HDA_MAX_STREAMS) return pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, N_("State contains %u streams while %u is the maximum supported"), cStreams, HDA_MAX_STREAMS); /* Load stream states. */ for (uint32_t i = 0; i < cStreams; i++) { uint8_t idStream; rc = pHlp->pfnSSMGetU8(pSSM, &idStream); AssertRCReturn(rc, rc); HDASTREAM StreamDummyShared; HDASTREAMR3 StreamDummyR3; PHDASTREAM pStreamShared = idStream < RT_ELEMENTS(pThis->aStreams) ? &pThis->aStreams[idStream] : &StreamDummyShared; PHDASTREAMR3 pStreamR3 = idStream < RT_ELEMENTS(pThisCC->aStreams) ? &pThisCC->aStreams[idStream] : &StreamDummyR3; AssertLogRelMsgStmt(idStream < RT_ELEMENTS(pThisCC->aStreams), ("HDA stream ID=%RU8 not supported, skipping loadingit ...\n", idStream), RT_ZERO(StreamDummyShared); RT_ZERO(StreamDummyR3)); rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, pStreamR3, idStream); if (RT_FAILURE(rc)) { LogRel(("HDA: Stream #%RU32: Setting up of stream %RU8 failed, rc=%Rrc\n", i, idStream, rc)); break; } /* * Load BDLEs (Buffer Descriptor List Entries) and DMA counters. */ if (uVersion == HDA_SAVED_STATE_VERSION_5) { struct V5HDASTREAMSTATE /* HDASTREAMSTATE + HDABDLE */ { uint16_t cBLDEs; uint16_t uCurBDLE; uint32_t u32BDLEIndex; uint32_t cbBelowFIFOW; uint32_t u32BufOff; } Tmp; static SSMFIELD const g_aV5State1Fields[] = { SSMFIELD_ENTRY(V5HDASTREAMSTATE, cBLDEs), SSMFIELD_ENTRY(V5HDASTREAMSTATE, uCurBDLE), SSMFIELD_ENTRY_TERM() }; rc = pHlp->pfnSSMGetStructEx(pSSM, &Tmp, sizeof(Tmp), 0 /* fFlags */, g_aV5State1Fields, NULL); AssertRCReturn(rc, rc); pStreamShared->State.idxCurBdle = (uint8_t)Tmp.uCurBDLE; /* not necessary */ for (uint16_t a = 0; a < Tmp.cBLDEs; a++) { static SSMFIELD const g_aV5State2Fields[] = { SSMFIELD_ENTRY(V5HDASTREAMSTATE, u32BDLEIndex), SSMFIELD_ENTRY_OLD(au8FIFO, 256), SSMFIELD_ENTRY(V5HDASTREAMSTATE, cbBelowFIFOW), SSMFIELD_ENTRY_TERM() }; rc = pHlp->pfnSSMGetStructEx(pSSM, &Tmp, sizeof(Tmp), 0 /* fFlags */, g_aV5State2Fields, NULL); AssertRCReturn(rc, rc); } } else { rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State, sizeof(HDASTREAMSTATE), 0 /* fFlags */, g_aSSMStreamStateFields6, NULL); AssertRCReturn(rc, rc); HDABDLEDESC IgnDesc; rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnDesc, sizeof(IgnDesc), 0 /* fFlags */, g_aSSMBDLEDescFields6, pDevIns); AssertRCReturn(rc, rc); HDABDLESTATELEGACY IgnState; rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnState, sizeof(IgnState), 0 /* fFlags */, g_aSSMBDLEStateFields6, NULL); AssertRCReturn(rc, rc); Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", idStream, HDA_STREAM_REG(pThis, LPIB, idStream), HDA_STREAM_REG(pThis, CBL, idStream), HDA_STREAM_REG(pThis, LVI, idStream))); #ifdef LOG_ENABLED hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->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 = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); LogRel2(("hdaR3LoadExec: uVersion=%RU32, uPass=0x%x\n", uVersion, uPass)); /* * Load Codec nodes states. */ int rc = hdaR3CodecLoadState(pDevIns, &pThis->Codec, pThisCC->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_SAVED_STATE_VERSION_6) /* Handle older saved states? */ { rc = hdaR3LoadExecLegacy(pDevIns, pThis, pThisCC, pSSM, uVersion); if (RT_SUCCESS(rc)) rc = hdaR3LoadExecTail(pDevIns, pThis, pThisCC, pSSM); return rc; } /* * Load MMIO registers. */ uint32_t cRegs; rc = pHlp->pfnSSMGetU32(pSSM, &cRegs); AssertRCReturn(rc, rc); 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)) { pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs)); rc = pHlp->pfnSSMSkip(pSSM, sizeof(uint32_t) * (cRegs - RT_ELEMENTS(pThis->au32Regs))); AssertRCReturn(rc, rc); } else { rc = pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(uint32_t) * cRegs); AssertRCReturn(rc, rc); } /* 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 ( pHlp->pfnSSMHandleRevision(pSSM) >= 116273 || pHlp->pfnSSMHandleVersion(pSSM) >= VBOX_FULL_VERSION_MAKE(5, 2, 0)) { pHlp->pfnSSMGetU64(pSSM, &pThis->tsWalClkStart); /* Was current wall clock */ rc = pHlp->pfnSSMGetU8(pSSM, &pThis->u8IRQL); AssertRCReturn(rc, rc); /* Convert the saved wall clock timestamp to a start timestamp. */ if (uVersion < HDA_SAVED_STATE_WITHOUT_PERIOD && pThis->tsWalClkStart != 0) { uint64_t const cTimerTicksPerSec = PDMDevHlpTimerGetFreq(pDevIns, pThis->aStreams[0].hTimer); AssertLogRel(cTimerTicksPerSec <= UINT32_MAX); pThis->tsWalClkStart = ASMMultU64ByU32DivByU32(pThis->tsWalClkStart, cTimerTicksPerSec, 24000000 /* wall clock freq */); pThis->tsWalClkStart = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[0].hTimer) - pThis->tsWalClkStart; } } /* * Load streams. */ uint32_t cStreams; rc = pHlp->pfnSSMGetU32(pSSM, &cStreams); AssertRCReturn(rc, rc); if (cStreams > HDA_MAX_STREAMS) return pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, N_("State contains %u streams while %u is the maximum supported"), cStreams, HDA_MAX_STREAMS); Log2Func(("cStreams=%RU32\n", cStreams)); /* Load stream states. */ for (uint32_t i = 0; i < cStreams; i++) { uint8_t idStream; rc = pHlp->pfnSSMGetU8(pSSM, &idStream); AssertRCReturn(rc, rc); /* Paranoia. */ AssertLogRelMsgReturn(idStream < HDA_MAX_STREAMS, ("HDA: Saved state contains bogus stream ID %RU8 for stream #%RU8", idStream, i), VERR_SSM_INVALID_STATE); HDASTREAM StreamDummyShared; HDASTREAMR3 StreamDummyR3; PHDASTREAM pStreamShared = idStream < RT_ELEMENTS(pThis->aStreams) ? &pThis->aStreams[idStream] : &StreamDummyShared; PHDASTREAMR3 pStreamR3 = idStream < RT_ELEMENTS(pThisCC->aStreams) ? &pThisCC->aStreams[idStream] : &StreamDummyR3; AssertLogRelMsgStmt(idStream < RT_ELEMENTS(pThisCC->aStreams), ("HDA stream ID=%RU8 not supported, skipping loadingit ...\n", idStream), RT_ZERO(StreamDummyShared); RT_ZERO(StreamDummyR3)); rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, pStreamR3, idStream); if (RT_FAILURE(rc)) { LogRel(("HDA: Stream #%RU8: Setting up failed, rc=%Rrc\n", idStream, rc)); /* Continue. */ } rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State, sizeof(HDASTREAMSTATE), 0 /* fFlags */, g_aSSMStreamStateFields7, NULL); AssertRCReturn(rc, rc); /* * Load BDLEs (Buffer Descriptor List Entries) and DMA counters. * Obsolete. Derived from LPID now. */ HDABDLEDESC IgnDesc; rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnDesc, sizeof(IgnDesc), 0 /* fFlags */, g_aSSMBDLEDescFields7, NULL); AssertRCReturn(rc, rc); HDABDLESTATELEGACY IgnState; rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnState, sizeof(IgnState), 0 /* fFlags */, g_aSSMBDLEStateFields7, NULL); AssertRCReturn(rc, rc); Log2Func(("[SD%RU8]\n", pStreamShared->u8SD)); /* * Load period state. */ if (uVersion <= HDA_SAVED_STATE_WITHOUT_PERIOD) { static SSMFIELD const s_aSSMStreamPeriodFields7[] = /* For the removed HDASTREAMPERIOD structure. */ { SSMFIELD_ENTRY_OLD(u64StartWalClk, sizeof(uint64_t)), SSMFIELD_ENTRY_OLD(u64ElapsedWalClk, sizeof(uint64_t)), SSMFIELD_ENTRY_OLD(cFramesTransferred, sizeof(uint32_t)), SSMFIELD_ENTRY_OLD(cIntPending, sizeof(uint8_t)), /** @todo Not sure what we should for non-zero values on restore... ignoring it for now. */ SSMFIELD_ENTRY_TERM() }; uint8_t bWhatever = 0; rc = pHlp->pfnSSMGetStructEx(pSSM, &bWhatever, sizeof(bWhatever), 0 /* fFlags */, s_aSSMStreamPeriodFields7, NULL); AssertRCReturn(rc, rc); } /* * Load internal (FIFO) buffer. */ uint32_t cbCircBufSize = 0; pHlp->pfnSSMGetU32(pSSM, &cbCircBufSize); /* cbCircBuf */ uint32_t cbCircBufUsed = 0; rc = pHlp->pfnSSMGetU32(pSSM, &cbCircBufUsed); /* cbCircBuf */ AssertRCReturn(rc, rc); if (cbCircBufSize) /* If 0, skip the buffer. */ { /* Paranoia. */ AssertLogRelMsgReturn(cbCircBufSize <= _32M, ("HDA: Saved state contains bogus DMA buffer size (%RU32) for stream #%RU8", cbCircBufSize, idStream), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); AssertLogRelMsgReturn(cbCircBufUsed <= cbCircBufSize, ("HDA: Saved state contains invalid DMA buffer usage (%RU32/%RU32) for stream #%RU8", cbCircBufUsed, cbCircBufSize, idStream), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); /* Do we need to cre-create the circular buffer do fit the data size? */ if ( pStreamR3->State.pCircBuf && cbCircBufSize != (uint32_t)RTCircBufSize(pStreamR3->State.pCircBuf)) { RTCircBufDestroy(pStreamR3->State.pCircBuf); pStreamR3->State.pCircBuf = NULL; } rc = RTCircBufCreate(&pStreamR3->State.pCircBuf, cbCircBufSize); AssertRCReturn(rc, rc); if (cbCircBufUsed) { void *pvBuf; size_t cbBuf; RTCircBufAcquireWriteBlock(pStreamR3->State.pCircBuf, cbCircBufUsed, &pvBuf, &cbBuf); AssertLogRelMsgReturn(cbBuf == cbCircBufUsed, ("cbBuf=%zu cbCircBufUsed=%zu\n", cbBuf, cbCircBufUsed), VERR_INTERNAL_ERROR_3); rc = pHlp->pfnSSMGetMem(pSSM, pvBuf, cbBuf); AssertRCReturn(rc, rc); pStreamR3->State.offWrite = cbCircBufUsed; RTCircBufReleaseWriteBlock(pStreamR3->State.pCircBuf, cbBuf); Assert(cbBuf == cbCircBufUsed); } } Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", idStream, HDA_STREAM_REG(pThis, LPIB, idStream), HDA_STREAM_REG(pThis, CBL, idStream), HDA_STREAM_REG(pThis, LVI, idStream))); #ifdef LOG_ENABLED hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1); #endif /** @todo (Re-)initialize active periods? */ } /* for cStreams */ rc = hdaR3LoadExecTail(pDevIns, pThis, pThisCC, pSSM); AssertRC(rc); LogFlowFuncLeaveRC(rc); return rc; } /********************************************************************************************************************************* * IPRT format type handlers * *********************************************************************************************************************************/ /** * @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 Item Handlers * *********************************************************************************************************************************/ /** Worker for hdaR3DbgInfo. */ static int hdaR3DbgLookupRegByName(const char *pszArgs) { if (pszArgs && *pszArgs != '\0') for (int iReg = 0; iReg < HDA_NUM_REGS; ++iReg) if (!RTStrICmp(g_aHdaRegMap[iReg].abbrev, pszArgs)) return iReg; return -1; } /** Worker for hdaR3DbgInfo. */ static void hdaR3DbgPrintRegister(PPDMDEVINS pDevIns, PHDASTATE pThis, PCDBGFINFOHLP pHlp, int iHdaIndex) { /** @todo HDA_REG_IDX_NOMEM & GCAP both uses mem_idx zero, no flag or anything to tell them appart. */ if (g_aHdaRegMap[iHdaIndex].mem_idx != 0 || g_aHdaRegMap[iHdaIndex].pfnRead != hdaRegReadWALCLK) pHlp->pfnPrintf(pHlp, "%s: 0x%x\n", g_aHdaRegMap[iHdaIndex].abbrev, pThis->au32Regs[g_aHdaRegMap[iHdaIndex].mem_idx]); else pHlp->pfnPrintf(pHlp, "%s: 0x%RX64\n", g_aHdaRegMap[iHdaIndex].abbrev, hdaGetWallClock(pDevIns, pThis)); } /** * @callback_method_impl{FNDBGFHANDLERDEV} */ static DECLCALLBACK(void) hdaR3DbgInfo(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); int idxReg = hdaR3DbgLookupRegByName(pszArgs); if (idxReg != -1) hdaR3DbgPrintRegister(pDevIns, pThis, pHlp, idxReg); else for (idxReg = 0; idxReg < HDA_NUM_REGS; ++idxReg) hdaR3DbgPrintRegister(pDevIns, pThis, pHlp, idxReg); } /** Worker for hdaR3DbgInfoStream. */ static void hdaR3DbgPrintStream(PHDASTATE pThis, PCDBGFINFOHLP pHlp, int idxStream) { pHlp->pfnPrintf(pHlp, "Stream #%d:\n", idxStream); pHlp->pfnPrintf(pHlp, " SD%dCTL : %R[sdctl]\n", idxStream, HDA_STREAM_REG(pThis, CTL, idxStream)); pHlp->pfnPrintf(pHlp, " SD%dCTS : %R[sdsts]\n", idxStream, HDA_STREAM_REG(pThis, STS, idxStream)); pHlp->pfnPrintf(pHlp, " SD%dFIFOS: %R[sdfifos]\n", idxStream, HDA_STREAM_REG(pThis, FIFOS, idxStream)); pHlp->pfnPrintf(pHlp, " SD%dFIFOW: %R[sdfifow]\n", idxStream, HDA_STREAM_REG(pThis, FIFOW, idxStream)); PHDASTREAM const pStream = &pThis->aStreams[idxStream]; pHlp->pfnPrintf(pHlp, " Current BDLE%02u: %s%#RX64 LB %#x%s - off=%#x\n", pStream->State.idxCurBdle, "%%" /*vboxdbg phys prefix*/, pStream->State.aBdl[pStream->State.idxCurBdle].GCPhys, pStream->State.aBdl[pStream->State.idxCurBdle].cb, pStream->State.aBdl[pStream->State.idxCurBdle].fFlags ? " IOC" : "", pStream->State.offCurBdle); } /** Worker for hdaR3DbgInfoBDL. */ static void hdaR3DbgPrintBDL(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, PCDBGFINFOHLP pHlp, int idxStream) { const PHDASTREAM pStream = &pThis->aStreams[idxStream]; const PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[idxStream]; PCPDMAUDIOPCMPROPS pGuestProps = &pStreamR3->State.Mapping.GuestProps; uint64_t const u64BaseDMA = RT_MAKE_U64(HDA_STREAM_REG(pThis, BDPL, idxStream), HDA_STREAM_REG(pThis, BDPU, idxStream)); uint16_t const u16LVI = HDA_STREAM_REG(pThis, LVI, idxStream); uint32_t const u32CBL = HDA_STREAM_REG(pThis, CBL, idxStream); uint8_t const idxCurBdle = pStream->State.idxCurBdle; pHlp->pfnPrintf(pHlp, "Stream #%d BDL: %s%#011RX64 LB %#x (LVI=%u)\n", idxStream, "%%" /*vboxdbg phys prefix*/, u64BaseDMA, u16LVI * sizeof(HDABDLEDESC), u16LVI); if (u64BaseDMA || idxCurBdle != 0 || pStream->State.aBdl[idxCurBdle].GCPhys != 0 || pStream->State.aBdl[idxCurBdle].cb != 0) pHlp->pfnPrintf(pHlp, " Current: BDLE%03u: %s%#011RX64 LB %#x%s - off=%#x LPIB=%#RX32\n", pStream->State.idxCurBdle, "%%" /*vboxdbg phys prefix*/, pStream->State.aBdl[idxCurBdle].GCPhys, pStream->State.aBdl[idxCurBdle].cb, pStream->State.aBdl[idxCurBdle].fFlags ? " IOC" : "", pStream->State.offCurBdle, HDA_STREAM_REG(pThis, LPIB, idxStream)); if (!u64BaseDMA) return; /* * The BDL: */ uint64_t cbTotal = 0; for (uint16_t i = 0; i < u16LVI + 1; i++) { HDABDLEDESC bd = {0, 0, 0}; PDMDevHlpPCIPhysRead(pDevIns, u64BaseDMA + i * sizeof(HDABDLEDESC), &bd, sizeof(bd)); char szFlags[64]; szFlags[0] = '\0'; if (bd.fFlags & ~HDA_BDLE_F_IOC) RTStrPrintf(szFlags, sizeof(szFlags), " !!fFlags=%#x!!\n", bd.fFlags); pHlp->pfnPrintf(pHlp, " %sBDLE%03u: %s%#011RX64 LB %#06x (%RU64 us) %s%s\n", idxCurBdle == i ? "=>" : " ", i, "%%", bd.u64BufAddr, bd.u32BufSize, PDMAudioPropsBytesToMicro(pGuestProps, bd.u32BufSize), bd.fFlags & HDA_BDLE_F_IOC ? " IOC=1" : "", szFlags); if (memcmp(&bd, &pStream->State.aBdl[i], sizeof(bd)) != 0) { szFlags[0] = '\0'; if (bd.fFlags & ~HDA_BDLE_F_IOC) RTStrPrintf(szFlags, sizeof(szFlags), " !!fFlags=%#x!!\n", bd.fFlags); pHlp->pfnPrintf(pHlp, " !!!loaded: %s%#011RX64 LB %#06x %s%s\n", "%%", pStream->State.aBdl[i].GCPhys, pStream->State.aBdl[i].cb, pStream->State.aBdl[i].fFlags & HDA_BDLE_F_IOC ? " IOC=1" : "", szFlags); } cbTotal += bd.u32BufSize; } pHlp->pfnPrintf(pHlp, " Total: %#RX64 bytes (%RU64), %u ms\n", cbTotal, cbTotal, PDMAudioPropsBytesToMilli(&pStreamR3->State.Mapping.GuestProps, (uint32_t)cbTotal)); if (cbTotal != u32CBL) pHlp->pfnPrintf(pHlp, " Warning: %#RX64 bytes does not match CBL (%#RX64)!\n", cbTotal, u32CBL); /* * The scheduling plan. */ uint16_t const idxSchedule = pStream->State.idxSchedule; pHlp->pfnPrintf(pHlp, " Scheduling: %u items, %u prologue. Current: %u, loop %u.\n", pStream->State.cSchedule, pStream->State.cSchedulePrologue, idxSchedule, pStream->State.idxScheduleLoop); for (uint16_t i = 0; i < pStream->State.cSchedule; i++) pHlp->pfnPrintf(pHlp, " %s#%02u: %#x bytes, %u loop%s, %RU32 ticks. BDLE%u thru BDLE%u\n", i == idxSchedule ? "=>" : " ", i, pStream->State.aSchedule[i].cbPeriod, pStream->State.aSchedule[i].cLoops, pStream->State.aSchedule[i].cLoops == 1 ? "" : "s", pStream->State.aSchedule[i].cPeriodTicks, pStream->State.aSchedule[i].idxFirst, pStream->State.aSchedule[i].idxFirst + pStream->State.aSchedule[i].cEntries - 1); } /** Used by hdaR3DbgInfoStream and hdaR3DbgInfoBDL. */ static int hdaR3DbgLookupStrmIdx(PCDBGFINFOHLP pHlp, const char *pszArgs) { if (pszArgs && *pszArgs) { int32_t idxStream; int rc = RTStrToInt32Full(pszArgs, 0, &idxStream); if (RT_SUCCESS(rc) && idxStream >= -1 && idxStream < HDA_MAX_STREAMS) return idxStream; pHlp->pfnPrintf(pHlp, "Argument '%s' is not a valid stream number!\n", pszArgs); } return -1; } /** * @callback_method_impl{FNDBGFHANDLERDEV, hdastream} */ static DECLCALLBACK(void) hdaR3DbgInfoStream(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); int idxStream = hdaR3DbgLookupStrmIdx(pHlp, pszArgs); if (idxStream != -1) hdaR3DbgPrintStream(pThis, pHlp, idxStream); else for (idxStream = 0; idxStream < HDA_MAX_STREAMS; ++idxStream) hdaR3DbgPrintStream(pThis, pHlp, idxStream); } /** * @callback_method_impl{FNDBGFHANDLERDEV, hdabdle} */ static DECLCALLBACK(void) hdaR3DbgInfoBDL(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); int idxStream = hdaR3DbgLookupStrmIdx(pHlp, pszArgs); if (idxStream != -1) hdaR3DbgPrintBDL(pDevIns, pThis, pThisCC, pHlp, idxStream); else { for (idxStream = 0; idxStream < HDA_MAX_STREAMS; ++idxStream) hdaR3DbgPrintBDL(pDevIns, pThis, pThisCC, pHlp, idxStream); idxStream = -1; } /* * DMA stream positions: */ uint64_t const uDPBase = pThis->u64DPBase & DPBASE_ADDR_MASK; pHlp->pfnPrintf(pHlp, "DMA counters %#011RX64 LB %#x, %s:\n", uDPBase, HDA_MAX_STREAMS * 2 * sizeof(uint32_t), pThis->fDMAPosition ? "enabled" : "disabled"); if (uDPBase) { struct { uint32_t off, uReserved; } aPositions[HDA_MAX_STREAMS]; RT_ZERO(aPositions); PDMDevHlpPCIPhysRead(pDevIns, uDPBase , &aPositions[0], sizeof(aPositions)); for (unsigned i = 0; i < RT_ELEMENTS(aPositions); i++) if (idxStream == -1 || i == (unsigned)idxStream) /* lazy bird */ { char szReserved[64]; szReserved[0] = '\0'; if (aPositions[i].uReserved != 0) RTStrPrintf(szReserved, sizeof(szReserved), " reserved=%#x", aPositions[i].uReserved); pHlp->pfnPrintf(pHlp, " Stream #%u DMA @ %#x%s\n", i, aPositions[i].off, szReserved); } } } /** * @callback_method_impl{FNDBGFHANDLERDEV, hdcnodes} */ static DECLCALLBACK(void) hdaR3DbgInfoCodecNodes(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); if (pThisCC->pCodec->pfnDbgListNodes) pThisCC->pCodec->pfnDbgListNodes(&pThis->Codec, pThisCC->pCodec, pHlp, pszArgs); else pHlp->pfnPrintf(pHlp, "Codec implementation doesn't provide corresponding callback\n"); } /** * @callback_method_impl{FNDBGFHANDLERDEV, hdcselector} */ static DECLCALLBACK(void) hdaR3DbgInfoCodecSelector(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); if (pThisCC->pCodec->pfnDbgSelector) pThisCC->pCodec->pfnDbgSelector(&pThis->Codec, pThisCC->pCodec, pHlp, pszArgs); else pHlp->pfnPrintf(pHlp, "Codec implementation doesn't provide corresponding callback\n"); } /** * @callback_method_impl{FNDBGFHANDLERDEV, hdamixer} */ static DECLCALLBACK(void) hdaR3DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) { PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); if (pThisCC->pMixer) AudioMixerDebug(pThisCC->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) { PHDASTATER3 pThisCC = RT_FROM_MEMBER(pInterface, HDASTATER3, IBase); PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase); return NULL; } /********************************************************************************************************************************* * PDMDEVREGR3 * *********************************************************************************************************************************/ /** * 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 pDevIns The device instance. * @param pThis The shared HDA device state. * @param pThisCC The ring-3 HDA device 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(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, 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(pDevIns, uLUN, &pThisCC->IBase, &pDrvBase, pszDesc); if (RT_SUCCESS(rc)) { PHDADRIVER pDrv = (PHDADRIVER)RTMemAllocZ(sizeof(HDADRIVER)); if (pDrv) { pDrv->pDrvBase = pDrvBase; pDrv->pHDAStateShared = pThis; pDrv->pHDAStateR3 = pThisCC; pDrv->uLUN = uLUN; 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)); /* * 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(&pThisCC->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 pThisCC The ring-3 HDA device state. * @param pDrv Driver to detach from device. * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines. */ static int hdaR3DetachInternal(PHDASTATER3 pThisCC, 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(pThisCC, pDrv); /* Next, search backwards for a capable (attached) driver which now will be the * new recording source. */ PHDADRIVER pDrvCur; RTListForEachReverse(&pThisCC->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(pThisCC->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(pThisCC->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 = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); DEVHDA_LOCK_RETURN(pDevIns, pThis, VERR_IGNORED); LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags)); PHDADRIVER pDrv; int rc2 = hdaR3AttachInternal(pDevIns, pThis, pThisCC, uLUN, fFlags, &pDrv); if (RT_SUCCESS(rc2)) rc2 = hdaR3MixerAddDrv(pThisCC, pDrv); if (RT_FAILURE(rc2)) LogFunc(("Failed with %Rrc\n", rc2)); DEVHDA_UNLOCK(pDevIns, pThis); return VINF_SUCCESS; } /** * @interface_method_impl{PDMDEVREG,pfnDetach} */ static DECLCALLBACK(void) hdaR3Detach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); DEVHDA_LOCK(pDevIns, pThis); LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags)); PHDADRIVER pDrv, pDrvNext; RTListForEachSafe(&pThisCC->lstDrv, pDrv, pDrvNext, HDADRIVER, Node) { if (pDrv->uLUN == uLUN) { int rc2 = hdaR3DetachInternal(pThisCC, pDrv, fFlags); if (RT_SUCCESS(rc2)) { RTMemFree(pDrv); pDrv = NULL; } break; } } DEVHDA_UNLOCK(pDevIns, pThis); } /** * Powers off the device. * * @param pDevIns Device instance to power off. */ static DECLCALLBACK(void) hdaR3PowerOff(PPDMDEVINS pDevIns) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); DEVHDA_LOCK_RETURN_VOID(pDevIns, pThis); LogRel2(("HDA: Powering off ...\n")); /* Ditto goes for the codec, which in turn uses the mixer. */ hdaR3CodecPowerOff(pThisCC->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 (pThisCC->pMixer) { AudioMixerDestroy(pThisCC->pMixer); pThisCC->pMixer = NULL; } DEVHDA_UNLOCK(pDevIns, pThis); } /** * Replaces a driver with a the NullAudio drivers. * * @returns VBox status code. * @param pDevIns The device instance. * @param pThis The shared HDA device state. * @param pThisCC The ring-3 HDA device state. * @param iLun The logical unit which is being replaced. */ static int hdaR3ReconfigLunWithNullAudio(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, unsigned iLun) { int rc = PDMDevHlpDriverReconfigure2(pDevIns, iLun, "AUDIO", "NullAudio"); if (RT_SUCCESS(rc)) rc = hdaR3AttachInternal(pDevIns, pThis, pThisCC, iLun, 0 /* fFlags */, NULL /* ppDrv */); LogFunc(("pThis=%p, iLun=%u, rc=%Rrc\n", pThis, iLun, rc)); return rc; } /** * @interface_method_impl{PDMDEVREG,pfnReset} */ static DECLCALLBACK(void) hdaR3Reset(PPDMDEVINS pDevIns) { PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); LogFlowFuncEnter(); DEVHDA_LOCK_RETURN_VOID(pDevIns, 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(pDevIns, pThis, pThisCC); /* 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(pDevIns, pThis); } /** * @interface_method_impl{PDMDEVREG,pfnDestruct} */ static DECLCALLBACK(int) hdaR3Destruct(PPDMDEVINS pDevIns) { PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); DEVHDA_LOCK(pDevIns, pThis); /** @todo r=bird: this will fail on early constructor failure. */ PHDADRIVER pDrv; while (!RTListIsEmpty(&pThisCC->lstDrv)) { pDrv = RTListGetFirst(&pThisCC->lstDrv, HDADRIVER, Node); RTListNodeRemove(&pDrv->Node); RTMemFree(pDrv); } if (pThisCC->pCodec) { RTMemFree(pThisCC->pCodec); pThisCC->pCodec = NULL; } hdaCodecDestruct(&pThis->Codec); for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++) hdaR3StreamDestroy(&pThis->aStreams[i], &pThisCC->aStreams[i]); DEVHDA_UNLOCK(pDevIns, 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 = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3); PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; Assert(iInstance == 0); RT_NOREF(iInstance); /* * Initialize the state sufficently to make the destructor work. */ pThis->uAlignmentCheckMagic = HDASTATE_ALIGNMENT_CHECK_MAGIC; RTListInit(&pThisCC->lstDrv); pThis->cbCorbBuf = HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE; pThis->cbRirbBuf = HDA_RIRB_SIZE * HDA_RIRB_ELEMENT_SIZE; /** @todo r=bird: There are probably other things which should be * initialized here before we start failing. */ /* * Validate and read configuration. */ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "BufSizeInMs" "|BufSizeOutMs" "|InitialDelayMs" "|TimerHz" "|PosAdjustEnabled" "|PosAdjustFrames" "|TransferHeuristicsEnabled" "|DebugEnabled" "|DebugPathOut", ""); /* Note: Error checking of this value happens in hdaR3StreamSetUp(). */ int rc = pHlp->pfnCFGMQueryU16Def(pCfg, "BufSizeInMs", &pThis->cbCircBufInMs, 0 /* Default value, if not set. */); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("HDA configuration error: failed to read input buffer size (ms) as unsigned integer")); /* Note: Error checking of this value happens in hdaR3StreamSetUp(). */ rc = pHlp->pfnCFGMQueryU16Def(pCfg, "BufSizeOutMs", &pThis->cbCircBufOutMs, 0 /* Default value, if not set. */); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("HDA configuration error: failed to read output buffer size (ms) as unsigned integer")); rc = pHlp->pfnCFGMQueryU16Def(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)); /** @devcfgm{hda,InitialDelayMs,uint16_t,0,256,12,ms} * How long to delay when a stream starts before engaging the asynchronous I/O * thread from the DMA timer callback. Because it's used from the DMA timer * callback, it will implicitly be rounded up to the next timer period. * This is for adding a little host scheduling leeway into the playback. */ rc = pHlp->pfnCFGMQueryU16Def(pCfg, "InitialDelayMs", &pThis->msInitialDelay, 12); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("HDA configuration error: failed to read 'InitialDelayMs' as uint16_t")); if (pThis->msInitialDelay > 256) return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("HDA configuration error: Out of range: 0 <= InitialDelayMs < 256: %u"), pThis->msInitialDelay); rc = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryU16Def(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 = pHlp->pfnCFGMQueryBoolDef(pCfg, "TransferHeuristicsEnabled", &pThis->fTransferHeuristicsEnabled, true); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("HDA configuration error: failed to read data transfer heuristics enabled as boolean")); if (!pThis->fTransferHeuristicsEnabled) LogRel(("HDA: Data transfer heuristics are disabled\n")); rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThisCC->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 = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "DebugPathOut", &pThisCC->Dbg.pszOutPath, NULL); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("HDA configuration error: failed to read debugging output path flag as string")); if (pThisCC->Dbg.fEnabled) LogRel2(("HDA: Debug output will be saved to '%s'\n", pThisCC->Dbg.pszOutPath)); /* * Use our 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). */ pThisCC->pDevIns = pDevIns; /* IBase */ pThisCC->IBase.pfnQueryInterface = hdaR3QueryInterface; /* PCI Device */ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0]; PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev); PDMPciDevSetVendorId( pPciDev, HDA_PCI_VENDOR_ID); /* nVidia */ PDMPciDevSetDeviceId( pPciDev, HDA_PCI_DEVICE_ID); /* HDA */ PDMPciDevSetCommand( pPciDev, 0x0000); /* 04 rw,ro - pcicmd. */ PDMPciDevSetStatus( pPciDev, VBOX_PCI_STATUS_CAP_LIST); /* 06 rwc?,ro? - pcists. */ PDMPciDevSetRevisionId( pPciDev, 0x01); /* 08 ro - rid. */ PDMPciDevSetClassProg( pPciDev, 0x00); /* 09 ro - pi. */ PDMPciDevSetClassSub( pPciDev, 0x03); /* 0a ro - scc; 03 == HDA. */ PDMPciDevSetClassBase( pPciDev, 0x04); /* 0b ro - bcc; 04 == multimedia. */ PDMPciDevSetHeaderType( pPciDev, 0x00); /* 0e ro - headtyp. */ PDMPciDevSetBaseAddress( pPciDev, 0, /* 10 rw - MMIO */ false /* fIoSpace */, false /* fPrefetchable */, true /* f64Bit */, 0x00000000); PDMPciDevSetInterruptLine( pPciDev, 0x00); /* 3c rw. */ PDMPciDevSetInterruptPin( pPciDev, 0x01); /* 3d ro - INTA#. */ # if defined(HDA_AS_PCI_EXPRESS) PDMPciDevSetCapabilityList(pPciDev, 0x80); # elif defined(VBOX_WITH_MSI_DEVICES) PDMPciDevSetCapabilityList(pPciDev, 0x60); # else PDMPciDevSetCapabilityList(pPciDev, 0x50); /* ICH6 datasheet 18.1.16 */ # endif /// @todo r=michaln: If there are really no PDMPciDevSetXx 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 */ PDMPciDevSetByte( pPciDev, 0x40, 0x01); /* Power Management */ PDMPciDevSetByte( pPciDev, 0x50 + 0, VBOX_PCI_CAP_ID_PM); PDMPciDevSetByte( pPciDev, 0x50 + 1, 0x0); /* next */ PDMPciDevSetWord( pPciDev, 0x50 + 2, VBOX_PCI_PM_CAP_DSI | 0x02 /* version, PM1.1 */ ); # ifdef HDA_AS_PCI_EXPRESS /* PCI Express */ PDMPciDevSetByte( pPciDev, 0x80 + 0, VBOX_PCI_CAP_ID_EXP); /* PCI_Express */ PDMPciDevSetByte( pPciDev, 0x80 + 1, 0x60); /* next */ /* Device flags */ PDMPciDevSetWord( pPciDev, 0x80 + 2, 1 /* version */ | (VBOX_PCI_EXP_TYPE_ROOT_INT_EP << 4) /* Root Complex Integrated Endpoint */ | (100 << 9) /* MSI */ ); /* Device capabilities */ PDMPciDevSetDWord( pPciDev, 0x80 + 4, VBOX_PCI_EXP_DEVCAP_FLRESET); /* Device control */ PDMPciDevSetWord( pPciDev, 0x80 + 8, 0); /* Device status */ PDMPciDevSetWord( pPciDev, 0x80 + 10, 0); /* Link caps */ PDMPciDevSetDWord( pPciDev, 0x80 + 12, 0); /* Link control */ PDMPciDevSetWord( pPciDev, 0x80 + 16, 0); /* Link status */ PDMPciDevSetWord( pPciDev, 0x80 + 18, 0); /* Slot capabilities */ PDMPciDevSetDWord( pPciDev, 0x80 + 20, 0); /* Slot control */ PDMPciDevSetWord( pPciDev, 0x80 + 24, 0); /* Slot status */ PDMPciDevSetWord( pPciDev, 0x80 + 26, 0); /* Root control */ PDMPciDevSetWord( pPciDev, 0x80 + 28, 0); /* Root capabilities */ PDMPciDevSetWord( pPciDev, 0x80 + 30, 0); /* Root status */ PDMPciDevSetDWord( pPciDev, 0x80 + 32, 0); /* Device capabilities 2 */ PDMPciDevSetDWord( pPciDev, 0x80 + 36, 0); /* Device control 2 */ PDMPciDevSetQWord( pPciDev, 0x80 + 40, 0); /* Link control 2 */ PDMPciDevSetQWord( pPciDev, 0x80 + 48, 0); /* Slot control 2 */ PDMPciDevSetWord( pPciDev, 0x80 + 56, 0); # endif /* HDA_AS_PCI_EXPRESS */ /* * Register the PCI device. */ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev); AssertRCReturn(rc, rc); /** @todo r=bird: The IOMMMIO_FLAGS_READ_DWORD flag isn't entirely optimal, * as several frequently used registers aren't dword sized. 6.0 and earlier * will go to ring-3 to handle accesses to any such register, where-as 6.1 and * later will do trivial register reads in ring-0. Real optimal code would use * IOMMMIO_FLAGS_READ_PASSTHRU and do the necessary extra work to deal with * anything the guest may throw at us. */ rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0, 0x4000, PCI_ADDRESS_SPACE_MEM, hdaMmioWrite, hdaMmioRead, NULL /*pvUser*/, IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_PASSTHRU, "HDA", &pThis->hMmio); AssertRCReturn(rc, 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 */ PDMPciDevSetCapabilityList(pPciDev, 0x50); } # endif rc = PDMDevHlpSSMRegister(pDevIns, HDA_SAVED_STATE_VERSION, sizeof(*pThis), hdaR3SaveExec, hdaR3LoadExec); AssertRCReturn(rc, rc); # ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO LogRel(("HDA: Asynchronous I/O enabled\n")); # endif /* * Attach drivers. We ASSUME they are configured consecutively without any * gaps, so we stop when we hit the first LUN w/o a driver configured. */ for (unsigned iLun = 0; ; iLun++) { AssertBreak(iLun < UINT8_MAX); LogFunc(("Trying to attach driver for LUN#%u ...\n", iLun)); rc = hdaR3AttachInternal(pDevIns, pThis, pThisCC, iLun, 0 /* fFlags */, NULL /* ppDrv */); if (rc == VERR_PDM_NO_ATTACHED_DRIVER) { LogFunc(("cLUNs=%u\n", iLun)); break; } if (rc == VERR_AUDIO_BACKEND_INIT_FAILED) { hdaR3ReconfigLunWithNullAudio(pDevIns, pThis, pThisCC, iLun); /* Pretend attaching to the NULL audio backend will never fail. */ 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")); } else AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc); } /* * Create the mixer. */ uint32_t fMixer = AUDMIXER_FLAGS_NONE; if (pThisCC->Dbg.fEnabled) fMixer |= AUDMIXER_FLAGS_DEBUG; rc = AudioMixerCreate("HDA Mixer", fMixer, &pThisCC->pMixer); AssertRCReturn(rc, rc); /* * Add mixer output sinks. */ # ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND rc = AudioMixerCreateSink(pThisCC->pMixer, "[Playback] Front", AUDMIXSINKDIR_OUTPUT, &pThisCC->SinkFront.pMixSink); AssertRCReturn(rc, rc); rc = AudioMixerCreateSink(pThisCC->pMixer, "[Playback] Center / Subwoofer", AUDMIXSINKDIR_OUTPUT, &pThisCC->SinkCenterLFE.pMixSink); AssertRCReturn(rc, rc); rc = AudioMixerCreateSink(pThisCC->pMixer, "[Playback] Rear", AUDMIXSINKDIR_OUTPUT, &pThisCC->SinkRear.pMixSink); AssertRCReturn(rc, rc); # else rc = AudioMixerCreateSink(pThisCC->pMixer, "[Playback] PCM Output", AUDMIXSINKDIR_OUTPUT, &pThisCC->SinkFront.pMixSink); AssertRCReturn(rc, rc); # endif /* VBOX_WITH_AUDIO_HDA_51_SURROUND */ /* * Add mixer input sinks. */ rc = AudioMixerCreateSink(pThisCC->pMixer, "[Recording] Line In", AUDMIXSINKDIR_INPUT, &pThisCC->SinkLineIn.pMixSink); AssertRCReturn(rc, rc); # ifdef VBOX_WITH_AUDIO_HDA_MIC_IN rc = AudioMixerCreateSink(pThisCC->pMixer, "[Recording] Microphone In", AUDMIXSINKDIR_INPUT, &pThisCC->SinkMicIn.pMixSink); AssertRCReturn(rc, rc); # endif /* There is no master volume control. Set the master to max. */ PDMAUDIOVOLUME vol = { false, 255, 255 }; rc = AudioMixerSetMasterVolume(pThisCC->pMixer, &vol); AssertRCReturn(rc, rc); /* Allocate codec. */ PHDACODECR3 pCodecR3 = (PHDACODECR3)RTMemAllocZ(sizeof(HDACODECR3)); AssertPtrReturn(pCodecR3, VERR_NO_MEMORY); /* Set codec callbacks to this controller. */ pCodecR3->pDevIns = pDevIns; pCodecR3->pfnCbMixerAddStream = hdaR3MixerAddStream; pCodecR3->pfnCbMixerRemoveStream = hdaR3MixerRemoveStream; pCodecR3->pfnCbMixerControl = hdaR3MixerControl; pCodecR3->pfnCbMixerSetVolume = hdaR3MixerSetVolume; /* Construct the common + R3 codec part. */ rc = hdaR3CodecConstruct(pDevIns, &pThis->Codec, pCodecR3, 0 /* Codec index */, pCfg); AssertRCReturn(rc, rc); pThisCC->pCodec = pCodecR3; /* 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->Codec.u16VendorId); Assert(pThis->Codec.u16DeviceId); PDMPciDevSetSubSystemVendorId(pPciDev, pThis->Codec.u16VendorId); /* 2c ro - intel.) */ PDMPciDevSetSubSystemId( pPciDev, pThis->Codec.u16DeviceId); /* 2e ro. */ /* * Create the per stream timers and the asso. * * We must the critical section for the timers as the device has a * noop section associated with it. * * 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. */ /** @todo r=bird: The need to use virtual sync is perhaps because TM * doesn't schedule regular TMCLOCK_VIRTUAL timers as accurately as it * should (VT-x preemption timer, etc). Hope to address that before * long. @bugref{9943}. */ 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 (size_t i = 0; i < HDA_MAX_STREAMS; i++) { /* We need the first timer in ring-0 to calculate the wall clock (WALCLK) time. */ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, hdaR3Timer, (void *)(uintptr_t)i, TMTIMER_FLAGS_NO_CRIT_SECT | (i == 0 ? TMTIMER_FLAGS_RING0 : TMTIMER_FLAGS_NO_RING0), s_apszNames[i], &pThis->aStreams[i].hTimer); AssertRCReturn(rc, rc); rc = PDMDevHlpTimerSetCritSect(pDevIns, pThis->aStreams[i].hTimer, &pThis->CritSect); AssertRCReturn(rc, rc); } /* * Create all hardware streams. */ for (uint8_t i = 0; i < HDA_MAX_STREAMS; ++i) { rc = hdaR3StreamConstruct(&pThis->aStreams[i], &pThisCC->aStreams[i], pThis, pThisCC, i /* u8SD */); AssertRCReturn(rc, rc); } # ifdef VBOX_WITH_AUDIO_HDA_ONETIME_INIT /* * Initialize the driver chain. */ PHDADRIVER pDrv; RTListForEach(&pThisCC->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); hdaR3ReconfigLunWithNullAudio(pThis, pDrv->uLUN); 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 */ hdaR3Reset(pDevIns); /* * Info items and string formatter types. The latter is non-optional as * the info handles use (at least some of) the custom types and we cannot * accept screwing formatting. */ PDMDevHlpDBGFInfoRegister(pDevIns, "hda", "HDA registers. (hda [register case-insensitive])", hdaR3DbgInfo); PDMDevHlpDBGFInfoRegister(pDevIns, "hdabdl", "HDA buffer descriptor list (BDL) and DMA stream positions. (hdabdl [stream number])", hdaR3DbgInfoBDL); 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("sdctl", hdaR3StrFmtSDCTL, NULL); AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); rc = RTStrFormatTypeRegister("sdsts", hdaR3StrFmtSDSTS, NULL); AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); /** @todo the next two are rather pointless. */ rc = RTStrFormatTypeRegister("sdfifos", hdaR3StrFmtSDFIFOS, NULL); AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); rc = RTStrFormatTypeRegister("sdfifow", hdaR3StrFmtSDFIFOW, NULL); AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc); /* * Asserting sanity. */ 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 /* * Register statistics. */ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIn, STAMTYPE_PROFILE, "Input", STAMUNIT_TICKS_PER_CALL, "Profiling input."); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatOut, STAMTYPE_PROFILE, "Output", STAMUNIT_TICKS_PER_CALL, "Profiling output."); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "BytesRead" , STAMUNIT_BYTES, "Bytes read from HDA emulation."); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, "BytesWritten", STAMUNIT_BYTES, "Bytes written to HDA emulation."); AssertCompile(RT_ELEMENTS(g_aHdaRegMap) == HDA_NUM_REGS); AssertCompile(RT_ELEMENTS(pThis->aStatRegReads) == HDA_NUM_REGS); AssertCompile(RT_ELEMENTS(pThis->aStatRegReadsToR3) == HDA_NUM_REGS); AssertCompile(RT_ELEMENTS(pThis->aStatRegWrites) == HDA_NUM_REGS); AssertCompile(RT_ELEMENTS(pThis->aStatRegWritesToR3) == HDA_NUM_REGS); for (size_t i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++) { PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegReads[i], STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, g_aHdaRegMap[i].desc, "Regs/%03x-%s-Reads", g_aHdaRegMap[i].offset, g_aHdaRegMap[i].abbrev); PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegReadsToR3[i], STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, g_aHdaRegMap[i].desc, "Regs/%03x-%s-Reads-ToR3", g_aHdaRegMap[i].offset, g_aHdaRegMap[i].abbrev); PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegWrites[i], STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, g_aHdaRegMap[i].desc, "Regs/%03x-%s-Writes", g_aHdaRegMap[i].offset, g_aHdaRegMap[i].abbrev); PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegWritesToR3[i], STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, g_aHdaRegMap[i].desc, "Regs/%03x-%s-Writes-ToR3", g_aHdaRegMap[i].offset, g_aHdaRegMap[i].abbrev); } PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiReadsR3, STAMTYPE_COUNTER, "RegMultiReadsR3", STAMUNIT_OCCURENCES, "Register read not targeting just one register, handled in ring-3"); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiReadsRZ, STAMTYPE_COUNTER, "RegMultiReadsRZ", STAMUNIT_OCCURENCES, "Register read not targeting just one register, handled in ring-0"); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiWritesR3, STAMTYPE_COUNTER, "RegMultiWritesR3", STAMUNIT_OCCURENCES, "Register writes not targeting just one register, handled in ring-3"); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiWritesRZ, STAMTYPE_COUNTER, "RegMultiWritesRZ", STAMUNIT_OCCURENCES, "Register writes not targeting just one register, handled in ring-0"); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegSubWriteR3, STAMTYPE_COUNTER, "RegSubWritesR3", STAMUNIT_OCCURENCES, "Trucated register writes, handled in ring-3"); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegSubWriteRZ, STAMTYPE_COUNTER, "RegSubWritesRZ", STAMUNIT_OCCURENCES, "Trucated register writes, handled in ring-0"); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegUnknownReads, STAMTYPE_COUNTER, "RegUnknownReads", STAMUNIT_OCCURENCES, "Reads of unknown registers."); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegUnknownWrites, STAMTYPE_COUNTER, "RegUnknownWrites", STAMUNIT_OCCURENCES, "Writes to unknown registers."); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegWritesBlockedByReset, STAMTYPE_COUNTER, "RegWritesBlockedByReset", STAMUNIT_OCCURENCES, "Writes blocked by pending reset (GCTL/CRST)"); PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegWritesBlockedByRun, STAMTYPE_COUNTER, "RegWritesBlockedByRun", STAMUNIT_OCCURENCES, "Writes blocked by byte RUN bit."); # endif for (uint8_t idxStream = 0; idxStream < RT_ELEMENTS(pThisCC->aStreams); idxStream++) { PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowProblems, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, "Number of internal DMA buffer problems.", "Stream%u/DMABufferProblems", idxStream); if (hdaGetDirFromSD(idxStream) == PDMAUDIODIR_OUT) PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrors, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, "Number of internal DMA buffer overflows.", "Stream%u/DMABufferOverflows", idxStream); else { PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrors, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES, "Number of internal DMA buffer underuns.", "Stream%u/DMABufferUnderruns", idxStream); PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrorBytes, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES, "Number of bytes of silence added to cope with underruns.", "Stream%u/DMABufferSilence", idxStream); } PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.offRead, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES, "Virtual internal buffer read position.", "Stream%u/offRead", idxStream); PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.offWrite, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES, "Virtual internal buffer write position.", "Stream%u/offWrite", idxStream); PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.cbTransferSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES, "Bytes transfered per DMA timer callout.", "Stream%u/cbTransferSize", idxStream); PDMDevHlpSTAMRegisterF(pDevIns, (void*)&pThis->aStreams[idxStream].State.fRunning, STAMTYPE_BOOL, STAMVISIBILITY_USED, STAMUNIT_BYTES, "True if the stream is in RUN mode.", "Stream%u/fRunning", idxStream); PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.Cfg.Props.uHz, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES, "The stream frequency.", "Stream%u/Cfg/Hz", idxStream); PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.Cfg.Props.cChannels, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES, "The number of channels.", "Stream%u/Cfg/Channels-Host", idxStream); PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.Mapping.GuestProps.cChannels, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES, "The number of channels.", "Stream%u/Cfg/Channels-Guest", idxStream); PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.Cfg.Props.cbSample, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES, "The size of a sample (per channel).", "Stream%u/Cfg/cbSample", idxStream); } return VINF_SUCCESS; } #else /* !IN_RING3 */ /** * @callback_method_impl{PDMDEVREGR0,pfnConstruct} */ static DECLCALLBACK(int) hdaRZConstruct(PPDMDEVINS pDevIns) { PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE); PHDASTATER0 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER0); int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns)); AssertRCReturn(rc, rc); rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, hdaMmioWrite, hdaMmioRead, NULL /*pvUser*/); AssertRCReturn(rc, rc); /* Construct the R0 codec part. */ rc = hdaR0CodecConstruct(pDevIns, &pThis->Codec, &pThisCC->Codec); AssertRCReturn(rc, rc); return VINF_SUCCESS; } #endif /* !IN_RING3 */ /** * The device registration structure. */ const PDMDEVREG g_DeviceHDA = { /* .u32Version = */ PDM_DEVREG_VERSION, /* .uReserved0 = */ 0, /* .szName = */ "hda", /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE, /* .fClass = */ PDM_DEVREG_CLASS_AUDIO, /* .cMaxInstances = */ 1, /* .uSharedVersion = */ 42, /* .cbInstanceShared = */ sizeof(HDASTATE), /* .cbInstanceCC = */ CTX_EXPR(sizeof(HDASTATER3), sizeof(HDASTATER0), 0), /* .cbInstanceRC = */ 0, /* .cMaxPciDevices = */ 1, /* .cMaxMsixVectors = */ 0, /* .pszDescription = */ "Intel HD Audio Controller", #if defined(IN_RING3) /* .pszRCMod = */ "VBoxDDRC.rc", /* .pszR0Mod = */ "VBoxDDR0.r0", /* .pfnConstruct = */ hdaR3Construct, /* .pfnDestruct = */ hdaR3Destruct, /* .pfnRelocate = */ NULL, /* .pfnMemSetup = */ NULL, /* .pfnPowerOn = */ NULL, /* .pfnReset = */ hdaR3Reset, /* .pfnSuspend = */ NULL, /* .pfnResume = */ NULL, /* .pfnAttach = */ hdaR3Attach, /* .pfnDetach = */ hdaR3Detach, /* .pfnQueryInterface = */ NULL, /* .pfnInitComplete = */ NULL, /* .pfnPowerOff = */ hdaR3PowerOff, /* .pfnSoftReset = */ NULL, /* .pfnReserved0 = */ NULL, /* .pfnReserved1 = */ NULL, /* .pfnReserved2 = */ NULL, /* .pfnReserved3 = */ NULL, /* .pfnReserved4 = */ NULL, /* .pfnReserved5 = */ NULL, /* .pfnReserved6 = */ NULL, /* .pfnReserved7 = */ NULL, #elif defined(IN_RING0) /* .pfnEarlyConstruct = */ NULL, /* .pfnConstruct = */ hdaRZConstruct, /* .pfnDestruct = */ NULL, /* .pfnFinalDestruct = */ NULL, /* .pfnRequest = */ NULL, /* .pfnReserved0 = */ NULL, /* .pfnReserved1 = */ NULL, /* .pfnReserved2 = */ NULL, /* .pfnReserved3 = */ NULL, /* .pfnReserved4 = */ NULL, /* .pfnReserved5 = */ NULL, /* .pfnReserved6 = */ NULL, /* .pfnReserved7 = */ NULL, #elif defined(IN_RC) /* .pfnConstruct = */ hdaRZConstruct, /* .pfnReserved0 = */ NULL, /* .pfnReserved1 = */ NULL, /* .pfnReserved2 = */ NULL, /* .pfnReserved3 = */ NULL, /* .pfnReserved4 = */ NULL, /* .pfnReserved5 = */ NULL, /* .pfnReserved6 = */ NULL, /* .pfnReserved7 = */ NULL, #else # error "Not in IN_RING3, IN_RING0 or IN_RC!" #endif /* .u32VersionEnd = */ PDM_DEVREG_VERSION }; #endif /* !VBOX_DEVICE_STRUCT_TESTCASE */