summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Audio/DevIchAc97.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:49:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:49:04 +0000
commit16f504a9dca3fe3b70568f67b7d41241ae485288 (patch)
treec60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Devices/Audio/DevIchAc97.cpp
parentInitial commit. (diff)
downloadvirtualbox-upstream.tar.xz
virtualbox-upstream.zip
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/Audio/DevIchAc97.cpp')
-rw-r--r--src/VBox/Devices/Audio/DevIchAc97.cpp4800
1 files changed, 4800 insertions, 0 deletions
diff --git a/src/VBox/Devices/Audio/DevIchAc97.cpp b/src/VBox/Devices/Audio/DevIchAc97.cpp
new file mode 100644
index 00000000..92ec2446
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevIchAc97.cpp
@@ -0,0 +1,4800 @@
+/* $Id: DevIchAc97.cpp $ */
+/** @file
+ * DevIchAc97 - VBox ICH AC97 Audio Controller.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_AC97
+#include <VBox/log.h>
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/AssertGuest.h>
+
+#include <iprt/assert.h>
+#ifdef IN_RING3
+# ifdef DEBUG
+# include <iprt/file.h>
+# endif
+# include <iprt/mem.h>
+# include <iprt/semaphore.h>
+# include <iprt/string.h>
+# include <iprt/uuid.h>
+# include <iprt/zero.h>
+#endif
+
+#include "VBoxDD.h"
+
+#include "AudioMixBuffer.h"
+#include "AudioMixer.h"
+#include "AudioHlp.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Current saved state version. */
+#define AC97_SAVED_STATE_VERSION 1
+
+/** Default timer frequency (in Hz). */
+#define AC97_TIMER_HZ_DEFAULT 100
+
+/** Maximum number of streams we support. */
+#define AC97_MAX_STREAMS 3
+
+/** Maximum FIFO size (in bytes) - unused. */
+#define AC97_FIFO_MAX 256
+
+/** @name AC97_SR_XXX - Status Register Bits (AC97_NABM_OFF_SR, PI_SR, PO_SR, MC_SR).
+ * @{ */
+#define AC97_SR_FIFOE RT_BIT(4) /**< rwc, FIFO error. */
+#define AC97_SR_BCIS RT_BIT(3) /**< rwc, Buffer completion interrupt status. */
+#define AC97_SR_LVBCI RT_BIT(2) /**< rwc, Last valid buffer completion interrupt. */
+#define AC97_SR_CELV RT_BIT(1) /**< ro, Current equals last valid. */
+#define AC97_SR_DCH RT_BIT(0) /**< ro, Controller halted. */
+#define AC97_SR_VALID_MASK (RT_BIT(5) - 1)
+#define AC97_SR_WCLEAR_MASK (AC97_SR_FIFOE | AC97_SR_BCIS | AC97_SR_LVBCI)
+#define AC97_SR_RO_MASK (AC97_SR_DCH | AC97_SR_CELV)
+#define AC97_SR_INT_MASK (AC97_SR_FIFOE | AC97_SR_BCIS | AC97_SR_LVBCI)
+/** @} */
+
+/** @name AC97_CR_XXX - Control Register Bits (AC97_NABM_OFF_CR, PI_CR, PO_CR, MC_CR).
+ * @{ */
+#define AC97_CR_IOCE RT_BIT(4) /**< rw, Interrupt On Completion Enable. */
+#define AC97_CR_FEIE RT_BIT(3) /**< rw FIFO Error Interrupt Enable. */
+#define AC97_CR_LVBIE RT_BIT(2) /**< rw Last Valid Buffer Interrupt Enable. */
+#define AC97_CR_RR RT_BIT(1) /**< rw Reset Registers. */
+#define AC97_CR_RPBM RT_BIT(0) /**< rw Run/Pause Bus Master. */
+#define AC97_CR_VALID_MASK (RT_BIT(5) - 1)
+#define AC97_CR_DONT_CLEAR_MASK (AC97_CR_IOCE | AC97_CR_FEIE | AC97_CR_LVBIE)
+/** @} */
+
+/** @name AC97_GC_XXX - Global Control Bits (see AC97_GLOB_CNT).
+ * @{ */
+#define AC97_GC_WR 4 /**< rw Warm reset. */
+#define AC97_GC_CR 2 /**< rw Cold reset. */
+#define AC97_GC_VALID_MASK (RT_BIT(6) - 1)
+/** @} */
+
+/** @name AC97_GS_XXX - Global Status Bits (AC97_GLOB_STA).
+ * @{ */
+#define AC97_GS_MD3 RT_BIT(17) /**< rw */
+#define AC97_GS_AD3 RT_BIT(16) /**< rw */
+#define AC97_GS_RCS RT_BIT(15) /**< rwc */
+#define AC97_GS_B3S12 RT_BIT(14) /**< ro */
+#define AC97_GS_B2S12 RT_BIT(13) /**< ro */
+#define AC97_GS_B1S12 RT_BIT(12) /**< ro */
+#define AC97_GS_S1R1 RT_BIT(11) /**< rwc */
+#define AC97_GS_S0R1 RT_BIT(10) /**< rwc */
+#define AC97_GS_S1CR RT_BIT(9) /**< ro */
+#define AC97_GS_S0CR RT_BIT(8) /**< ro */
+#define AC97_GS_MINT RT_BIT(7) /**< ro */
+#define AC97_GS_POINT RT_BIT(6) /**< ro */
+#define AC97_GS_PIINT RT_BIT(5) /**< ro */
+#define AC97_GS_RSRVD (RT_BIT(4) | RT_BIT(3))
+#define AC97_GS_MOINT RT_BIT(2) /**< ro */
+#define AC97_GS_MIINT RT_BIT(1) /**< ro */
+#define AC97_GS_GSCI RT_BIT(0) /**< rwc */
+#define AC97_GS_RO_MASK ( AC97_GS_B3S12 \
+ | AC97_GS_B2S12 \
+ | AC97_GS_B1S12 \
+ | AC97_GS_S1CR \
+ | AC97_GS_S0CR \
+ | AC97_GS_MINT \
+ | AC97_GS_POINT \
+ | AC97_GS_PIINT \
+ | AC97_GS_RSRVD \
+ | AC97_GS_MOINT \
+ | AC97_GS_MIINT)
+#define AC97_GS_VALID_MASK (RT_BIT(18) - 1)
+#define AC97_GS_WCLEAR_MASK (AC97_GS_RCS | AC97_GS_S1R1 | AC97_GS_S0R1 | AC97_GS_GSCI)
+/** @} */
+
+/** @name Buffer Descriptor (BDLE, BDL).
+ * @{ */
+#define AC97_BD_IOC RT_BIT(31) /**< Interrupt on Completion. */
+#define AC97_BD_BUP RT_BIT(30) /**< Buffer Underrun Policy. */
+
+#define AC97_BD_LEN_MASK 0xFFFF /**< Mask for the BDL buffer length. */
+
+#define AC97_BD_LEN_CTL_MBZ UINT32_C(0x3fff0000) /**< Must-be-zero mask for AC97BDLE.ctl_len. */
+
+#define AC97_MAX_BDLE 32 /**< Maximum number of BDLEs. */
+/** @} */
+
+/** @name Extended Audio ID Register (EAID).
+ * @{ */
+#define AC97_EAID_VRA RT_BIT(0) /**< Variable Rate Audio. */
+#define AC97_EAID_VRM RT_BIT(3) /**< Variable Rate Mic Audio. */
+#define AC97_EAID_REV0 RT_BIT(10) /**< AC'97 revision compliance. */
+#define AC97_EAID_REV1 RT_BIT(11) /**< AC'97 revision compliance. */
+/** @} */
+
+/** @name Extended Audio Control and Status Register (EACS).
+ * @{ */
+#define AC97_EACS_VRA RT_BIT(0) /**< Variable Rate Audio (4.2.1.1). */
+#define AC97_EACS_VRM RT_BIT(3) /**< Variable Rate Mic Audio (4.2.1.1). */
+/** @} */
+
+/** @name Baseline Audio Register Set (BARS).
+ * @{ */
+#define AC97_BARS_VOL_MASK 0x1f /**< Volume mask for the Baseline Audio Register Set (5.7.2). */
+#define AC97_BARS_GAIN_MASK 0x0f /**< Gain mask for the Baseline Audio Register Set. */
+#define AC97_BARS_VOL_MUTE_SHIFT 15 /**< Mute bit shift for the Baseline Audio Register Set (5.7.2). */
+/** @} */
+
+/** AC'97 uses 1.5dB steps, we use 0.375dB steps: 1 AC'97 step equals 4 PDM steps. */
+#define AC97_DB_FACTOR 4
+
+/** @name Recording inputs?
+ * @{ */
+#define AC97_REC_MIC UINT8_C(0)
+#define AC97_REC_CD UINT8_C(1)
+#define AC97_REC_VIDEO UINT8_C(2)
+#define AC97_REC_AUX UINT8_C(3)
+#define AC97_REC_LINE_IN UINT8_C(4)
+#define AC97_REC_STEREO_MIX UINT8_C(5)
+#define AC97_REC_MONO_MIX UINT8_C(6)
+#define AC97_REC_PHONE UINT8_C(7)
+#define AC97_REC_MASK UINT8_C(7)
+/** @} */
+
+/** @name Mixer registers / NAM BAR registers?
+ * @{ */
+#define AC97_Reset 0x00
+#define AC97_Master_Volume_Mute 0x02
+#define AC97_Headphone_Volume_Mute 0x04 /**< Also known as AUX, see table 16, section 5.7. */
+#define AC97_Master_Volume_Mono_Mute 0x06
+#define AC97_Master_Tone_RL 0x08
+#define AC97_PC_BEEP_Volume_Mute 0x0a
+#define AC97_Phone_Volume_Mute 0x0c
+#define AC97_Mic_Volume_Mute 0x0e
+#define AC97_Line_In_Volume_Mute 0x10
+#define AC97_CD_Volume_Mute 0x12
+#define AC97_Video_Volume_Mute 0x14
+#define AC97_Aux_Volume_Mute 0x16
+#define AC97_PCM_Out_Volume_Mute 0x18
+#define AC97_Record_Select 0x1a
+#define AC97_Record_Gain_Mute 0x1c
+#define AC97_Record_Gain_Mic_Mute 0x1e
+#define AC97_General_Purpose 0x20
+#define AC97_3D_Control 0x22
+#define AC97_AC_97_RESERVED 0x24
+#define AC97_Powerdown_Ctrl_Stat 0x26
+#define AC97_Extended_Audio_ID 0x28
+#define AC97_Extended_Audio_Ctrl_Stat 0x2a
+#define AC97_PCM_Front_DAC_Rate 0x2c
+#define AC97_PCM_Surround_DAC_Rate 0x2e
+#define AC97_PCM_LFE_DAC_Rate 0x30
+#define AC97_PCM_LR_ADC_Rate 0x32
+#define AC97_MIC_ADC_Rate 0x34
+#define AC97_6Ch_Vol_C_LFE_Mute 0x36
+#define AC97_6Ch_Vol_L_R_Surround_Mute 0x38
+#define AC97_Vendor_Reserved 0x58
+#define AC97_AD_Misc 0x76
+#define AC97_Vendor_ID1 0x7c
+#define AC97_Vendor_ID2 0x7e
+/** @} */
+
+/** @name Analog Devices miscellaneous regiter bits used in AD1980.
+ * @{ */
+#define AC97_AD_MISC_LOSEL RT_BIT(5) /**< Surround (rear) goes to line out outputs. */
+#define AC97_AD_MISC_HPSEL RT_BIT(10) /**< PCM (front) goes to headphone outputs. */
+/** @} */
+
+
+/** @name BUP flag values.
+ * @{ */
+#define BUP_SET RT_BIT_32(0)
+#define BUP_LAST RT_BIT_32(1)
+/** @} */
+
+/** @name AC'97 source indices.
+ * @note The order of these indices is fixed (also applies for saved states) for
+ * the moment. So make sure you know what you're done when altering this!
+ * @{
+ */
+#define AC97SOUNDSOURCE_PI_INDEX 0 /**< PCM in */
+#define AC97SOUNDSOURCE_PO_INDEX 1 /**< PCM out */
+#define AC97SOUNDSOURCE_MC_INDEX 2 /**< Mic in */
+#define AC97SOUNDSOURCE_MAX 3 /**< Max sound sources. */
+/** @} */
+
+/** Port number (offset into NABM BAR) to stream index. */
+#define AC97_PORT2IDX(a_idx) ( ((a_idx) >> 4) & 3 )
+/** Port number (offset into NABM BAR) to stream index, but no masking. */
+#define AC97_PORT2IDX_UNMASKED(a_idx) ( ((a_idx) >> 4) )
+
+/** @name Stream offsets
+ * @{ */
+#define AC97_NABM_OFF_BDBAR 0x0 /**< Buffer Descriptor Base Address */
+#define AC97_NABM_OFF_CIV 0x4 /**< Current Index Value */
+#define AC97_NABM_OFF_LVI 0x5 /**< Last Valid Index */
+#define AC97_NABM_OFF_SR 0x6 /**< Status Register */
+#define AC97_NABM_OFF_PICB 0x8 /**< Position in Current Buffer */
+#define AC97_NABM_OFF_PIV 0xa /**< Prefetched Index Value */
+#define AC97_NABM_OFF_CR 0xb /**< Control Register */
+#define AC97_NABM_OFF_MASK 0xf /**< Mask for getting the the per-stream register. */
+/** @} */
+
+
+/** @name PCM in NABM BAR registers (0x00..0x0f).
+ * @{ */
+#define PI_BDBAR (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0x0) /**< PCM in: Buffer Descriptor Base Address */
+#define PI_CIV (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0x4) /**< PCM in: Current Index Value */
+#define PI_LVI (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0x5) /**< PCM in: Last Valid Index */
+#define PI_SR (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0x6) /**< PCM in: Status Register */
+#define PI_PICB (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0x8) /**< PCM in: Position in Current Buffer */
+#define PI_PIV (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0xa) /**< PCM in: Prefetched Index Value */
+#define PI_CR (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0xb) /**< PCM in: Control Register */
+/** @} */
+
+/** @name PCM out NABM BAR registers (0x10..0x1f).
+ * @{ */
+#define PO_BDBAR (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0x0) /**< PCM out: Buffer Descriptor Base Address */
+#define PO_CIV (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0x4) /**< PCM out: Current Index Value */
+#define PO_LVI (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0x5) /**< PCM out: Last Valid Index */
+#define PO_SR (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0x6) /**< PCM out: Status Register */
+#define PO_PICB (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0x8) /**< PCM out: Position in Current Buffer */
+#define PO_PIV (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0xa) /**< PCM out: Prefetched Index Value */
+#define PO_CR (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0xb) /**< PCM out: Control Register */
+/** @} */
+
+/** @name Mic in NABM BAR registers (0x20..0x2f).
+ * @{ */
+#define MC_BDBAR (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0x0) /**< PCM in: Buffer Descriptor Base Address */
+#define MC_CIV (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0x4) /**< PCM in: Current Index Value */
+#define MC_LVI (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0x5) /**< PCM in: Last Valid Index */
+#define MC_SR (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0x6) /**< PCM in: Status Register */
+#define MC_PICB (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0x8) /**< PCM in: Position in Current Buffer */
+#define MC_PIV (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0xa) /**< PCM in: Prefetched Index Value */
+#define MC_CR (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0xb) /**< PCM in: Control Register */
+/** @} */
+
+/** @name Misc NABM BAR registers.
+ * @{ */
+/** NABMBAR: Global Control Register.
+ * @note This is kind of in the MIC IN area. */
+#define AC97_GLOB_CNT 0x2c
+/** NABMBAR: Global Status. */
+#define AC97_GLOB_STA 0x30
+/** Codec Access Semaphore Register. */
+#define AC97_CAS 0x34
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** The ICH AC'97 (Intel) controller (shared). */
+typedef struct AC97STATE *PAC97STATE;
+/** The ICH AC'97 (Intel) controller (ring-3). */
+typedef struct AC97STATER3 *PAC97STATER3;
+
+/**
+ * Buffer Descriptor List Entry (BDLE).
+ *
+ * (See section 3.2.1 in Intel document number 252751-001, or section 1.2.2.1 in
+ * Intel document number 302349-003.)
+ */
+typedef struct AC97BDLE
+{
+ /** Location of data buffer (bits 31:1). */
+ uint32_t addr;
+ /** Flags (bits 31 + 30) and length (bits 15:0) of data buffer (in audio samples).
+ * @todo split up into two 16-bit fields. */
+ uint32_t ctl_len;
+} AC97BDLE;
+AssertCompileSize(AC97BDLE, 8);
+/** Pointer to BDLE. */
+typedef AC97BDLE *PAC97BDLE;
+
+/**
+ * Bus master register set for an audio stream.
+ *
+ * (See section 16.2 in Intel document 301473-002, or section 2.2 in Intel
+ * document 302349-003.)
+ */
+typedef struct AC97BMREGS
+{
+ uint32_t bdbar; /**< rw 0, Buffer Descriptor List: BAR (Base Address Register). */
+ uint8_t civ; /**< ro 0, Current index value. */
+ uint8_t lvi; /**< rw 0, Last valid index. */
+ uint16_t sr; /**< rw 1, Status register. */
+ uint16_t picb; /**< ro 0, Position in current buffer (samples left to process). */
+ uint8_t piv; /**< ro 0, Prefetched index value. */
+ uint8_t cr; /**< rw 0, Control register. */
+ int32_t bd_valid; /**< Whether current BDLE is initialized or not. */
+ AC97BDLE bd; /**< Current Buffer Descriptor List Entry (BDLE). */
+} AC97BMREGS;
+AssertCompileSizeAlignment(AC97BMREGS, 8);
+/** Pointer to the BM registers of an audio stream. */
+typedef AC97BMREGS *PAC97BMREGS;
+
+/**
+ * The internal state of an AC'97 stream.
+ */
+typedef struct AC97STREAMSTATE
+{
+ /** Critical section for this stream. */
+ RTCRITSECT CritSect;
+ /** Circular buffer (FIFO) for holding DMA'ed data. */
+ R3PTRTYPE(PRTCIRCBUF) pCircBuf;
+#if HC_ARCH_BITS == 32
+ uint32_t Padding;
+#endif
+ /** Current circular buffer read offset (for tracing & logging). */
+ uint64_t offRead;
+ /** Current circular buffer write offset (for tracing & logging). */
+ uint64_t offWrite;
+ /** The stream's current configuration. */
+ PDMAUDIOSTREAMCFG Cfg; //+108
+ /** Timestamp of the last DMA data transfer. */
+ uint64_t tsTransferLast;
+ /** Timestamp of the next DMA data transfer.
+ * Next for determining the next scheduling window.
+ * Can be 0 if no next transfer is scheduled. */
+ uint64_t tsTransferNext;
+ /** The stream's timer Hz rate.
+ * This value can can be different from the device's default Hz rate,
+ * depending on the rate the stream expects (e.g. for 5.1 speaker setups).
+ * Set in R3StreamInit(). */
+ uint16_t uTimerHz;
+ /** Set if we've registered the asynchronous update job. */
+ bool fRegisteredAsyncUpdateJob;
+ /** Input streams only: Set when we switch from feeding the guest silence and
+ * commits to proving actual audio input bytes. */
+ bool fInputPreBuffered;
+ /** This is ZERO if stream setup succeeded, otherwise it's the RTTimeNanoTS() at
+ * which to retry setting it up. The latter applies only to same
+ * parameters. */
+ uint64_t nsRetrySetup;
+ /** Timestamp (in ns) of last stream update. */
+ uint64_t tsLastUpdateNs;
+
+ /** Size of the DMA buffer (pCircBuf) in bytes. */
+ uint32_t StatDmaBufSize;
+ /** Number of used bytes in the DMA buffer (pCircBuf). */
+ uint32_t StatDmaBufUsed;
+ /** Counter for all under/overflows problems. */
+ STAMCOUNTER StatDmaFlowProblems;
+ /** Counter for unresovled under/overflows problems. */
+ STAMCOUNTER StatDmaFlowErrors;
+ /** Number of bytes involved in unresolved flow errors. */
+ STAMCOUNTER StatDmaFlowErrorBytes;
+ STAMCOUNTER StatDmaSkippedDch;
+ STAMCOUNTER StatDmaSkippedPendingBcis;
+ STAMPROFILE StatStart;
+ STAMPROFILE StatReset;
+ STAMPROFILE StatStop;
+ STAMPROFILE StatReSetUpChanged;
+ STAMPROFILE StatReSetUpSame;
+ STAMCOUNTER StatWriteLviRecover;
+ STAMCOUNTER StatWriteCr;
+} AC97STREAMSTATE;
+AssertCompileSizeAlignment(AC97STREAMSTATE, 8);
+/** Pointer to internal state of an AC'97 stream. */
+typedef AC97STREAMSTATE *PAC97STREAMSTATE;
+
+/**
+ * Runtime configurable debug stuff for an AC'97 stream.
+ */
+typedef struct AC97STREAMDEBUGRT
+{
+ /** Whether debugging is enabled or not. */
+ bool fEnabled;
+ uint8_t Padding[7];
+ /** File for dumping stream reads / writes.
+ * For input streams, this dumps data being written to the device FIFO,
+ * whereas for output streams this dumps data being read from the device FIFO. */
+ R3PTRTYPE(PAUDIOHLPFILE) pFileStream;
+ /** File for dumping DMA reads / writes.
+ * For input streams, this dumps data being written to the device DMA,
+ * whereas for output streams this dumps data being read from the device DMA. */
+ R3PTRTYPE(PAUDIOHLPFILE) pFileDMA;
+} AC97STREAMDEBUGRT;
+
+/**
+ * Debug stuff for an AC'97 stream.
+ */
+typedef struct AC97STREAMDEBUG
+{
+ /** Runtime debug stuff. */
+ AC97STREAMDEBUGRT Runtime;
+} AC97STREAMDEBUG;
+
+/**
+ * The shared AC'97 stream state.
+ */
+typedef struct AC97STREAM
+{
+ /** Bus master registers of this stream. */
+ AC97BMREGS Regs;
+ /** Stream number (SDn). */
+ uint8_t u8SD;
+ uint8_t abPadding0[7];
+
+ /** The timer for pumping data thru the attached LUN drivers. */
+ TMTIMERHANDLE hTimer;
+ /** When the timer was armed (timer clock). */
+ uint64_t uArmedTs;
+ /** (Virtual) clock ticks per transfer. */
+ uint64_t cDmaPeriodTicks;
+ /** Transfer chunk size (in bytes) of a transfer period. */
+ uint32_t cbDmaPeriod;
+ /** DMA period counter (for logging). */
+ uint32_t uDmaPeriod;
+
+ STAMCOUNTER StatWriteLvi;
+ STAMCOUNTER StatWriteSr1;
+ STAMCOUNTER StatWriteSr2;
+ STAMCOUNTER StatWriteBdBar;
+} AC97STREAM;
+AssertCompileSizeAlignment(AC97STREAM, 8);
+/** Pointer to a shared AC'97 stream state. */
+typedef AC97STREAM *PAC97STREAM;
+
+
+/**
+ * The ring-3 AC'97 stream state.
+ */
+typedef struct AC97STREAMR3
+{
+ /** Stream number (SDn). */
+ uint8_t u8SD;
+ uint8_t abPadding0[7];
+ /** Internal state of this stream. */
+ AC97STREAMSTATE State;
+ /** Debug stuff. */
+ AC97STREAMDEBUG Dbg;
+} AC97STREAMR3;
+AssertCompileSizeAlignment(AC97STREAMR3, 8);
+/** Pointer to an AC'97 stream state for ring-3. */
+typedef AC97STREAMR3 *PAC97STREAMR3;
+
+
+/**
+ * A driver stream (host backend).
+ *
+ * Each driver has its own instances of audio mixer streams, which then
+ * can go into the same (or even different) audio mixer sinks.
+ */
+typedef struct AC97DRIVERSTREAM
+{
+ /** Associated mixer stream handle. */
+ R3PTRTYPE(PAUDMIXSTREAM) pMixStrm;
+} AC97DRIVERSTREAM;
+/** Pointer to a driver stream. */
+typedef AC97DRIVERSTREAM *PAC97DRIVERSTREAM;
+
+/**
+ * A host backend driver (LUN).
+ */
+typedef struct AC97DRIVER
+{
+ /** Node for storing this driver in our device driver list of AC97STATE. */
+ RTLISTNODER3 Node;
+ /** LUN # to which this driver has been assigned. */
+ uint8_t uLUN;
+ /** Whether this driver is in an attached state or not. */
+ bool fAttached;
+ uint8_t abPadding[6];
+ /** Pointer to attached driver base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Audio connector interface to the underlying host backend. */
+ R3PTRTYPE(PPDMIAUDIOCONNECTOR) pConnector;
+ /** Driver stream for line input. */
+ AC97DRIVERSTREAM LineIn;
+ /** Driver stream for mic input. */
+ AC97DRIVERSTREAM MicIn;
+ /** Driver stream for output. */
+ AC97DRIVERSTREAM Out;
+ /** The LUN description. */
+ char szDesc[48 - 2];
+} AC97DRIVER;
+/** Pointer to a host backend driver (LUN). */
+typedef AC97DRIVER *PAC97DRIVER;
+
+/**
+ * Debug settings.
+ */
+typedef struct AC97STATEDEBUG
+{
+ /** Whether debugging is enabled or not. */
+ bool fEnabled;
+ bool afAlignment[7];
+ /** Path where to dump the debug output to.
+ * Can be NULL, in which the system's temporary directory will be used then. */
+ R3PTRTYPE(char *) pszOutPath;
+} AC97STATEDEBUG;
+
+
+/* Codec models. */
+typedef enum AC97CODEC
+{
+ AC97CODEC_INVALID = 0, /**< Customary illegal zero value. */
+ AC97CODEC_STAC9700, /**< SigmaTel STAC9700 */
+ AC97CODEC_AD1980, /**< Analog Devices AD1980 */
+ AC97CODEC_AD1981B, /**< Analog Devices AD1981B */
+ AC97CODEC_32BIT_HACK = 0x7fffffff
+} AC97CODEC;
+
+
+/**
+ * The shared AC'97 device state.
+ */
+typedef struct AC97STATE
+{
+ /** Critical section protecting the AC'97 state. */
+ PDMCRITSECT CritSect;
+ /** Global Control (Bus Master Control Register). */
+ uint32_t glob_cnt;
+ /** Global Status (Bus Master Control Register). */
+ uint32_t glob_sta;
+ /** Codec Access Semaphore Register (Bus Master Control Register). */
+ uint32_t cas;
+ uint32_t last_samp;
+ uint8_t mixer_data[256];
+ /** Array of AC'97 streams (parallel to AC97STATER3::aStreams). */
+ AC97STREAM aStreams[AC97_MAX_STREAMS];
+ /** The device timer Hz rate. Defaults to AC97_TIMER_HZ_DEFAULT_DEFAULT. */
+ uint16_t uTimerHz;
+ /** Config: Internal input DMA buffer size override, specified in milliseconds.
+ * Zero means default size according to buffer and stream config.
+ * @sa BufSizeInMs config value. */
+ uint16_t cMsCircBufIn;
+ /** Config: Internal output DMA buffer size override, specified in milliseconds.
+ * Zero means default size according to buffer and stream config.
+ * @sa BufSizeOutMs config value. */
+ uint16_t cMsCircBufOut;
+ uint16_t au16Padding1[1];
+ uint8_t silence[128];
+ uint32_t bup_flag;
+ /** Codec model. */
+ AC97CODEC enmCodecModel;
+
+ /** PCI region \#0: NAM I/O ports. */
+ IOMIOPORTHANDLE hIoPortsNam;
+ /** PCI region \#0: NANM I/O ports. */
+ IOMIOPORTHANDLE hIoPortsNabm;
+
+ STAMCOUNTER StatUnimplementedNabmReads;
+ STAMCOUNTER StatUnimplementedNabmWrites;
+ STAMCOUNTER StatUnimplementedNamReads;
+ STAMCOUNTER StatUnimplementedNamWrites;
+#ifdef VBOX_WITH_STATISTICS
+ STAMPROFILE StatTimer;
+#endif
+} AC97STATE;
+AssertCompileMemberAlignment(AC97STATE, aStreams, 8);
+AssertCompileMemberAlignment(AC97STATE, StatUnimplementedNabmReads, 8);
+
+
+/**
+ * The ring-3 AC'97 device state.
+ */
+typedef struct AC97STATER3
+{
+ /** Array of AC'97 streams (parallel to AC97STATE:aStreams). */
+ AC97STREAMR3 aStreams[AC97_MAX_STREAMS];
+ /** R3 pointer to the device instance. */
+ PPDMDEVINSR3 pDevIns;
+ /** List of associated LUN drivers (AC97DRIVER). */
+ RTLISTANCHORR3 lstDrv;
+ /** The device's software mixer. */
+ R3PTRTYPE(PAUDIOMIXER) pMixer;
+ /** Audio sink for PCM output. */
+ R3PTRTYPE(PAUDMIXSINK) pSinkOut;
+ /** Audio sink for line input. */
+ R3PTRTYPE(PAUDMIXSINK) pSinkLineIn;
+ /** Audio sink for microphone input. */
+ R3PTRTYPE(PAUDMIXSINK) pSinkMicIn;
+ /** The base interface for LUN\#0. */
+ PDMIBASE IBase;
+ /** Debug settings. */
+ AC97STATEDEBUG Dbg;
+} AC97STATER3;
+AssertCompileMemberAlignment(AC97STATER3, aStreams, 8);
+/** Pointer to the ring-3 AC'97 device state. */
+typedef AC97STATER3 *PAC97STATER3;
+
+
+/**
+ * Acquires the AC'97 lock.
+ */
+#define DEVAC97_LOCK(a_pDevIns, a_pThis) \
+ do { \
+ int const rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, VERR_IGNORED); \
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV((a_pDevIns), &(a_pThis)->CritSect, rcLock); \
+ } while (0)
+
+/**
+ * Acquires the AC'97 lock or returns.
+ */
+# define DEVAC97_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)
+
+/**
+ * Releases the AC'97 lock.
+ */
+#define DEVAC97_UNLOCK(a_pDevIns, a_pThis) \
+ do { PDMDevHlpCritSectLeave((a_pDevIns), &(a_pThis)->CritSect); } while (0)
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void ichac97StreamUpdateSR(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, uint32_t new_sr);
+static uint16_t ichac97MixerGet(PAC97STATE pThis, uint32_t uMixerIdx);
+#ifdef IN_RING3
+DECLINLINE(void) ichac97R3StreamLock(PAC97STREAMR3 pStreamCC);
+DECLINLINE(void) ichac97R3StreamUnlock(PAC97STREAMR3 pStreamCC);
+static void ichac97R3DbgPrintBdl(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream,
+ PCDBGFINFOHLP pHlp, const char *pszPrefix);
+static DECLCALLBACK(void) ichac97R3Reset(PPDMDEVINS pDevIns);
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef IN_RING3
+/** NABM I/O port descriptions. */
+static const IOMIOPORTDESC g_aNabmPorts[] =
+{
+ { "PCM IN - BDBAR", "PCM IN - BDBAR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "PCM IN - CIV", "PCM IN - CIV", NULL, NULL },
+ { "PCM IN - LVI", "PCM IN - LIV", NULL, NULL },
+ { "PCM IN - SR", "PCM IN - SR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "PCM IN - PICB", "PCM IN - PICB", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "PCM IN - PIV", "PCM IN - PIV", NULL, NULL },
+ { "PCM IN - CR", "PCM IN - CR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+
+ { "PCM OUT - BDBAR", "PCM OUT - BDBAR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "PCM OUT - CIV", "PCM OUT - CIV", NULL, NULL },
+ { "PCM OUT - LVI", "PCM OUT - LIV", NULL, NULL },
+ { "PCM OUT - SR", "PCM OUT - SR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "PCM OUT - PICB", "PCM OUT - PICB", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "PCM OUT - PIV", "PCM OUT - PIV", NULL, NULL },
+ { "PCM OUT - CR", "PCM IN - CR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+
+ { "MIC IN - BDBAR", "MIC IN - BDBAR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "MIC IN - CIV", "MIC IN - CIV", NULL, NULL },
+ { "MIC IN - LVI", "MIC IN - LIV", NULL, NULL },
+ { "MIC IN - SR", "MIC IN - SR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "MIC IN - PICB", "MIC IN - PICB", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "MIC IN - PIV", "MIC IN - PIV", NULL, NULL },
+ { "MIC IN - CR", "MIC IN - CR", NULL, NULL },
+ { "GLOB CNT", "GLOB CNT", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+
+ { "GLOB STA", "GLOB STA", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "CAS", "CAS", NULL, NULL },
+ { NULL, NULL, NULL, NULL },
+};
+
+/** @name Source indices
+ * @{ */
+# define AC97SOUNDSOURCE_PI_INDEX 0 /**< PCM in */
+# define AC97SOUNDSOURCE_PO_INDEX 1 /**< PCM out */
+# define AC97SOUNDSOURCE_MC_INDEX 2 /**< Mic in */
+# define AC97SOUNDSOURCE_MAX 3 /**< Max sound sources. */
+/** @} */
+
+/** Port number (offset into NABM BAR) to stream index. */
+# define AC97_PORT2IDX(a_idx) ( ((a_idx) >> 4) & 3 )
+/** Port number (offset into NABM BAR) to stream index, but no masking. */
+# define AC97_PORT2IDX_UNMASKED(a_idx) ( ((a_idx) >> 4) )
+
+/** @name Stream offsets
+ * @{ */
+# define AC97_NABM_OFF_BDBAR 0x0 /**< Buffer Descriptor Base Address */
+# define AC97_NABM_OFF_CIV 0x4 /**< Current Index Value */
+# define AC97_NABM_OFF_LVI 0x5 /**< Last Valid Index */
+# define AC97_NABM_OFF_SR 0x6 /**< Status Register */
+# define AC97_NABM_OFF_PICB 0x8 /**< Position in Current Buffer */
+# define AC97_NABM_OFF_PIV 0xa /**< Prefetched Index Value */
+# define AC97_NABM_OFF_CR 0xb /**< Control Register */
+# define AC97_NABM_OFF_MASK 0xf /**< Mask for getting the the per-stream register. */
+/** @} */
+
+#endif /* IN_RING3 */
+
+
+
+static void ichac97WarmReset(PAC97STATE pThis)
+{
+ NOREF(pThis);
+}
+
+static void ichac97ColdReset(PAC97STATE pThis)
+{
+ NOREF(pThis);
+}
+
+
+#ifdef IN_RING3
+
+/**
+ * Returns the audio direction of a specified stream descriptor.
+ *
+ * @return Audio direction.
+ */
+DECLINLINE(PDMAUDIODIR) ichac97R3GetDirFromSD(uint8_t uSD)
+{
+ switch (uSD)
+ {
+ case AC97SOUNDSOURCE_PI_INDEX: return PDMAUDIODIR_IN;
+ case AC97SOUNDSOURCE_PO_INDEX: return PDMAUDIODIR_OUT;
+ case AC97SOUNDSOURCE_MC_INDEX: return PDMAUDIODIR_IN;
+ }
+
+ AssertFailed();
+ return PDMAUDIODIR_UNKNOWN;
+}
+
+
+/**
+ * Retrieves the audio mixer sink of a corresponding AC'97 stream index.
+ *
+ * @returns Pointer to audio mixer sink if found, or NULL if not found / invalid.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param uIndex Stream index to get audio mixer sink for.
+ */
+DECLINLINE(PAUDMIXSINK) ichac97R3IndexToSink(PAC97STATER3 pThisCC, uint8_t uIndex)
+{
+ switch (uIndex)
+ {
+ case AC97SOUNDSOURCE_PI_INDEX: return pThisCC->pSinkLineIn;
+ case AC97SOUNDSOURCE_PO_INDEX: return pThisCC->pSinkOut;
+ case AC97SOUNDSOURCE_MC_INDEX: return pThisCC->pSinkMicIn;
+ default:
+ AssertMsgFailedReturn(("Wrong index %RU8\n", uIndex), NULL);
+ }
+}
+
+
+/*********************************************************************************************************************************
+* Stream DMA *
+*********************************************************************************************************************************/
+
+/**
+ * Retrieves the available size of (buffered) audio data (in bytes) of a given AC'97 stream.
+ *
+ * @returns Available data (in bytes).
+ * @param pStreamCC The AC'97 stream to retrieve size for (ring-3).
+ */
+DECLINLINE(uint32_t) ichac97R3StreamGetUsed(PAC97STREAMR3 pStreamCC)
+{
+ PRTCIRCBUF const pCircBuf = pStreamCC->State.pCircBuf;
+ if (pCircBuf)
+ return (uint32_t)RTCircBufUsed(pCircBuf);
+ return 0;
+}
+
+
+/**
+ * Retrieves the free size of audio data (in bytes) of a given AC'97 stream.
+ *
+ * @returns Free data (in bytes).
+ * @param pStreamCC AC'97 stream to retrieve size for (ring-3).
+ */
+DECLINLINE(uint32_t) ichac97R3StreamGetFree(PAC97STREAMR3 pStreamCC)
+{
+ PRTCIRCBUF const pCircBuf = pStreamCC->State.pCircBuf;
+ if (pCircBuf)
+ return (uint32_t)RTCircBufFree(pCircBuf);
+ return 0;
+}
+
+
+# if 0 /* Unused */
+static void ichac97R3WriteBUP(PAC97STATE pThis, uint32_t cbElapsed)
+{
+ LogFlowFunc(("cbElapsed=%RU32\n", cbElapsed));
+
+ if (!(pThis->bup_flag & BUP_SET))
+ {
+ if (pThis->bup_flag & BUP_LAST)
+ {
+ unsigned int i;
+ uint32_t *p = (uint32_t*)pThis->silence;
+ for (i = 0; i < sizeof(pThis->silence) / 4; i++) /** @todo r=andy Assumes 16-bit samples, stereo. */
+ *p++ = pThis->last_samp;
+ }
+ else
+ RT_ZERO(pThis->silence);
+
+ pThis->bup_flag |= BUP_SET;
+ }
+
+ while (cbElapsed)
+ {
+ uint32_t cbToWrite = RT_MIN(cbElapsed, (uint32_t)sizeof(pThis->silence));
+ uint32_t cbWrittenToStream;
+
+ int rc2 = AudioMixerSinkWrite(pThisCC->pSinkOut, AUDMIXOP_COPY,
+ pThis->silence, cbToWrite, &cbWrittenToStream);
+ if (RT_SUCCESS(rc2))
+ {
+ if (cbWrittenToStream < cbToWrite) /* Lagging behind? */
+ LogFlowFunc(("Warning: Only written %RU32 / %RU32 bytes, expect lags\n", cbWrittenToStream, cbToWrite));
+ }
+
+ /* Always report all data as being written;
+ * backends who were not able to catch up have to deal with it themselves. */
+ Assert(cbElapsed >= cbToWrite);
+ cbElapsed -= cbToWrite;
+ }
+}
+# endif /* Unused */
+
+
+/**
+ * Fetches the next buffer descriptor (BDLE) updating the stream registers.
+ *
+ * This will skip zero length descriptors.
+ *
+ * @returns Zero, or AC97_SR_BCIS if skipped zero length buffer with IOC set.
+ * @param pDevIns The device instance.
+ * @param pStream AC'97 stream to fetch BDLE for.
+ * @param pStreamCC The AC'97 stream, ring-3 state.
+ *
+ * @remarks Updates CIV, PIV, BD and PICB.
+ *
+ * @note Both PIV and CIV will be zero after a stream reset, so the first
+ * time we advance the buffer position afterwards, CIV will remain zero
+ * and PIV becomes 1. Thus we will start processing from BDLE00 and
+ * not BDLE01 as CIV=0 may lead you to think.
+ */
+static uint32_t ichac97R3StreamFetchNextBdle(PPDMDEVINS pDevIns, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC)
+{
+ RT_NOREF(pStreamCC);
+ uint32_t fSrBcis = 0;
+
+ /*
+ * Loop for skipping zero length entries.
+ */
+ for (;;)
+ {
+ /* Advance the buffer. */
+ pStream->Regs.civ = pStream->Regs.piv % AC97_MAX_BDLE /* (paranoia) */;
+ pStream->Regs.piv = (pStream->Regs.piv + 1) % AC97_MAX_BDLE;
+
+ /* Load it. */
+ AC97BDLE Bdle = { 0, 0 };
+ PDMDevHlpPCIPhysRead(pDevIns, pStream->Regs.bdbar + pStream->Regs.civ * sizeof(AC97BDLE), &Bdle, sizeof(AC97BDLE));
+ pStream->Regs.bd_valid = 1;
+ pStream->Regs.bd.addr = RT_H2LE_U32(Bdle.addr) & ~3;
+ pStream->Regs.bd.ctl_len = RT_H2LE_U32(Bdle.ctl_len);
+ pStream->Regs.picb = pStream->Regs.bd.ctl_len & AC97_BD_LEN_MASK;
+
+ LogFlowFunc(("BDLE%02u: %#RX32 L %#x / LB %#x, ctl=%#06x%s%s\n",
+ pStream->Regs.civ, pStream->Regs.bd.addr, pStream->Regs.bd.ctl_len & AC97_BD_LEN_MASK,
+ (pStream->Regs.bd.ctl_len & AC97_BD_LEN_MASK) * PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props),
+ pStream->Regs.bd.ctl_len >> 16,
+ pStream->Regs.bd.ctl_len & AC97_BD_IOC ? " ioc" : "",
+ pStream->Regs.bd.ctl_len & AC97_BD_BUP ? " bup" : ""));
+
+ /* Complain about any reserved bits set in CTL and ADDR: */
+ ASSERT_GUEST_MSG(!(pStream->Regs.bd.ctl_len & AC97_BD_LEN_CTL_MBZ),
+ ("Reserved bits set: %#RX32\n", pStream->Regs.bd.ctl_len));
+ ASSERT_GUEST_MSG(!(RT_H2LE_U32(Bdle.addr) & 3),
+ ("Reserved addr bits set: %#RX32\n", RT_H2LE_U32(Bdle.addr) ));
+
+ /* If the length is non-zero or if we've reached LVI, we're done regardless
+ of what's been loaded. Otherwise, we skip zero length buffers. */
+ if (pStream->Regs.picb)
+ break;
+ if (pStream->Regs.civ == (pStream->Regs.lvi % AC97_MAX_BDLE /* (paranoia) */))
+ {
+ LogFunc(("BDLE%02u is zero length! Can't skip (CIV=LVI). %#RX32 %#RX32\n", pStream->Regs.civ, Bdle.addr, Bdle.ctl_len));
+ break;
+ }
+ LogFunc(("BDLE%02u is zero length! Skipping. %#RX32 %#RX32\n", pStream->Regs.civ, Bdle.addr, Bdle.ctl_len));
+
+ /* If the buffer has IOC set, make sure it's triggered by the caller. */
+ if (pStream->Regs.bd.ctl_len & AC97_BD_IOC)
+ fSrBcis |= AC97_SR_BCIS;
+ }
+
+ /* 1.2.4.2 PCM Buffer Restrictions (in 302349-003) - #1 */
+ ASSERT_GUEST_MSG(!(pStream->Regs.picb & 1),
+ ("Odd lengths buffers are not allowed: %#x (%d) samples\n", pStream->Regs.picb, pStream->Regs.picb));
+
+ /* 1.2.4.2 PCM Buffer Restrictions (in 302349-003) - #2 */
+ ASSERT_GUEST_MSG(pStream->Regs.picb > 0, ("Zero length buffers not allowed to terminate list (LVI=%u CIV=%u)\n",
+ pStream->Regs.lvi, pStream->Regs.civ));
+
+ return fSrBcis;
+}
+
+
+/**
+ * Transfers data of an AC'97 stream according to its usage (input / output).
+ *
+ * For an SDO (output) stream this means reading DMA data from the device to
+ * the AC'97 stream's internal FIFO buffer.
+ *
+ * For an SDI (input) stream this is reading audio data from the AC'97 stream's
+ * internal FIFO buffer and writing it as DMA data to the device.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 state.
+ * @param pStream The AC'97 stream to update (shared).
+ * @param pStreamCC The AC'97 stream to update (ring-3).
+ * @param cbToProcess The max amount of data to process (i.e.
+ * put into / remove from the circular buffer).
+ * Unless something is going seriously wrong, this
+ * will always be transfer size for the current
+ * period. The current period will never be
+ * larger than what can be stored in the current
+ * buffer (i.e. what PICB indicates).
+ * @param fWriteSilence Whether to write silence if this is an input
+ * stream (done while waiting for backend to get
+ * going).
+ * @param fInput Set if input, clear if output.
+ */
+static int ichac97R3StreamTransfer(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream,
+ PAC97STREAMR3 pStreamCC, uint32_t cbToProcess, bool fWriteSilence, bool fInput)
+{
+ if (RT_LIKELY(cbToProcess > 0))
+ Assert(PDMAudioPropsIsSizeAligned(&pStreamCC->State.Cfg.Props, cbToProcess));
+ else
+ return VINF_SUCCESS;
+
+ ichac97R3StreamLock(pStreamCC);
+
+ /*
+ * Check that the controller is not halted (DCH) and that the buffer
+ * completion interrupt isn't pending.
+ */
+ /** @todo r=bird: Why do we not just barge ahead even when BCIS is set? Can't
+ * find anything in spec indicating that we shouldn't. Linux shouldn't
+ * care if be bundle IOCs, as it checks how many steps we've taken using
+ * CIV. The Windows AC'97 sample driver doesn't care at all, since it
+ * just sets LIV to CIV-1 (thought that's probably not what the real
+ * windows driver does)...
+ *
+ * This is not going to sound good if it happens often enough, because
+ * each time we'll lose one DMA period (exact length depends on the
+ * buffer here).
+ *
+ * If we're going to keep this hack, there should be a
+ * PDMDevHlpTimerSetRelative call arm-ing the DMA timer to fire shortly
+ * after BCIS is cleared. Otherwise, we might lag behind even more
+ * before we get stuff going again.
+ *
+ * I just wish there was some clear reasoning in the source code for
+ * weird shit like this. This is just random voodoo. Sigh^3! */
+ if (!(pStream->Regs.sr & (AC97_SR_DCH | AC97_SR_BCIS))) /* Controller halted? */
+ { /* not halted nor does it have pending interrupt - likely */ }
+ else
+ {
+ /** @todo Stop DMA timer when DCH is set. */
+ if (pStream->Regs.sr & AC97_SR_DCH)
+ {
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaSkippedDch);
+ LogFunc(("[SD%RU8] DCH set\n", pStream->u8SD));
+ }
+ if (pStream->Regs.sr & AC97_SR_BCIS)
+ {
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaSkippedPendingBcis);
+ LogFunc(("[SD%RU8] BCIS set\n", pStream->u8SD));
+ }
+ if ((pStream->Regs.cr & AC97_CR_RPBM) /* Bus master operation started. */ && !fInput)
+ {
+ /*ichac97R3WriteBUP(pThis, cbToProcess);*/
+ }
+
+ ichac97R3StreamUnlock(pStreamCC);
+ return VINF_SUCCESS;
+ }
+
+ /* 0x1ba*2 = 0x374 (884) 0x3c0
+ * Transfer loop.
+ */
+#ifdef LOG_ENABLED
+ uint32_t cbProcessedTotal = 0;
+#endif
+ int rc = VINF_SUCCESS;
+ PRTCIRCBUF pCircBuf = pStreamCC->State.pCircBuf;
+ AssertReturnStmt(pCircBuf, ichac97R3StreamUnlock(pStreamCC), VINF_SUCCESS);
+ Assert((uint32_t)pStream->Regs.picb * PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props) >= cbToProcess);
+ Log3Func(("[SD%RU8] cbToProcess=%#x PICB=%#x/%#x\n", pStream->u8SD, cbToProcess,
+ pStream->Regs.picb, pStream->Regs.picb * PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props)));
+
+ while (cbToProcess > 0)
+ {
+ uint32_t cbChunk = cbToProcess;
+
+ /*
+ * Output.
+ */
+ if (!fInput)
+ {
+ void *pvDst = NULL;
+ size_t cbDst = 0;
+ RTCircBufAcquireWriteBlock(pCircBuf, cbChunk, &pvDst, &cbDst);
+
+ if (cbDst)
+ {
+ int rc2 = PDMDevHlpPCIPhysRead(pDevIns, pStream->Regs.bd.addr, pvDst, cbDst);
+ AssertRC(rc2);
+
+ if (RT_LIKELY(!pStreamCC->Dbg.Runtime.pFileDMA))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pStreamCC->Dbg.Runtime.pFileDMA, pvDst, cbDst);
+ }
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbDst);
+
+ cbChunk = (uint32_t)cbDst; /* Update the current chunk size to what really has been written. */
+ }
+ /*
+ * Input.
+ */
+ else if (!fWriteSilence)
+ {
+ void *pvSrc = NULL;
+ size_t cbSrc = 0;
+ RTCircBufAcquireReadBlock(pCircBuf, cbChunk, &pvSrc, &cbSrc);
+
+ if (cbSrc)
+ {
+ int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, pStream->Regs.bd.addr, pvSrc, cbSrc);
+ AssertRC(rc2);
+
+ if (RT_LIKELY(!pStreamCC->Dbg.Runtime.pFileDMA))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pStreamCC->Dbg.Runtime.pFileDMA, pvSrc, cbSrc);
+ }
+
+ RTCircBufReleaseReadBlock(pCircBuf, cbSrc);
+
+ cbChunk = (uint32_t)cbSrc; /* Update the current chunk size to what really has been read. */
+ }
+ else
+ {
+ /* Since the format is signed 16-bit or 32-bit integer samples, we can
+ use g_abRTZero64K as source and avoid some unnecessary bzero() work. */
+ cbChunk = RT_MIN(cbChunk, sizeof(g_abRTZero64K));
+ cbChunk = PDMAudioPropsFloorBytesToFrame(&pStreamCC->State.Cfg.Props, cbChunk);
+
+ int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, pStream->Regs.bd.addr, g_abRTZero64K, cbChunk);
+ AssertRC(rc2);
+ }
+
+ Assert(PDMAudioPropsIsSizeAligned(&pStreamCC->State.Cfg.Props, cbChunk));
+ Assert(cbChunk <= cbToProcess);
+
+ /*
+ * Advance.
+ */
+ pStream->Regs.picb -= cbChunk / PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props);
+ pStream->Regs.bd.addr += cbChunk;
+ cbToProcess -= cbChunk;
+#ifdef LOG_ENABLED
+ cbProcessedTotal += cbChunk;
+#endif
+ LogFlowFunc(("[SD%RU8] cbChunk=%#x, cbToProcess=%#x, cbTotal=%#x picb=%#x\n",
+ pStream->u8SD, cbChunk, cbToProcess, cbProcessedTotal, pStream->Regs.picb));
+ }
+
+ /*
+ * Fetch a new buffer descriptor if we've exhausted the current one.
+ */
+ if (!pStream->Regs.picb)
+ {
+ uint32_t fNewSr = pStream->Regs.sr & ~AC97_SR_CELV;
+
+ if (pStream->Regs.bd.ctl_len & AC97_BD_IOC)
+ fNewSr |= AC97_SR_BCIS;
+
+ if (pStream->Regs.civ != pStream->Regs.lvi)
+ fNewSr |= ichac97R3StreamFetchNextBdle(pDevIns, pStream, pStreamCC);
+ else
+ {
+ LogFunc(("Underrun CIV (%RU8) == LVI (%RU8)\n", pStream->Regs.civ, pStream->Regs.lvi));
+ fNewSr |= AC97_SR_LVBCI | AC97_SR_DCH | AC97_SR_CELV;
+ pThis->bup_flag = (pStream->Regs.bd.ctl_len & AC97_BD_BUP) ? BUP_LAST : 0;
+ /** @todo r=bird: The bup_flag isn't cleared anywhere else. We should probably
+ * do what the spec says, and keep writing zeros (silence).
+ * Alternatively, we could hope the guest will pause the DMA engine
+ * immediately after seeing this condition, in which case we should
+ * stop the DMA timer from being re-armed. */
+ }
+
+ ichac97StreamUpdateSR(pDevIns, pThis, pStream, fNewSr);
+ }
+
+ ichac97R3StreamUnlock(pStreamCC);
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Input streams: Pulls data from the mixer, putting it in the internal DMA
+ * buffer.
+ *
+ * @param pStreamR3 The AC'97 stream (ring-3 bits).
+ * @param pSink The mixer sink to pull from.
+ */
+static void ichac97R3StreamPullFromMixer(PAC97STREAMR3 pStreamR3, PAUDMIXSINK pSink)
+{
+# ifdef LOG_ENABLED
+ uint64_t const offWriteOld = pStreamR3->State.offWrite;
+# endif
+ pStreamR3->State.offWrite = AudioMixerSinkTransferToCircBuf(pSink,
+ pStreamR3->State.pCircBuf,
+ pStreamR3->State.offWrite,
+ pStreamR3->u8SD,
+ pStreamR3->Dbg.Runtime.fEnabled
+ ? pStreamR3->Dbg.Runtime.pFileStream : NULL);
+
+ Log3Func(("[SD%RU8] transferred=%#RX64 bytes -> @%#RX64\n", pStreamR3->u8SD,
+ pStreamR3->State.offWrite - offWriteOld, pStreamR3->State.offWrite));
+
+ /* Update buffer stats. */
+ pStreamR3->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf);
+}
+
+
+/**
+ * Output streams: Pushes data to the mixer.
+ *
+ * @param pStreamR3 The AC'97 stream (ring-3 bits).
+ * @param pSink The mixer sink to push to.
+ */
+static void ichac97R3StreamPushToMixer(PAC97STREAMR3 pStreamR3, PAUDMIXSINK pSink)
+{
+# ifdef LOG_ENABLED
+ uint64_t const offReadOld = pStreamR3->State.offRead;
+# endif
+ pStreamR3->State.offRead = AudioMixerSinkTransferFromCircBuf(pSink,
+ pStreamR3->State.pCircBuf,
+ pStreamR3->State.offRead,
+ pStreamR3->u8SD,
+ pStreamR3->Dbg.Runtime.fEnabled
+ ? pStreamR3->Dbg.Runtime.pFileStream : NULL);
+
+ Log3Func(("[SD%RU8] transferred=%#RX64 bytes -> @%#RX64\n", pStreamR3->u8SD,
+ pStreamR3->State.offRead - offReadOld, pStreamR3->State.offRead));
+
+ /* Update buffer stats. */
+ pStreamR3->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf);
+}
+
+
+/**
+ * Updates an AC'97 stream by doing its DMA transfers.
+ *
+ * The host sink(s) set the overall pace (bird: no it doesn't, the DMA timer
+ * does - we just hope like heck it matches the speed at which the *backend*
+ * host audio driver processes samples).
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 state.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param pStream The AC'97 stream to update (shared).
+ * @param pStreamCC The AC'97 stream to update (ring-3).
+ * @param pSink The sink being updated.
+ */
+static void ichac97R3StreamUpdateDma(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC,
+ PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, PAUDMIXSINK pSink)
+{
+ RT_NOREF(pThisCC);
+ int rc2;
+
+ /* The amount we're supposed to be transfering in this DMA period. */
+ uint32_t cbPeriod = pStream->cbDmaPeriod;
+
+ /*
+ * Output streams (SDO).
+ */
+ if (pStreamCC->State.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ /*
+ * Check how much room we have in our DMA buffer. There should be at
+ * least one period worth of space there or we're in an overflow situation.
+ */
+ uint32_t cbStreamFree = ichac97R3StreamGetFree(pStreamCC);
+ if (cbStreamFree >= cbPeriod)
+ { /* likely */ }
+ else
+ {
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaFlowProblems);
+ LogFunc(("Warning! Stream #%u has insufficient space free: %u bytes, need %u. Will try move data out of the buffer...\n",
+ pStreamCC->u8SD, cbStreamFree, cbPeriod));
+ int rc = AudioMixerSinkTryLock(pSink);
+ if (RT_SUCCESS(rc))
+ {
+ ichac97R3StreamPushToMixer(pStreamCC, pSink);
+ AudioMixerSinkUpdate(pSink, 0, 0);
+ AudioMixerSinkUnlock(pSink);
+ }
+ else
+ RTThreadYield();
+ LogFunc(("Gained %u bytes.\n", ichac97R3StreamGetFree(pStreamCC) - cbStreamFree));
+
+ cbStreamFree = ichac97R3StreamGetFree(pStreamCC);
+ if (cbStreamFree < cbPeriod)
+ {
+ /* Unable to make sufficient space. Drop the whole buffer content.
+ * This is needed in order to keep the device emulation running at a constant rate,
+ * at the cost of losing valid (but too much) data. */
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaFlowErrors);
+ LogRel2(("AC97: Warning: Hit stream #%RU8 overflow, dropping %u bytes of audio data\n",
+ pStreamCC->u8SD, ichac97R3StreamGetUsed(pStreamCC)));
+# ifdef AC97_STRICT
+ AssertMsgFailed(("Hit stream #%RU8 overflow -- timing bug?\n", pStreamCC->u8SD));
+# endif
+ RTCircBufReset(pStreamCC->State.pCircBuf);
+ pStreamCC->State.offWrite = 0;
+ pStreamCC->State.offRead = 0;
+ cbStreamFree = ichac97R3StreamGetFree(pStreamCC);
+ Assert(cbStreamFree >= cbPeriod);
+ }
+ }
+
+ /*
+ * Do the DMA transfer.
+ */
+ Log3Func(("[SD%RU8] PICB=%#x samples / %RU64 ms, cbFree=%#x / %RU64 ms, cbTransferChunk=%#x / %RU64 ms\n", pStream->u8SD,
+ pStream->Regs.picb, PDMAudioPropsBytesToMilli(&pStreamCC->State.Cfg.Props,
+ PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props)
+ * pStream->Regs.picb),
+ cbStreamFree, PDMAudioPropsBytesToMilli(&pStreamCC->State.Cfg.Props, cbStreamFree),
+ cbPeriod, PDMAudioPropsBytesToMilli(&pStreamCC->State.Cfg.Props, cbPeriod)));
+
+ rc2 = ichac97R3StreamTransfer(pDevIns, pThis, pStream, pStreamCC, RT_MIN(cbStreamFree, cbPeriod),
+ false /*fWriteSilence*/, false /*fInput*/);
+ AssertRC(rc2);
+
+ pStreamCC->State.tsLastUpdateNs = RTTimeNanoTS();
+
+
+ /*
+ * Notify the AIO thread.
+ */
+ rc2 = AudioMixerSinkSignalUpdateJob(pSink);
+ AssertRC(rc2);
+ }
+ /*
+ * Input stream (SDI).
+ */
+ else
+ {
+ /*
+ * See how much data we've got buffered...
+ */
+ bool fWriteSilence = false;
+ uint32_t cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC);
+ if (pStreamCC->State.fInputPreBuffered && cbStreamUsed >= cbPeriod)
+ { /*likely*/ }
+ /*
+ * Because it may take a while for the input stream to get going (at least
+ * with pulseaudio), we feed the guest silence till we've pre-buffer a
+ * couple of timer Hz periods. (This avoid lots of bogus buffer underruns
+ * when starting an input stream and hogging the timer EMT.)
+ */
+ else if (!pStreamCC->State.fInputPreBuffered)
+ {
+ uint32_t const cbPreBuffer = PDMAudioPropsNanoToBytes(&pStreamCC->State.Cfg.Props,
+ RT_NS_1SEC / pStreamCC->State.uTimerHz);
+ if (cbStreamUsed < cbPreBuffer)
+ {
+ Log3Func(("Pre-buffering (got %#x out of %#x bytes)...\n", cbStreamUsed, cbPreBuffer));
+ fWriteSilence = true;
+ cbStreamUsed = cbPeriod;
+ }
+ else
+ {
+ Log3Func(("Completed pre-buffering (got %#x, needed %#x bytes).\n", cbStreamUsed, cbPreBuffer));
+ pStreamCC->State.fInputPreBuffered = true;
+ fWriteSilence = ichac97R3StreamGetFree(pStreamCC) >= cbPreBuffer + cbPreBuffer / 2;
+ if (fWriteSilence)
+ cbStreamUsed = cbPeriod;
+ }
+ }
+ /*
+ * When we're low on data, we must really try fetch some ourselves
+ * as buffer underruns must not happen.
+ */
+ else
+ {
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaFlowProblems);
+ LogFunc(("Warning! Stream #%u has insufficient data available: %u bytes, need %u. Will try move pull more data into the buffer...\n",
+ pStreamCC->u8SD, cbStreamUsed, cbPeriod));
+ int rc = AudioMixerSinkTryLock(pSink);
+ if (RT_SUCCESS(rc))
+ {
+ AudioMixerSinkUpdate(pSink, cbStreamUsed, cbPeriod);
+ ichac97R3StreamPullFromMixer(pStreamCC, pSink);
+ AudioMixerSinkUnlock(pSink);
+ }
+ else
+ RTThreadYield();
+ LogFunc(("Gained %u bytes.\n", ichac97R3StreamGetUsed(pStreamCC) - cbStreamUsed));
+ cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC);
+ if (cbStreamUsed < cbPeriod)
+ {
+ /* Unable to find sufficient input data by simple prodding.
+ In order to keep a constant byte stream following thru the DMA
+ engine into the guest, we will try again and then fall back on
+ filling the gap with silence. */
+ uint32_t cbSilence = 0;
+ do
+ {
+ AudioMixerSinkLock(pSink);
+
+ cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC);
+ if (cbStreamUsed < cbPeriod)
+ {
+ ichac97R3StreamPullFromMixer(pStreamCC, pSink);
+ cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC);
+ while (cbStreamUsed < cbPeriod)
+ {
+ void *pvDstBuf;
+ size_t cbDstBuf;
+ RTCircBufAcquireWriteBlock(pStreamCC->State.pCircBuf, cbPeriod - cbStreamUsed,
+ &pvDstBuf, &cbDstBuf);
+ RT_BZERO(pvDstBuf, cbDstBuf);
+ RTCircBufReleaseWriteBlock(pStreamCC->State.pCircBuf, cbDstBuf);
+ cbSilence += (uint32_t)cbDstBuf;
+ cbStreamUsed += (uint32_t)cbDstBuf;
+ }
+ }
+
+ AudioMixerSinkUnlock(pSink);
+ } while (cbStreamUsed < cbPeriod);
+ if (cbSilence > 0)
+ {
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaFlowErrors);
+ STAM_REL_COUNTER_ADD(&pStreamCC->State.StatDmaFlowErrorBytes, cbSilence);
+ LogRel2(("AC97: Warning: Stream #%RU8 underrun, added %u bytes of silence (%u us)\n", pStreamCC->u8SD,
+ cbSilence, PDMAudioPropsBytesToMicro(&pStreamCC->State.Cfg.Props, cbSilence)));
+ }
+ }
+ }
+
+ /*
+ * Do the DMA'ing.
+ */
+ if (cbStreamUsed)
+ {
+ rc2 = ichac97R3StreamTransfer(pDevIns, pThis, pStream, pStreamCC, RT_MIN(cbPeriod, cbStreamUsed),
+ fWriteSilence, true /*fInput*/);
+ AssertRC(rc2);
+
+ pStreamCC->State.tsLastUpdateNs = RTTimeNanoTS();
+ }
+
+ /*
+ * We should always kick the AIO thread.
+ */
+ /** @todo This isn't entirely ideal. If we get into an underrun situation,
+ * we ideally want the AIO thread to run right before the DMA timer
+ * rather than right after it ran. */
+ Log5Func(("Notifying AIO thread\n"));
+ rc2 = AudioMixerSinkSignalUpdateJob(pSink);
+ AssertRC(rc2);
+ }
+}
+
+
+/**
+ * @callback_method_impl{FNAUDMIXSINKUPDATE}
+ *
+ * For output streams this moves data from the internal DMA buffer (in which
+ * ichac97R3StreamUpdateDma put it), thru the mixer and to the various backend
+ * audio devices.
+ *
+ * For input streams this pulls data from the backend audio device(s), thru the
+ * mixer and puts it in the internal DMA buffer ready for
+ * ichac97R3StreamUpdateDma to pump into guest memory.
+ */
+static DECLCALLBACK(void) ichac97R3StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser)
+{
+ PAC97STATER3 const pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ PAC97STREAMR3 const pStreamCC = (PAC97STREAMR3)pvUser;
+ Assert(pStreamCC->u8SD == (uintptr_t)(pStreamCC - &pThisCC->aStreams[0]));
+ Assert(pSink == ichac97R3IndexToSink(pThisCC, pStreamCC->u8SD));
+ RT_NOREF(pThisCC);
+
+ /*
+ * Output (SDO).
+ */
+ if (pStreamCC->State.Cfg.enmDir == PDMAUDIODIR_OUT)
+ ichac97R3StreamPushToMixer(pStreamCC, pSink);
+ /*
+ * Input (SDI).
+ */
+ else
+ ichac97R3StreamPullFromMixer(pStreamCC, pSink);
+}
+
+
+/**
+ * Updates the next transfer based on a specific amount of bytes.
+ *
+ * @param pDevIns The device instance.
+ * @param pStream The AC'97 stream to update (shared).
+ * @param pStreamCC The AC'97 stream to update (ring-3).
+ */
+static void ichac97R3StreamTransferUpdate(PPDMDEVINS pDevIns, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC)
+{
+ /*
+ * Get the number of bytes left in the current buffer.
+ *
+ * This isn't entirely optimal iff the current entry doesn't have IOC set, in
+ * that case we should use the number of bytes to the next IOC. Unfortuantely,
+ * it seems the spec doesn't allow us to prefetch more than one BDLE, so we
+ * probably cannot look ahead without violating that restriction. This is
+ * probably a purely theoretical problem at this point.
+ */
+ uint32_t const cbLeftInBdle = pStream->Regs.picb * PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props);
+ if (cbLeftInBdle > 0) /** @todo r=bird: see todo about this in ichac97R3StreamFetchBDLE. */
+ {
+ /*
+ * Since the buffer can be up to 0xfffe samples long (frame aligning stereo
+ * prevents 0xffff), which translates to 743ms at a 44.1kHz rate, we must
+ * also take the nominal timer frequency into account here so we keep
+ * moving data at a steady rate. (In theory, I think the guest can even
+ * set up just one buffer and anticipate where we are in the buffer
+ * processing when it writes/reads from it. Linux seems to be doing such
+ * configs when not playing or something.)
+ */
+ uint32_t const cbMaxPerHz = PDMAudioPropsNanoToBytes(&pStreamCC->State.Cfg.Props, RT_NS_1SEC / pStreamCC->State.uTimerHz);
+
+ if (cbLeftInBdle <= cbMaxPerHz)
+ pStream->cbDmaPeriod = cbLeftInBdle;
+ /* Try avoid leaving a very short period at the end of a buffer. */
+ else if (cbLeftInBdle >= cbMaxPerHz + cbMaxPerHz / 2)
+ pStream->cbDmaPeriod = cbMaxPerHz;
+ else
+ pStream->cbDmaPeriod = PDMAudioPropsFloorBytesToFrame(&pStreamCC->State.Cfg.Props, cbLeftInBdle / 2);
+
+ /*
+ * Translate the chunk size to timer ticks.
+ */
+ uint64_t const cNsXferChunk = PDMAudioPropsBytesToNano(&pStreamCC->State.Cfg.Props, pStream->cbDmaPeriod);
+ pStream->cDmaPeriodTicks = PDMDevHlpTimerFromNano(pDevIns, pStream->hTimer, cNsXferChunk);
+ Assert(pStream->cDmaPeriodTicks > 0);
+
+ Log3Func(("[SD%RU8] cbLeftInBdle=%#RX32 cbMaxPerHz=%#RX32 (%RU16Hz) -> cbDmaPeriod=%#RX32 cDmaPeriodTicks=%RX64\n",
+ pStream->u8SD, cbLeftInBdle, cbMaxPerHz, pStreamCC->State.uTimerHz, pStream->cbDmaPeriod, pStream->cDmaPeriodTicks));
+ }
+}
+
+
+/**
+ * Sets the virtual device timer to a new expiration time.
+ *
+ * @param pDevIns The device instance.
+ * @param pStream AC'97 stream to set timer for.
+ * @param cTicksToDeadline The number of ticks to the new deadline.
+ *
+ * @remarks This used to be more complicated a long time ago...
+ */
+DECLINLINE(void) ichac97R3TimerSet(PPDMDEVINS pDevIns, PAC97STREAM pStream, uint64_t cTicksToDeadline)
+{
+ int rc = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimer, cTicksToDeadline, &pStream->uArmedTs);
+ AssertRC(rc);
+}
+
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV,
+ * Timer callback which handles the audio data transfers on a periodic basis.}
+ */
+static DECLCALLBACK(void) ichac97R3Timer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ STAM_PROFILE_START(&pThis->StatTimer, a);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ PAC97STREAM pStream = (PAC97STREAM)pvUser;
+ PAC97STREAMR3 pStreamCC = &RT_SAFE_SUBSCRIPT8(pThisCC->aStreams, pStream->u8SD);
+ Assert(hTimer == pStream->hTimer); RT_NOREF(hTimer);
+
+ Assert(pStream - &pThis->aStreams[0] == pStream->u8SD);
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect));
+ Assert(PDMDevHlpTimerIsLockOwner(pDevIns, pStream->hTimer));
+
+ PAUDMIXSINK pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD);
+ if (pSink && AudioMixerSinkIsActive(pSink))
+ {
+ ichac97R3StreamUpdateDma(pDevIns, pThis, pThisCC, pStream, pStreamCC, pSink);
+
+ pStream->uDmaPeriod++;
+ ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC);
+ ichac97R3TimerSet(pDevIns, pStream, pStream->cDmaPeriodTicks);
+ }
+
+ STAM_PROFILE_STOP(&pThis->StatTimer, a);
+}
+
+#endif /* IN_RING3 */
+
+
+/*********************************************************************************************************************************
+* AC'97 Stream Management *
+*********************************************************************************************************************************/
+#ifdef IN_RING3
+
+/**
+ * Locks an AC'97 stream for serialized access.
+ *
+ * @returns VBox status code.
+ * @param pStreamCC The AC'97 stream to lock (ring-3).
+ */
+DECLINLINE(void) ichac97R3StreamLock(PAC97STREAMR3 pStreamCC)
+{
+ int rc2 = RTCritSectEnter(&pStreamCC->State.CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Unlocks a formerly locked AC'97 stream.
+ *
+ * @returns VBox status code.
+ * @param pStreamCC The AC'97 stream to unlock (ring-3).
+ */
+DECLINLINE(void) ichac97R3StreamUnlock(PAC97STREAMR3 pStreamCC)
+{
+ int rc2 = RTCritSectLeave(&pStreamCC->State.CritSect);
+ AssertRC(rc2);
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Updates the status register (SR) of an AC'97 audio stream.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 state.
+ * @param pStream AC'97 stream to update SR for.
+ * @param new_sr New value for status register (SR).
+ */
+static void ichac97StreamUpdateSR(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, uint32_t new_sr)
+{
+ bool fSignal = false;
+ int iIRQL = 0;
+
+ uint32_t new_mask = new_sr & AC97_SR_INT_MASK;
+ uint32_t old_mask = pStream->Regs.sr & AC97_SR_INT_MASK;
+
+ if (new_mask ^ old_mask)
+ {
+ /** @todo Is IRQ deasserted when only one of status bits is cleared? */
+ if (!new_mask)
+ {
+ fSignal = true;
+ iIRQL = 0;
+ }
+ else if ((new_mask & AC97_SR_LVBCI) && (pStream->Regs.cr & AC97_CR_LVBIE))
+ {
+ fSignal = true;
+ iIRQL = 1;
+ }
+ else if ((new_mask & AC97_SR_BCIS) && (pStream->Regs.cr & AC97_CR_IOCE))
+ {
+ fSignal = true;
+ iIRQL = 1;
+ }
+ }
+
+ pStream->Regs.sr = new_sr;
+
+ LogFlowFunc(("IOC%d, LVB%d, sr=%#x, fSignal=%RTbool, IRQL=%d\n",
+ pStream->Regs.sr & AC97_SR_BCIS, pStream->Regs.sr & AC97_SR_LVBCI, pStream->Regs.sr, fSignal, iIRQL));
+
+ if (fSignal)
+ {
+ static uint32_t const s_aMasks[] = { AC97_GS_PIINT, AC97_GS_POINT, AC97_GS_MINT };
+ Assert(pStream->u8SD < AC97_MAX_STREAMS);
+ if (iIRQL)
+ pThis->glob_sta |= s_aMasks[pStream->u8SD];
+ else
+ pThis->glob_sta &= ~s_aMasks[pStream->u8SD];
+
+ LogFlowFunc(("Setting IRQ level=%d\n", iIRQL));
+ PDMDevHlpPCISetIrq(pDevIns, 0, iIRQL);
+ }
+}
+
+/**
+ * Writes a new value to a stream's status register (SR).
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 device state.
+ * @param pStream Stream to update SR for.
+ * @param u32Val New value to set the stream's SR to.
+ */
+static void ichac97StreamWriteSR(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, uint32_t u32Val)
+{
+ Log3Func(("[SD%RU8] SR <- %#x (sr %#x)\n", pStream->u8SD, u32Val, pStream->Regs.sr));
+
+ pStream->Regs.sr |= u32Val & ~(AC97_SR_RO_MASK | AC97_SR_WCLEAR_MASK);
+ ichac97StreamUpdateSR(pDevIns, pThis, pStream, pStream->Regs.sr & ~(u32Val & AC97_SR_WCLEAR_MASK));
+}
+
+#ifdef IN_RING3
+
+/**
+ * Resets an AC'97 stream.
+ *
+ * @param pThis The shared AC'97 state.
+ * @param pStream The AC'97 stream to reset (shared).
+ * @param pStreamCC The AC'97 stream to reset (ring-3).
+ */
+static void ichac97R3StreamReset(PAC97STATE pThis, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC)
+{
+ ichac97R3StreamLock(pStreamCC);
+
+ LogFunc(("[SD%RU8]\n", pStream->u8SD));
+
+ if (pStreamCC->State.pCircBuf)
+ RTCircBufReset(pStreamCC->State.pCircBuf);
+
+ pStream->Regs.bdbar = 0;
+ pStream->Regs.civ = 0;
+ pStream->Regs.lvi = 0;
+
+ pStream->Regs.picb = 0;
+ pStream->Regs.piv = 0; /* Note! Because this is also zero, we will actually start transferring with BDLE00. */
+ pStream->Regs.cr &= AC97_CR_DONT_CLEAR_MASK;
+ pStream->Regs.bd_valid = 0;
+
+ RT_ZERO(pThis->silence);
+
+ ichac97R3StreamUnlock(pStreamCC);
+}
+
+/**
+ * Retrieves a specific driver stream of a AC'97 driver.
+ *
+ * @returns Pointer to driver stream if found, or NULL if not found.
+ * @param pDrv Driver to retrieve driver stream for.
+ * @param enmDir Stream direction to retrieve.
+ * @param enmPath Stream destination / source to retrieve.
+ */
+static PAC97DRIVERSTREAM ichac97R3MixerGetDrvStream(PAC97DRIVER pDrv, PDMAUDIODIR enmDir, PDMAUDIOPATH enmPath)
+{
+ if (enmDir == PDMAUDIODIR_IN)
+ {
+ LogFunc(("enmRecSource=%d\n", enmPath));
+ switch (enmPath)
+ {
+ case PDMAUDIOPATH_IN_LINE:
+ return &pDrv->LineIn;
+ case PDMAUDIOPATH_IN_MIC:
+ return &pDrv->MicIn;
+ default:
+ AssertFailedBreak();
+ }
+ }
+ else if (enmDir == PDMAUDIODIR_OUT)
+ {
+ LogFunc(("enmPlaybackDst=%d\n", enmPath));
+ switch (enmPath)
+ {
+ case PDMAUDIOPATH_OUT_FRONT:
+ return &pDrv->Out;
+ default:
+ AssertFailedBreak();
+ }
+ }
+ else
+ AssertFailed();
+
+ return NULL;
+}
+
+/**
+ * Adds a driver stream to a specific mixer sink.
+ *
+ * Called by ichac97R3MixerAddDrvStreams() and ichac97R3MixerAddDrv().
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pMixSink Mixer sink to add driver stream to.
+ * @param pCfg Stream configuration to use.
+ * @param pDrv Driver stream to add.
+ */
+static int ichac97R3MixerAddDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg, PAC97DRIVER pDrv)
+{
+ AssertPtrReturn(pMixSink, VERR_INVALID_POINTER);
+ LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pCfg->szName));
+
+ int rc;
+ PAC97DRIVERSTREAM pDrvStream = ichac97R3MixerGetDrvStream(pDrv, pCfg->enmDir, pCfg->enmPath);
+ if (pDrvStream)
+ {
+ AssertMsg(pDrvStream->pMixStrm == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN));
+
+ PAUDMIXSTREAM pMixStrm;
+ rc = AudioMixerSinkCreateStream(pMixSink, pDrv->pConnector, pCfg, pDevIns, &pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc));
+ if (RT_SUCCESS(rc))
+ {
+ rc = AudioMixerSinkAddStream(pMixSink, pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc));
+ if (RT_SUCCESS(rc))
+ pDrvStream->pMixStrm = pMixStrm;
+ else
+ AudioMixerStreamDestroy(pMixStrm, pDevIns, true /*fImmediate*/);
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Adds all current driver streams to a specific mixer sink.
+ *
+ * Called by ichac97R3StreamSetUp().
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param pMixSink Mixer sink to add stream to.
+ * @param pCfg Stream configuration to use.
+ */
+static int ichac97R3MixerAddDrvStreams(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pMixSink, VERR_INVALID_POINTER);
+
+ int rc;
+ if (AudioHlpStreamCfgIsValid(pCfg))
+ {
+ rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props, pCfg->Device.cMsSchedulingHint);
+ if (RT_SUCCESS(rc))
+ {
+ PAC97DRIVER pDrv;
+ RTListForEach(&pThisCC->lstDrv, pDrv, AC97DRIVER, Node)
+ {
+ int rc2 = ichac97R3MixerAddDrvStream(pDevIns, 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. */
+ }
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Removes a driver stream from a specific mixer sink.
+ *
+ * Worker for ichac97R3MixerRemoveDrvStreams.
+ *
+ * @param pDevIns The device instance.
+ * @param pMixSink Mixer sink to remove audio streams from.
+ * @param enmDir Stream direction to remove.
+ * @param enmPath Stream destination / source to remove.
+ * @param pDrv Driver stream to remove.
+ */
+static void ichac97R3MixerRemoveDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PDMAUDIODIR enmDir,
+ PDMAUDIOPATH enmPath, PAC97DRIVER pDrv)
+{
+ PAC97DRIVERSTREAM pDrvStream = ichac97R3MixerGetDrvStream(pDrv, enmDir, enmPath);
+ if (pDrvStream)
+ {
+ if (pDrvStream->pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pMixSink, pDrvStream->pMixStrm);
+
+ AudioMixerStreamDestroy(pDrvStream->pMixStrm, pDevIns, false /*fImmediate*/);
+ pDrvStream->pMixStrm = NULL;
+ }
+ }
+}
+
+/**
+ * Removes all driver streams from a specific mixer sink.
+ *
+ * Called by ichac97R3StreamSetUp() and ichac97R3StreamsDestroy().
+ *
+ * @param pDevIns The device instance.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param pMixSink Mixer sink to remove audio streams from.
+ * @param enmDir Stream direction to remove.
+ * @param enmPath Stream destination / source to remove.
+ */
+static void ichac97R3MixerRemoveDrvStreams(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, PAUDMIXSINK pMixSink,
+ PDMAUDIODIR enmDir, PDMAUDIOPATH enmPath)
+{
+ AssertPtrReturnVoid(pMixSink);
+
+ PAC97DRIVER pDrv;
+ RTListForEach(&pThisCC->lstDrv, pDrv, AC97DRIVER, Node)
+ {
+ ichac97R3MixerRemoveDrvStream(pDevIns, pMixSink, enmDir, enmPath, pDrv);
+ }
+}
+
+
+/**
+ * Gets the frequency of a given stream.
+ *
+ * @returns The frequency. Zero if invalid stream index.
+ * @param pThis The shared AC'97 device state.
+ * @param idxStream The stream.
+ */
+DECLINLINE(uint32_t) ichach97R3CalcStreamHz(PAC97STATE pThis, uint8_t idxStream)
+{
+ switch (idxStream)
+ {
+ case AC97SOUNDSOURCE_PI_INDEX:
+ return ichac97MixerGet(pThis, AC97_PCM_LR_ADC_Rate);
+
+ case AC97SOUNDSOURCE_MC_INDEX:
+ return ichac97MixerGet(pThis, AC97_MIC_ADC_Rate);
+
+ case AC97SOUNDSOURCE_PO_INDEX:
+ return ichac97MixerGet(pThis, AC97_PCM_Front_DAC_Rate);
+
+ default:
+ AssertMsgFailedReturn(("%d\n", idxStream), 0);
+ }
+}
+
+
+/**
+ * Gets the PCM properties for a given stream.
+ *
+ * @returns pProps.
+ * @param pThis The shared AC'97 device state.
+ * @param idxStream Which stream
+ * @param pProps Where to return the stream properties.
+ */
+DECLINLINE(PPDMAUDIOPCMPROPS) ichach97R3CalcStreamProps(PAC97STATE pThis, uint8_t idxStream, PPDMAUDIOPCMPROPS pProps)
+{
+ PDMAudioPropsInit(pProps, 2 /*16-bit*/, true /*signed*/, 2 /*stereo*/, ichach97R3CalcStreamHz(pThis, idxStream));
+ return pProps;
+}
+
+
+/**
+ * Sets up an AC'97 stream with its current mixer settings.
+ *
+ * This will set up an AC'97 stream with 2 (stereo) channels, 16-bit samples and
+ * the last set sample rate in the AC'97 mixer for this stream.
+ *
+ * @returns VBox status code.
+ * @retval VINF_NO_CHANGE if the streams weren't re-created.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 device state (shared).
+ * @param pThisCC The shared AC'97 device state (ring-3).
+ * @param pStream The AC'97 stream to open (shared).
+ * @param pStreamCC The AC'97 stream to open (ring-3).
+ * @param fForce Whether to force re-opening the stream or not.
+ * Otherwise re-opening only will happen if the PCM properties have changed.
+ *
+ * @remarks This is called holding:
+ * -# The AC'97 device lock.
+ * -# The AC'97 stream lock.
+ * -# The mixer sink lock (to prevent racing AIO thread).
+ */
+static int ichac97R3StreamSetUp(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC, PAC97STREAM pStream,
+ PAC97STREAMR3 pStreamCC, bool fForce)
+{
+ /*
+ * Assemble the stream config and get the associated mixer sink.
+ */
+ PDMAUDIOPCMPROPS PropsTmp;
+ PDMAUDIOSTREAMCFG Cfg;
+ PDMAudioStrmCfgInitWithProps(&Cfg, ichach97R3CalcStreamProps(pThis, pStream->u8SD, &PropsTmp));
+ Assert(Cfg.enmDir != PDMAUDIODIR_UNKNOWN);
+
+ PAUDMIXSINK pMixSink;
+ switch (pStream->u8SD)
+ {
+ case AC97SOUNDSOURCE_PI_INDEX:
+ Cfg.enmDir = PDMAUDIODIR_IN;
+ Cfg.enmPath = PDMAUDIOPATH_IN_LINE;
+ RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Line-In");
+
+ pMixSink = pThisCC->pSinkLineIn;
+ break;
+
+ case AC97SOUNDSOURCE_MC_INDEX:
+ Cfg.enmDir = PDMAUDIODIR_IN;
+ Cfg.enmPath = PDMAUDIOPATH_IN_MIC;
+ RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Mic-In");
+
+ pMixSink = pThisCC->pSinkMicIn;
+ break;
+
+ case AC97SOUNDSOURCE_PO_INDEX:
+ Cfg.enmDir = PDMAUDIODIR_OUT;
+ Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
+ RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Output");
+
+ pMixSink = pThisCC->pSinkOut;
+ break;
+
+ default:
+ AssertMsgFailedReturn(("u8SD=%d\n", pStream->u8SD), VERR_INTERNAL_ERROR_3);
+ }
+
+ /*
+ * Don't continue if the frequency is out of range (the rest of the
+ * properties should be okay).
+ * Note! Don't assert on this as we may easily end up here with Hz=0.
+ */
+ char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX];
+ if (AudioHlpStreamCfgIsValid(&Cfg))
+ { }
+ else
+ {
+ LogFunc(("Invalid stream #%u rate: %s\n", pStreamCC->u8SD, PDMAudioStrmCfgToString(&Cfg, szTmp, sizeof(szTmp)) ));
+ return VERR_OUT_OF_RANGE;
+ }
+
+ /*
+ * Read the buffer descriptors and check what the max distance between
+ * interrupts are, so we can more correctly size the internal DMA buffer.
+ *
+ * Note! The buffer list are not fixed once the stream starts running as
+ * with HDA, so this is just a general idea of what the guest is
+ * up to and we cannot really make much of a plan out of it.
+ */
+ uint8_t const bLvi = pStream->Regs.lvi % AC97_MAX_BDLE /* paranoia */;
+ uint8_t const bCiv = pStream->Regs.civ % AC97_MAX_BDLE /* paranoia */;
+ uint32_t const uAddrBdl = pStream->Regs.bdbar;
+
+ /* Linux does this a number of times while probing/whatever the device. The
+ IOMMU usually does allow us to read address zero, so let's skip and hope
+ for a better config before the guest actually wants to play/record.
+ (Note that bLvi and bCiv are also zero then, but I'm not entirely sure if
+ that can be taken to mean anything as such, as it still indicates that
+ BDLE00 is valid (LVI == last valid index).) */
+ /** @todo Instead of refusing to read address zero, we should probably allow
+ * reading address zero if explicitly programmed. But, too much work now. */
+ if (uAddrBdl != 0)
+ LogFlowFunc(("bdbar=%#x bLvi=%#x bCiv=%#x\n", uAddrBdl, bLvi, bCiv));
+ else
+ {
+ LogFunc(("Invalid stream #%u: bdbar=%#x bLvi=%#x bCiv=%#x (%s)\n", pStreamCC->u8SD, uAddrBdl, bLvi, bCiv,
+ PDMAudioStrmCfgToString(&Cfg, szTmp, sizeof(szTmp))));
+ return VERR_OUT_OF_RANGE;
+ }
+
+ AC97BDLE aBdl[AC97_MAX_BDLE];
+ RT_ZERO(aBdl);
+ PDMDevHlpPCIPhysRead(pDevIns, uAddrBdl, aBdl, sizeof(aBdl));
+
+ uint32_t cSamplesMax = 0;
+ uint32_t cSamplesMin = UINT32_MAX;
+ uint32_t cSamplesCur = 0;
+ uint32_t cSamplesTotal = 0;
+ uint32_t cBuffers = 1;
+ for (uintptr_t i = bCiv; ; cBuffers++)
+ {
+ Log2Func(("BDLE%02u: %#x LB %#x; %#x\n", i, aBdl[i].addr, aBdl[i].ctl_len & AC97_BD_LEN_MASK, aBdl[i].ctl_len >> 16));
+ cSamplesTotal += aBdl[i].ctl_len & AC97_BD_LEN_MASK;
+ cSamplesCur += aBdl[i].ctl_len & AC97_BD_LEN_MASK;
+ if (aBdl[i].ctl_len & AC97_BD_IOC)
+ {
+ if (cSamplesCur > cSamplesMax)
+ cSamplesMax = cSamplesCur;
+ if (cSamplesCur < cSamplesMin)
+ cSamplesMin = cSamplesCur;
+ cSamplesCur = 0;
+ }
+
+ /* Advance. */
+ if (i != bLvi)
+ i = (i + 1) % RT_ELEMENTS(aBdl);
+ else
+ break;
+ }
+ if (!cSamplesCur)
+ { /* likely */ }
+ else if (!cSamplesMax)
+ {
+ LogFlowFunc(("%u buffers without IOC set, assuming %#x samples as the IOC period.\n", cBuffers, cSamplesMax));
+ cSamplesMin = cSamplesMax = cSamplesCur;
+ }
+ else if (cSamplesCur > cSamplesMax)
+ {
+ LogFlowFunc(("final buffer is without IOC, using open period as max (%#x vs current max %#x).\n", cSamplesCur, cSamplesMax));
+ cSamplesMax = cSamplesCur;
+ }
+ else
+ LogFlowFunc(("final buffer is without IOC, ignoring (%#x vs current max %#x).\n", cSamplesCur, cSamplesMax));
+
+ uint32_t const cbDmaMinBuf = cSamplesMax * PDMAudioPropsSampleSize(&Cfg.Props) * 3; /* see further down */
+ uint32_t const cMsDmaMinBuf = PDMAudioPropsBytesToMilli(&Cfg.Props, cbDmaMinBuf);
+ LogRel3(("AC97: [SD%RU8] buffer length stats: total=%#x in %u buffers, min=%#x, max=%#x => min DMA buffer %u ms / %#x bytes\n",
+ pStream->u8SD, cSamplesTotal, cBuffers, cSamplesMin, cSamplesMax, cMsDmaMinBuf, cbDmaMinBuf));
+
+ /*
+ * Calculate the timer Hz / scheduling hint based on the stream frame rate.
+ */
+ uint32_t uTimerHz;
+ if (pThis->uTimerHz == AC97_TIMER_HZ_DEFAULT) /* Make sure that we don't have any custom Hz rate set we want to enforce */
+ {
+ if (Cfg.Props.uHz > 44100) /* E.g. 48000 Hz. */
+ uTimerHz = 200;
+ else
+ uTimerHz = AC97_TIMER_HZ_DEFAULT;
+ }
+ else
+ uTimerHz = pThis->uTimerHz;
+
+ if ( uTimerHz >= 10
+ && uTimerHz <= 500)
+ { /* likely */ }
+ else
+ {
+ LogFunc(("[SD%RU8] Adjusting uTimerHz=%u to %u\n", pStream->u8SD, uTimerHz,
+ Cfg.Props.uHz > 44100 ? 200 : AC97_TIMER_HZ_DEFAULT));
+ uTimerHz = Cfg.Props.uHz > 44100 ? 200 : AC97_TIMER_HZ_DEFAULT;
+ }
+
+ /* Translate it to a scheduling hint. */
+ uint32_t const cMsSchedulingHint = RT_MS_1SEC / uTimerHz;
+
+ /*
+ * Calculate the circular buffer size so we can decide whether to recreate
+ * the stream or not.
+ *
+ * As mentioned in the HDA code, this should be at least able to hold the
+ * data transferred in three DMA periods and in three AIO period (whichever
+ * is higher). However, if we assume that the DMA code will engage the DMA
+ * timer thread (currently EMT) if the AIO thread isn't getting schduled to
+ * transfer data thru the stack, we don't need to go overboard and double
+ * the minimums here. The less buffer the less possible delay can build when
+ * TM is doing catch up.
+ */
+ uint32_t cMsCircBuf = Cfg.enmDir == PDMAUDIODIR_IN ? pThis->cMsCircBufIn : pThis->cMsCircBufOut;
+ cMsCircBuf = RT_MAX(cMsCircBuf, cMsDmaMinBuf);
+ cMsCircBuf = RT_MAX(cMsCircBuf, cMsSchedulingHint * 3);
+ cMsCircBuf = RT_MIN(cMsCircBuf, RT_MS_1SEC * 2);
+ uint32_t const cbCircBuf = PDMAudioPropsMilliToBytes(&Cfg.Props, cMsCircBuf);
+
+ LogFlowFunc(("Stream %u: uTimerHz: %u -> %u; cMsSchedulingHint: %u -> %u; cbCircBuf: %#zx -> %#x (%u ms, cMsDmaMinBuf=%u)%s\n",
+ pStreamCC->u8SD, pStreamCC->State.uTimerHz, uTimerHz,
+ pStreamCC->State.Cfg.Device.cMsSchedulingHint, cMsSchedulingHint,
+ pStreamCC->State.pCircBuf ? RTCircBufSize(pStreamCC->State.pCircBuf) : 0, cbCircBuf, cMsCircBuf, cMsDmaMinBuf,
+ !pStreamCC->State.pCircBuf || RTCircBufSize(pStreamCC->State.pCircBuf) != cbCircBuf ? " - re-creating DMA buffer" : ""));
+
+ /*
+ * Update the stream's timer rate and scheduling hint, re-registering the AIO
+ * update job if necessary.
+ */
+ if ( pStreamCC->State.Cfg.Device.cMsSchedulingHint != cMsSchedulingHint
+ || !pStreamCC->State.fRegisteredAsyncUpdateJob)
+ {
+ if (pStreamCC->State.fRegisteredAsyncUpdateJob)
+ AudioMixerSinkRemoveUpdateJob(pMixSink, ichac97R3StreamUpdateAsyncIoJob, pStreamCC);
+ int rc2 = AudioMixerSinkAddUpdateJob(pMixSink, ichac97R3StreamUpdateAsyncIoJob, pStreamCC,
+ pStreamCC->State.Cfg.Device.cMsSchedulingHint);
+ AssertRC(rc2);
+ pStreamCC->State.fRegisteredAsyncUpdateJob = RT_SUCCESS(rc2) || rc2 == VERR_ALREADY_EXISTS;
+ }
+
+ pStreamCC->State.uTimerHz = uTimerHz;
+ Cfg.Device.cMsSchedulingHint = cMsSchedulingHint;
+
+ /*
+ * Re-create the circular buffer if necessary, resetting if not.
+ */
+ if ( pStreamCC->State.pCircBuf
+ && RTCircBufSize(pStreamCC->State.pCircBuf) == cbCircBuf)
+ RTCircBufReset(pStreamCC->State.pCircBuf);
+ else
+ {
+ if (pStreamCC->State.pCircBuf)
+ RTCircBufDestroy(pStreamCC->State.pCircBuf);
+
+ int rc = RTCircBufCreate(&pStreamCC->State.pCircBuf, cbCircBuf);
+ AssertRCReturnStmt(rc, pStreamCC->State.pCircBuf = NULL, rc);
+
+ pStreamCC->State.StatDmaBufSize = (uint32_t)RTCircBufSize(pStreamCC->State.pCircBuf);
+ }
+ Assert(pStreamCC->State.StatDmaBufSize == cbCircBuf);
+
+ /*
+ * Only (re-)create the stream (and driver chain) if we really have to.
+ * Otherwise avoid this and just reuse it, as this costs performance.
+ */
+ int rc = VINF_SUCCESS;
+ if ( fForce
+ || !PDMAudioStrmCfgMatchesProps(&Cfg, &pStreamCC->State.Cfg.Props)
+ || (pStreamCC->State.nsRetrySetup && RTTimeNanoTS() >= pStreamCC->State.nsRetrySetup))
+ {
+ LogRel2(("AC97: Setting up stream #%u: %s\n", pStreamCC->u8SD, PDMAudioStrmCfgToString(&Cfg, szTmp, sizeof(szTmp)) ));
+
+ ichac97R3MixerRemoveDrvStreams(pDevIns, pThisCC, pMixSink, Cfg.enmDir, Cfg.enmPath);
+
+ rc = ichac97R3MixerAddDrvStreams(pDevIns, pThisCC, pMixSink, &Cfg);
+ if (RT_SUCCESS(rc))
+ {
+ PDMAudioStrmCfgCopy(&pStreamCC->State.Cfg, &Cfg);
+ pStreamCC->State.nsRetrySetup = 0;
+ LogFlowFunc(("[SD%RU8] success (uHz=%u)\n", pStreamCC->u8SD, PDMAudioPropsHz(&Cfg.Props)));
+ }
+ else
+ {
+ LogFunc(("[SD%RU8] ichac97R3MixerAddDrvStreams failed: %Rrc (uHz=%u)\n",
+ pStreamCC->u8SD, rc, PDMAudioPropsHz(&Cfg.Props)));
+ pStreamCC->State.nsRetrySetup = RTTimeNanoTS() + 5*RT_NS_1SEC_64; /* retry in 5 seconds, unless config changes. */
+ }
+ }
+ else
+ {
+ LogFlowFunc(("[SD%RU8] Skipping set-up (unchanged: %s)\n",
+ pStreamCC->u8SD, PDMAudioStrmCfgToString(&Cfg, szTmp, sizeof(szTmp))));
+ rc = VINF_NO_CHANGE;
+ }
+ return rc;
+}
+
+
+/**
+ * Tears down an AC'97 stream (counter part to ichac97R3StreamSetUp).
+ *
+ * Empty stub at present, nothing to do here as we reuse streams and only really
+ * re-open them if parameters changed (seldom).
+ *
+ * @param pStream The AC'97 stream to close (shared).
+ */
+static void ichac97R3StreamTearDown(PAC97STREAM pStream)
+{
+ RT_NOREF(pStream);
+ LogFlowFunc(("[SD%RU8]\n", pStream->u8SD));
+}
+
+
+/**
+ * Tears down and sets up an AC'97 stream on the backend side with the current
+ * AC'97 mixer settings for this stream.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 device state.
+ * @param pThisCC The ring-3 AC'97 device state.
+ * @param pStream The AC'97 stream to re-open (shared).
+ * @param pStreamCC The AC'97 stream to re-open (ring-3).
+ * @param fForce Whether to force re-opening the stream or not.
+ * Otherwise re-opening only will happen if the PCM properties have changed.
+ */
+static int ichac97R3StreamReSetUp(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC,
+ PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, bool fForce)
+{
+ STAM_REL_PROFILE_START_NS(&pStreamCC->State.StatReSetUpChanged, r);
+ LogFlowFunc(("[SD%RU8]\n", pStream->u8SD));
+ Assert(pStream->u8SD == pStreamCC->u8SD);
+ Assert(pStream - &pThis->aStreams[0] == pStream->u8SD);
+ Assert(pStreamCC - &pThisCC->aStreams[0] == pStream->u8SD);
+
+ ichac97R3StreamTearDown(pStream);
+ int rc = ichac97R3StreamSetUp(pDevIns, pThis, pThisCC, pStream, pStreamCC, fForce);
+ if (rc == VINF_NO_CHANGE)
+ STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatReSetUpSame, r);
+ else
+ STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatReSetUpChanged, r);
+ return rc;
+}
+
+
+/**
+ * Enables or disables an AC'97 audio stream.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 state.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param pStream The AC'97 stream to enable or disable (shared state).
+ * @param pStreamCC The ring-3 stream state (matching to @a pStream).
+ * @param fEnable Whether to enable or disable the stream.
+ *
+ */
+static int ichac97R3StreamEnable(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC,
+ PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, bool fEnable)
+{
+ ichac97R3StreamLock(pStreamCC);
+ PAUDMIXSINK const pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD);
+ AudioMixerSinkLock(pSink);
+
+ int rc = VINF_SUCCESS;
+ /*
+ * Enable.
+ */
+ if (fEnable)
+ {
+ /* Reset the input pre-buffering state and DMA period counter. */
+ pStreamCC->State.fInputPreBuffered = false;
+ pStream->uDmaPeriod = 0;
+
+ /* Set up (update) the AC'97 stream as needed. */
+ rc = ichac97R3StreamSetUp(pDevIns, pThis, pThisCC, pStream, pStreamCC, false /* fForce */);
+ if (RT_SUCCESS(rc))
+ {
+ /* Open debug files. */
+ if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled))
+ { /* likely */ }
+ else
+ {
+ if (!AudioHlpFileIsOpen(pStreamCC->Dbg.Runtime.pFileStream))
+ AudioHlpFileOpen(pStreamCC->Dbg.Runtime.pFileStream, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS,
+ &pStreamCC->State.Cfg.Props);
+ if (!AudioHlpFileIsOpen(pStreamCC->Dbg.Runtime.pFileDMA))
+ AudioHlpFileOpen(pStreamCC->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS,
+ &pStreamCC->State.Cfg.Props);
+ }
+
+ /* Do the actual enabling (won't fail as long as pSink is valid). */
+ rc = AudioMixerSinkStart(pSink);
+ }
+ }
+ /*
+ * Disable
+ */
+ else
+ {
+ rc = AudioMixerSinkDrainAndStop(pSink, pStreamCC->State.pCircBuf ? (uint32_t)RTCircBufUsed(pStreamCC->State.pCircBuf) : 0);
+ ichac97R3StreamTearDown(pStream);
+ }
+
+ /* Make sure to leave the lock before (eventually) starting the timer. */
+ AudioMixerSinkUnlock(pSink);
+ ichac97R3StreamUnlock(pStreamCC);
+ LogFunc(("[SD%RU8] fEnable=%RTbool, rc=%Rrc\n", pStream->u8SD, fEnable, rc));
+ return rc;
+}
+
+
+/**
+ * Returns whether an AC'97 stream is enabled or not.
+ *
+ * Only used by ichac97R3SaveExec().
+ *
+ * @returns VBox status code.
+ * @param pThisCC The ring-3 AC'97 device state.
+ * @param pStream Stream to return status for.
+ */
+static bool ichac97R3StreamIsEnabled(PAC97STATER3 pThisCC, PAC97STREAM pStream)
+{
+ PAUDMIXSINK pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD);
+ bool fIsEnabled = pSink && (AudioMixerSinkGetStatus(pSink) & AUDMIXSINK_STS_RUNNING);
+
+ LogFunc(("[SD%RU8] fIsEnabled=%RTbool\n", pStream->u8SD, fIsEnabled));
+ return fIsEnabled;
+}
+
+
+/**
+ * Terminates an AC'97 audio stream (VM destroy).
+ *
+ * This is called by ichac97R3StreamsDestroy during VM poweroff & destruction.
+ *
+ * @returns VBox status code.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param pStream The AC'97 stream to destroy (shared).
+ * @param pStreamCC The AC'97 stream to destroy (ring-3).
+ * @sa ichac97R3StreamConstruct
+ */
+static void ichac97R3StreamDestroy(PAC97STATER3 pThisCC, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC)
+{
+ LogFlowFunc(("[SD%RU8]\n", pStream->u8SD));
+
+ ichac97R3StreamTearDown(pStream);
+
+ int rc2 = RTCritSectDelete(&pStreamCC->State.CritSect);
+ AssertRC(rc2);
+
+ if (pStreamCC->State.fRegisteredAsyncUpdateJob)
+ {
+ PAUDMIXSINK pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD);
+ if (pSink)
+ AudioMixerSinkRemoveUpdateJob(pSink, ichac97R3StreamUpdateAsyncIoJob, pStreamCC);
+ pStreamCC->State.fRegisteredAsyncUpdateJob = false;
+ }
+
+ if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled))
+ { /* likely */ }
+ else
+ {
+ AudioHlpFileDestroy(pStreamCC->Dbg.Runtime.pFileStream);
+ pStreamCC->Dbg.Runtime.pFileStream = NULL;
+
+ AudioHlpFileDestroy(pStreamCC->Dbg.Runtime.pFileDMA);
+ pStreamCC->Dbg.Runtime.pFileDMA = NULL;
+ }
+
+ if (pStreamCC->State.pCircBuf)
+ {
+ RTCircBufDestroy(pStreamCC->State.pCircBuf);
+ pStreamCC->State.pCircBuf = NULL;
+ }
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * Initializes an AC'97 audio stream (VM construct).
+ *
+ * This is only called by ichac97R3Construct.
+ *
+ * @returns VBox status code.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param pStream The AC'97 stream to create (shared).
+ * @param pStreamCC The AC'97 stream to create (ring-3).
+ * @param u8SD Stream descriptor number to assign.
+ * @sa ichac97R3StreamDestroy
+ */
+static int ichac97R3StreamConstruct(PAC97STATER3 pThisCC, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, uint8_t u8SD)
+{
+ LogFunc(("[SD%RU8] pStream=%p\n", u8SD, pStream));
+
+ AssertReturn(u8SD < AC97_MAX_STREAMS, VERR_INVALID_PARAMETER);
+ pStream->u8SD = u8SD;
+ pStreamCC->u8SD = u8SD;
+
+ int rc = RTCritSectInit(&pStreamCC->State.CritSect);
+ AssertRCReturn(rc, rc);
+
+ pStreamCC->Dbg.Runtime.fEnabled = pThisCC->Dbg.fEnabled;
+
+ if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled))
+ { /* likely */ }
+ else
+ {
+ int rc2 = AudioHlpFileCreateF(&pStreamCC->Dbg.Runtime.pFileStream, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV,
+ pThisCC->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/,
+ ichac97R3GetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN
+ ? "ac97StreamWriteSD%RU8" : "ac97StreamReadSD%RU8", pStream->u8SD);
+ AssertRC(rc2);
+
+ rc2 = AudioHlpFileCreateF(&pStreamCC->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV,
+ pThisCC->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/,
+ ichac97R3GetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN
+ ? "ac97DMAWriteSD%RU8" : "ac97DMAReadSD%RU8", pStream->u8SD);
+ AssertRC(rc2);
+
+ /* Delete stale debugging files from a former run. */
+ AudioHlpFileDelete(pStreamCC->Dbg.Runtime.pFileStream);
+ AudioHlpFileDelete(pStreamCC->Dbg.Runtime.pFileDMA);
+ }
+
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+
+/*********************************************************************************************************************************
+* NABM I/O Port Handlers (Global + Stream) *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ichac97IoPortNabmRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ RT_NOREF(pvUser);
+
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_READ);
+
+ /* Get the index of the NABMBAR port. */
+ if ( AC97_PORT2IDX_UNMASKED(offPort) < AC97_MAX_STREAMS
+ && offPort != AC97_GLOB_CNT)
+ {
+ PAC97STREAM pStream = &pThis->aStreams[AC97_PORT2IDX(offPort)];
+
+ switch (cb)
+ {
+ case 1:
+ switch (offPort & AC97_NABM_OFF_MASK)
+ {
+ case AC97_NABM_OFF_CIV:
+ /* Current Index Value Register */
+ *pu32 = pStream->Regs.civ;
+ Log3Func(("CIV[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ case AC97_NABM_OFF_LVI:
+ /* Last Valid Index Register */
+ *pu32 = pStream->Regs.lvi;
+ Log3Func(("LVI[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ case AC97_NABM_OFF_PIV:
+ /* Prefetched Index Value Register */
+ *pu32 = pStream->Regs.piv;
+ Log3Func(("PIV[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ case AC97_NABM_OFF_CR:
+ /* Control Register */
+ *pu32 = pStream->Regs.cr;
+ Log3Func(("CR[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ case AC97_NABM_OFF_SR:
+ /* Status Register (lower part) */
+ *pu32 = RT_LO_U8(pStream->Regs.sr);
+ Log3Func(("SRb[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ default:
+ *pu32 = UINT32_MAX;
+ LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads);
+ break;
+ }
+ break;
+
+ case 2:
+ switch (offPort & AC97_NABM_OFF_MASK)
+ {
+ case AC97_NABM_OFF_SR:
+ /* Status Register */
+ *pu32 = pStream->Regs.sr;
+ Log3Func(("SR[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ case AC97_NABM_OFF_PICB:
+ /* Position in Current Buffer
+ * ---
+ * We can do DMA work here if we want to give the guest a better impression of
+ * the DMA engine of a real device. For ring-0 we'd have to add some buffering
+ * to AC97STREAM (4K or so), only going to ring-3 if full. Ring-3 would commit
+ * that buffer and write directly to the internal DMA pCircBuf.
+ *
+ * Checking a Linux guest (knoppix 8.6.2), I see some PIC reads each DMA cycle,
+ * however most of these happen very very early, 1-10% into the buffer. So, I'm
+ * not sure if it's worth it, as it'll be a big complication... */
+#if 1
+ *pu32 = pStream->Regs.picb;
+# ifdef LOG_ENABLED
+ if (LogIs3Enabled())
+ {
+ uint64_t offPeriod = PDMDevHlpTimerGet(pDevIns, pStream->hTimer) - pStream->uArmedTs;
+ Log3Func(("PICB[%d] -> %#x (%RU64 of %RU64 ticks / %RU64%% into DMA period #%RU32)\n",
+ AC97_PORT2IDX(offPort), *pu32, offPeriod, pStream->cDmaPeriodTicks,
+ pStream->cDmaPeriodTicks ? offPeriod * 100 / pStream->cDmaPeriodTicks : 0,
+ pStream->uDmaPeriod));
+ }
+# endif
+#else /* For trying out sub-buffer PICB. Will cause distortions, but can be helpful to see if it help eliminate other issues. */
+ if ( (pStream->Regs.cr & AC97_CR_RPBM)
+ && !(pStream->Regs.sr & AC97_SR_DCH)
+ && pStream->uArmedTs > 0
+ && pStream->cDmaPeriodTicks > 0)
+ {
+ uint64_t const offPeriod = PDMDevHlpTimerGet(pDevIns, pStream->hTimer) - pStream->uArmedTs;
+ uint32_t cSamples;
+ if (offPeriod < pStream->cDmaPeriodTicks)
+ cSamples = pStream->Regs.picb * offPeriod / pStream->cDmaPeriodTicks;
+ else
+ cSamples = pStream->Regs.picb;
+ if (cSamples + 8 < pStream->Regs.picb)
+ { /* likely */ }
+ else if (pStream->Regs.picb > 8)
+ cSamples = pStream->Regs.picb - 8;
+ else
+ cSamples = 0;
+ *pu32 = pStream->Regs.picb - cSamples;
+ Log3Func(("PICB[%d] -> %#x (PICB=%#x cSamples=%#x offPeriod=%RU64 of %RU64 / %RU64%%)\n",
+ AC97_PORT2IDX(offPort), *pu32, pStream->Regs.picb, cSamples, offPeriod,
+ pStream->cDmaPeriodTicks, offPeriod * 100 / pStream->cDmaPeriodTicks));
+ }
+ else
+ {
+ *pu32 = pStream->Regs.picb;
+ Log3Func(("PICB[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ }
+#endif
+ break;
+ default:
+ *pu32 = UINT32_MAX;
+ LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads);
+ break;
+ }
+ break;
+
+ case 4:
+ switch (offPort & AC97_NABM_OFF_MASK)
+ {
+ case AC97_NABM_OFF_BDBAR:
+ /* Buffer Descriptor Base Address Register */
+ *pu32 = pStream->Regs.bdbar;
+ Log3Func(("BMADDR[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ case AC97_NABM_OFF_CIV:
+ /* 32-bit access: Current Index Value Register +
+ * Last Valid Index Register +
+ * Status Register */
+ *pu32 = pStream->Regs.civ | ((uint32_t)pStream->Regs.lvi << 8) | ((uint32_t)pStream->Regs.sr << 16);
+ Log3Func(("CIV LVI SR[%d] -> %#x, %#x, %#x\n",
+ AC97_PORT2IDX(offPort), pStream->Regs.civ, pStream->Regs.lvi, pStream->Regs.sr));
+ break;
+ case AC97_NABM_OFF_PICB:
+ /* 32-bit access: Position in Current Buffer Register +
+ * Prefetched Index Value Register +
+ * Control Register */
+ *pu32 = pStream->Regs.picb | ((uint32_t)pStream->Regs.piv << 16) | ((uint32_t)pStream->Regs.cr << 24);
+ Log3Func(("PICB PIV CR[%d] -> %#x %#x %#x %#x\n",
+ AC97_PORT2IDX(offPort), *pu32, pStream->Regs.picb, pStream->Regs.piv, pStream->Regs.cr));
+ break;
+
+ default:
+ *pu32 = UINT32_MAX;
+ LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads);
+ break;
+ }
+ break;
+
+ default:
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ AssertFailed();
+ return VERR_IOM_IOPORT_UNUSED;
+ }
+ }
+ else
+ {
+ switch (cb)
+ {
+ case 1:
+ switch (offPort)
+ {
+ case AC97_CAS:
+ /* Codec Access Semaphore Register */
+ Log3Func(("CAS %d\n", pThis->cas));
+ *pu32 = pThis->cas;
+ pThis->cas = 1;
+ break;
+ default:
+ *pu32 = UINT32_MAX;
+ LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads);
+ break;
+ }
+ break;
+
+ case 2:
+ *pu32 = UINT32_MAX;
+ LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads);
+ break;
+
+ case 4:
+ switch (offPort)
+ {
+ case AC97_GLOB_CNT:
+ /* Global Control */
+ *pu32 = pThis->glob_cnt;
+ Log3Func(("glob_cnt -> %#x\n", *pu32));
+ break;
+ case AC97_GLOB_STA:
+ /* Global Status */
+ *pu32 = pThis->glob_sta | AC97_GS_S0CR;
+ Log3Func(("glob_sta -> %#x\n", *pu32));
+ break;
+ default:
+ *pu32 = UINT32_MAX;
+ LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads);
+ break;
+ }
+ break;
+
+ default:
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ AssertFailed();
+ return VERR_IOM_IOPORT_UNUSED;
+ }
+ }
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ichac97IoPortNabmWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+#ifdef IN_RING3
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+#endif
+ RT_NOREF(pvUser);
+
+ VBOXSTRICTRC rc = VINF_SUCCESS;
+ if ( AC97_PORT2IDX_UNMASKED(offPort) < AC97_MAX_STREAMS
+ && offPort != AC97_GLOB_CNT)
+ {
+#ifdef IN_RING3
+ PAC97STREAMR3 pStreamCC = &pThisCC->aStreams[AC97_PORT2IDX(offPort)];
+#endif
+ PAC97STREAM pStream = &pThis->aStreams[AC97_PORT2IDX(offPort)];
+
+ switch (cb)
+ {
+ case 1:
+ switch (offPort & AC97_NABM_OFF_MASK)
+ {
+ /*
+ * Last Valid Index.
+ */
+ case AC97_NABM_OFF_LVI:
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+
+ if ( !(pStream->Regs.sr & AC97_SR_DCH)
+ || !(pStream->Regs.cr & AC97_CR_RPBM))
+ {
+ pStream->Regs.lvi = u32 % AC97_MAX_BDLE;
+ STAM_REL_COUNTER_INC(&pStream->StatWriteLvi);
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ Log3Func(("[SD%RU8] LVI <- %#x\n", pStream->u8SD, u32));
+ }
+ else
+ {
+#ifdef IN_RING3
+ /* Recover from underflow situation where CIV caught up with LVI
+ and the DMA processing stopped. We clear the status condition,
+ update LVI and then try to load the next BDLE. Unfortunately,
+ we cannot do this from ring-3 as much of the BDLE state is
+ ring-3 only. */
+ pStream->Regs.sr &= ~(AC97_SR_DCH | AC97_SR_CELV);
+ pStream->Regs.lvi = u32 % AC97_MAX_BDLE;
+ if (ichac97R3StreamFetchNextBdle(pDevIns, pStream, pStreamCC))
+ ichac97StreamUpdateSR(pDevIns, pThis, pStream, pStream->Regs.sr | AC97_SR_BCIS);
+
+ /* We now have to re-arm the DMA timer according to the new BDLE length.
+ This means leaving the device lock to avoid virtual sync lock order issues. */
+ ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC);
+ uint64_t const cTicksToDeadline = pStream->cDmaPeriodTicks;
+
+ /** @todo Stop the DMA timer when we get into the AC97_SR_CELV situation to
+ * avoid potential race here. */
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatWriteLviRecover);
+ DEVAC97_UNLOCK(pDevIns, pThis);
+
+ LogFunc(("[SD%RU8] LVI <- %#x; CIV=%#x PIV=%#x SR=%#x cTicksToDeadline=%#RX64 [recovering]\n",
+ pStream->u8SD, u32, pStream->Regs.civ, pStream->Regs.piv, pStream->Regs.sr, cTicksToDeadline));
+
+ int rc2 = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimer, cTicksToDeadline, &pStream->uArmedTs);
+ AssertRC(rc2);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ }
+ break;
+
+ /*
+ * Control Registers.
+ */
+ case AC97_NABM_OFF_CR:
+ {
+#ifdef IN_RING3
+ DEVAC97_LOCK(pDevIns, pThis);
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatWriteCr);
+
+ uint32_t const fCrChanged = pStream->Regs.cr ^ u32;
+ Log3Func(("[SD%RU8] CR <- %#x (was %#x; changed %#x)\n", pStream->u8SD, u32, pStream->Regs.cr, fCrChanged));
+
+ /*
+ * Busmaster reset.
+ */
+ if (u32 & AC97_CR_RR)
+ {
+ STAM_REL_PROFILE_START_NS(&pStreamCC->State.StatReset, r);
+ LogFunc(("[SD%RU8] Reset\n", pStream->u8SD));
+
+ /* Make sure that Run/Pause Bus Master bit (RPBM) is cleared (0).
+ 3.2.7 in 302349-003 says RPBM be must be clear when resetting
+ and that behavior is undefined if it's set. */
+ ASSERT_GUEST_STMT((pStream->Regs.cr & AC97_CR_RPBM) == 0,
+ ichac97R3StreamEnable(pDevIns, pThis, pThisCC, pStream,
+ pStreamCC, false /* fEnable */));
+
+ ichac97R3StreamReset(pThis, pStream, pStreamCC);
+
+ ichac97StreamUpdateSR(pDevIns, pThis, pStream, AC97_SR_DCH); /** @todo Do we need to do that? */
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatReset, r);
+ break;
+ }
+
+ /*
+ * Write the new value to the register and if RPBM didn't change we're done.
+ */
+ pStream->Regs.cr = u32 & AC97_CR_VALID_MASK;
+
+ if (!(fCrChanged & AC97_CR_RPBM))
+ DEVAC97_UNLOCK(pDevIns, pThis); /* Probably not so likely, but avoid one extra intentation level. */
+ /*
+ * Pause busmaster.
+ */
+ else if (!(pStream->Regs.cr & AC97_CR_RPBM))
+ {
+ STAM_REL_PROFILE_START_NS(&pStreamCC->State.StatStop, p);
+ LogFunc(("[SD%RU8] Pause busmaster (disable stream) SR=%#x -> %#x\n",
+ pStream->u8SD, pStream->Regs.sr, pStream->Regs.sr | AC97_SR_DCH));
+ ichac97R3StreamEnable(pDevIns, pThis, pThisCC, pStream, pStreamCC, false /* fEnable */);
+ pStream->Regs.sr |= AC97_SR_DCH;
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatStop, p);
+ }
+ /*
+ * Run busmaster.
+ */
+ else
+ {
+ STAM_REL_PROFILE_START_NS(&pStreamCC->State.StatStart, r);
+ LogFunc(("[SD%RU8] Run busmaster (enable stream) SR=%#x -> %#x\n",
+ pStream->u8SD, pStream->Regs.sr, pStream->Regs.sr & ~AC97_SR_DCH));
+ pStream->Regs.sr &= ~AC97_SR_DCH;
+
+ if (ichac97R3StreamFetchNextBdle(pDevIns, pStream, pStreamCC))
+ ichac97StreamUpdateSR(pDevIns, pThis, pStream, pStream->Regs.sr | AC97_SR_BCIS);
+# ifdef LOG_ENABLED
+ if (LogIsFlowEnabled())
+ ichac97R3DbgPrintBdl(pDevIns, pThis, pStream, PDMDevHlpDBGFInfoLogHlp(pDevIns), "ichac97IoPortNabmWrite: ");
+# endif
+ ichac97R3StreamEnable(pDevIns, pThis, pThisCC, pStream, pStreamCC, true /* fEnable */);
+
+ /*
+ * Arm the DMA timer. Must drop the AC'97 device lock first as it would
+ * create a lock order violation with the virtual sync time lock otherwise.
+ */
+ ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC);
+ uint64_t const cTicksToDeadline = pStream->cDmaPeriodTicks;
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+
+ /** @todo for output streams we could probably service this a little bit
+ * earlier if we push it, just to reduce the lag... For HDA we do a
+ * DMA run immediately after the stream is enabled. */
+ int rc2 = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimer, cTicksToDeadline, &pStream->uArmedTs);
+ AssertRC(rc2);
+
+ STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatStart, r);
+ }
+#else /* !IN_RING3 */
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ }
+
+ /*
+ * Status Registers.
+ */
+ case AC97_NABM_OFF_SR:
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+ ichac97StreamWriteSR(pDevIns, pThis, pStream, u32);
+ STAM_REL_COUNTER_INC(&pStream->StatWriteSr1);
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ break;
+
+ default:
+ /* Linux tries to write CIV. */
+ LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x%s <- %#x LB 1 (line " RT_XSTR(__LINE__) ")\n",
+ offPort, (offPort & AC97_NABM_OFF_MASK) == AC97_NABM_OFF_CIV ? " (CIV)" : "" , u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites);
+ break;
+ }
+ break;
+
+ case 2:
+ switch (offPort & AC97_NABM_OFF_MASK)
+ {
+ case AC97_NABM_OFF_SR:
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+ ichac97StreamWriteSR(pDevIns, pThis, pStream, u32);
+ STAM_REL_COUNTER_INC(&pStream->StatWriteSr2);
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ break;
+ default:
+ LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites);
+ break;
+ }
+ break;
+
+ case 4:
+ switch (offPort & AC97_NABM_OFF_MASK)
+ {
+ case AC97_NABM_OFF_BDBAR:
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+ /* Buffer Descriptor list Base Address Register */
+ pStream->Regs.bdbar = u32 & ~(uint32_t)3;
+ Log3Func(("[SD%RU8] BDBAR <- %#x (bdbar %#x)\n", AC97_PORT2IDX(offPort), u32, pStream->Regs.bdbar));
+ STAM_REL_COUNTER_INC(&pStream->StatWriteBdBar);
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ break;
+ default:
+ LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites);
+ break;
+ }
+ break;
+
+ default:
+ AssertMsgFailed(("offPort=%#x <- %#x LB %u\n", offPort, u32, cb));
+ break;
+ }
+ }
+ else
+ {
+ switch (cb)
+ {
+ case 1:
+ LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites);
+ break;
+
+ case 2:
+ LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites);
+ break;
+
+ case 4:
+ switch (offPort)
+ {
+ case AC97_GLOB_CNT:
+ /* Global Control */
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+ if (u32 & AC97_GC_WR)
+ ichac97WarmReset(pThis);
+ if (u32 & AC97_GC_CR)
+ ichac97ColdReset(pThis);
+ if (!(u32 & (AC97_GC_WR | AC97_GC_CR)))
+ pThis->glob_cnt = u32 & AC97_GC_VALID_MASK;
+ Log3Func(("glob_cnt <- %#x (glob_cnt %#x)\n", u32, pThis->glob_cnt));
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ break;
+ case AC97_GLOB_STA:
+ /* Global Status */
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+ pThis->glob_sta &= ~(u32 & AC97_GS_WCLEAR_MASK);
+ pThis->glob_sta |= (u32 & ~(AC97_GS_WCLEAR_MASK | AC97_GS_RO_MASK)) & AC97_GS_VALID_MASK;
+ Log3Func(("glob_sta <- %#x (glob_sta %#x)\n", u32, pThis->glob_sta));
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ break;
+ default:
+ LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites);
+ break;
+ }
+ break;
+
+ default:
+ AssertMsgFailed(("offPort=%#x <- %#x LB %u\n", offPort, u32, cb));
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* Mixer & NAM I/O handlers *
+*********************************************************************************************************************************/
+
+/**
+ * Sets a AC'97 mixer control to a specific value.
+ *
+ * @returns VBox status code.
+ * @param pThis The shared AC'97 state.
+ * @param uMixerIdx Mixer control to set value for.
+ * @param uVal Value to set.
+ */
+static void ichac97MixerSet(PAC97STATE pThis, uint8_t uMixerIdx, uint16_t uVal)
+{
+ AssertMsgReturnVoid(uMixerIdx + 2U <= sizeof(pThis->mixer_data),
+ ("Index %RU8 out of bounds (%zu)\n", uMixerIdx, sizeof(pThis->mixer_data)));
+
+ LogRel2(("AC97: Setting mixer index #%RU8 to %RU16 (%RU8 %RU8)\n", uMixerIdx, uVal, RT_HI_U8(uVal), RT_LO_U8(uVal)));
+
+ pThis->mixer_data[uMixerIdx + 0] = RT_LO_U8(uVal);
+ pThis->mixer_data[uMixerIdx + 1] = RT_HI_U8(uVal);
+}
+
+
+/**
+ * Gets a value from a specific AC'97 mixer control.
+ *
+ * @returns Retrieved mixer control value.
+ * @param pThis The shared AC'97 state.
+ * @param uMixerIdx Mixer control to get value for.
+ */
+static uint16_t ichac97MixerGet(PAC97STATE pThis, uint32_t uMixerIdx)
+{
+ AssertMsgReturn(uMixerIdx + 2U <= sizeof(pThis->mixer_data),
+ ("Index %RU8 out of bounds (%zu)\n", uMixerIdx, sizeof(pThis->mixer_data)),
+ UINT16_MAX);
+ return RT_MAKE_U16(pThis->mixer_data[uMixerIdx + 0], pThis->mixer_data[uMixerIdx + 1]);
+}
+
+#ifdef IN_RING3
+
+/**
+ * Sets the volume of a specific AC'97 mixer control.
+ *
+ * This currently only supports attenuation -- gain support is currently not implemented.
+ *
+ * @returns VBox status code.
+ * @param pThis The shared AC'97 state.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param index AC'97 mixer index to set volume for.
+ * @param enmMixerCtl Corresponding audio mixer sink.
+ * @param uVal Volume value to set.
+ */
+static int ichac97R3MixerSetVolume(PAC97STATE pThis, PAC97STATER3 pThisCC, int index, PDMAUDIOMIXERCTL enmMixerCtl, uint32_t uVal)
+{
+ /*
+ * From AC'97 SoundMax Codec AD1981A/AD1981B:
+ * "Because AC '97 defines 6-bit volume registers, to maintain compatibility whenever the
+ * D5 or D13 bits are set to 1, their respective lower five volume bits are automatically
+ * set to 1 by the Codec logic. On readback, all lower 5 bits will read ones whenever
+ * these bits are set to 1."
+ *
+ * Linux ALSA depends on this behavior to detect that only 5 bits are used for volume
+ * control and the optional 6th bit is not used. Note that this logic only applies to the
+ * master volume controls.
+ */
+ if ( index == AC97_Master_Volume_Mute
+ || index == AC97_Headphone_Volume_Mute
+ || index == AC97_Master_Volume_Mono_Mute)
+ {
+ if (uVal & RT_BIT(5)) /* D5 bit set? */
+ uVal |= RT_BIT(4) | RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0);
+ if (uVal & RT_BIT(13)) /* D13 bit set? */
+ uVal |= RT_BIT(12) | RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8);
+ }
+
+ const bool fCtlMuted = (uVal >> AC97_BARS_VOL_MUTE_SHIFT) & 1;
+ uint8_t uCtlAttLeft = (uVal >> 8) & AC97_BARS_VOL_MASK;
+ uint8_t uCtlAttRight = uVal & AC97_BARS_VOL_MASK;
+
+ /* For the master and headphone volume, 0 corresponds to 0dB attenuation. For the other
+ * volume controls, 0 means 12dB gain and 8 means unity gain.
+ */
+ if (index != AC97_Master_Volume_Mute && index != AC97_Headphone_Volume_Mute)
+ {
+# ifndef VBOX_WITH_AC97_GAIN_SUPPORT
+ /* NB: Currently there is no gain support, only attenuation. */
+ uCtlAttLeft = uCtlAttLeft < 8 ? 0 : uCtlAttLeft - 8;
+ uCtlAttRight = uCtlAttRight < 8 ? 0 : uCtlAttRight - 8;
+# endif
+ }
+ Assert(uCtlAttLeft <= 255 / AC97_DB_FACTOR);
+ Assert(uCtlAttRight <= 255 / AC97_DB_FACTOR);
+
+ LogFunc(("index=0x%x, uVal=%RU32, enmMixerCtl=%RU32\n", index, uVal, enmMixerCtl));
+ LogFunc(("uCtlAttLeft=%RU8, uCtlAttRight=%RU8 ", uCtlAttLeft, uCtlAttRight));
+
+ /*
+ * For AC'97 volume controls, each additional step means -1.5dB attenuation with
+ * zero being maximum. In contrast, we're internally using 255 (PDMAUDIO_VOLUME_MAX)
+ * steps, each -0.375dB, where 0 corresponds to -96dB and 255 corresponds to 0dB.
+ */
+ uint8_t lVol = PDMAUDIO_VOLUME_MAX - uCtlAttLeft * AC97_DB_FACTOR;
+ uint8_t rVol = PDMAUDIO_VOLUME_MAX - uCtlAttRight * AC97_DB_FACTOR;
+
+ Log(("-> fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n", fCtlMuted, lVol, rVol));
+
+ int rc = VINF_SUCCESS;
+
+ if (pThisCC->pMixer) /* Device can be in reset state, so no mixer available. */
+ {
+ PDMAUDIOVOLUME Vol;
+ PDMAudioVolumeInitFromStereo(&Vol, fCtlMuted, lVol, rVol);
+
+ PAUDMIXSINK pSink = NULL;
+ switch (enmMixerCtl)
+ {
+ case PDMAUDIOMIXERCTL_VOLUME_MASTER:
+ rc = AudioMixerSetMasterVolume(pThisCC->pMixer, &Vol);
+ break;
+
+ case PDMAUDIOMIXERCTL_FRONT:
+ pSink = pThisCC->pSinkOut;
+ break;
+
+ case PDMAUDIOMIXERCTL_MIC_IN:
+ case PDMAUDIOMIXERCTL_LINE_IN:
+ /* These are recognized but do nothing. */
+ break;
+
+ default:
+ AssertFailed();
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (pSink)
+ rc = AudioMixerSinkSetVolume(pSink, &Vol);
+ }
+
+ ichac97MixerSet(pThis, index, uVal);
+
+ if (RT_FAILURE(rc))
+ LogFlowFunc(("Failed with %Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * Sets the gain of a specific AC'97 recording control.
+ *
+ * @note Gain support is currently not implemented in PDM audio.
+ *
+ * @returns VBox status code.
+ * @param pThis The shared AC'97 state.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param index AC'97 mixer index to set volume for.
+ * @param enmMixerCtl Corresponding audio mixer sink.
+ * @param uVal Volume value to set.
+ */
+static int ichac97R3MixerSetGain(PAC97STATE pThis, PAC97STATER3 pThisCC, int index, PDMAUDIOMIXERCTL enmMixerCtl, uint32_t uVal)
+{
+ /*
+ * For AC'97 recording controls, each additional step means +1.5dB gain with
+ * zero being 0dB gain and 15 being +22.5dB gain.
+ */
+ bool const fCtlMuted = (uVal >> AC97_BARS_VOL_MUTE_SHIFT) & 1;
+ uint8_t uCtlGainLeft = (uVal >> 8) & AC97_BARS_GAIN_MASK;
+ uint8_t uCtlGainRight = uVal & AC97_BARS_GAIN_MASK;
+
+ Assert(uCtlGainLeft <= 255 / AC97_DB_FACTOR);
+ Assert(uCtlGainRight <= 255 / AC97_DB_FACTOR);
+
+ LogFunc(("index=0x%x, uVal=%RU32, enmMixerCtl=%RU32\n", index, uVal, enmMixerCtl));
+ LogFunc(("uCtlGainLeft=%RU8, uCtlGainRight=%RU8 ", uCtlGainLeft, uCtlGainRight));
+
+ uint8_t lVol = PDMAUDIO_VOLUME_MAX + uCtlGainLeft * AC97_DB_FACTOR;
+ uint8_t rVol = PDMAUDIO_VOLUME_MAX + uCtlGainRight * AC97_DB_FACTOR;
+
+ /* We do not currently support gain. Since AC'97 does not support attenuation
+ * for the recording input, the best we can do is set the maximum volume.
+ */
+# ifndef VBOX_WITH_AC97_GAIN_SUPPORT
+ /* NB: Currently there is no gain support, only attenuation. Since AC'97 does not
+ * support attenuation for the recording inputs, the best we can do is set the
+ * maximum volume.
+ */
+ lVol = rVol = PDMAUDIO_VOLUME_MAX;
+# endif
+
+ Log(("-> fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n", fCtlMuted, lVol, rVol));
+
+ int rc = VINF_SUCCESS;
+
+ if (pThisCC->pMixer) /* Device can be in reset state, so no mixer available. */
+ {
+ PDMAUDIOVOLUME Vol;
+ PDMAudioVolumeInitFromStereo(&Vol, fCtlMuted, lVol, rVol);
+
+ PAUDMIXSINK pSink = NULL;
+ switch (enmMixerCtl)
+ {
+ case PDMAUDIOMIXERCTL_MIC_IN:
+ pSink = pThisCC->pSinkMicIn;
+ break;
+
+ case PDMAUDIOMIXERCTL_LINE_IN:
+ pSink = pThisCC->pSinkLineIn;
+ break;
+
+ default:
+ AssertFailed();
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (pSink)
+ {
+ rc = AudioMixerSinkSetVolume(pSink, &Vol);
+ /* There is only one AC'97 recording gain control. If line in
+ * is changed, also update the microphone. If the optional dedicated
+ * microphone is changed, only change that.
+ * NB: The codecs we support do not have the dedicated microphone control.
+ */
+ if (pSink == pThisCC->pSinkLineIn && pThisCC->pSinkMicIn)
+ rc = AudioMixerSinkSetVolume(pSink, &Vol);
+ }
+ }
+
+ ichac97MixerSet(pThis, index, uVal);
+
+ if (RT_FAILURE(rc))
+ LogFlowFunc(("Failed with %Rrc\n", rc));
+
+ return rc;
+}
+
+
+/**
+ * Converts an AC'97 recording source index to a PDM audio recording source.
+ *
+ * @returns PDM audio recording source.
+ * @param uIdx AC'97 index to convert.
+ */
+static PDMAUDIOPATH ichac97R3IdxToRecSource(uint8_t uIdx)
+{
+ switch (uIdx)
+ {
+ case AC97_REC_MIC: return PDMAUDIOPATH_IN_MIC;
+ case AC97_REC_CD: return PDMAUDIOPATH_IN_CD;
+ case AC97_REC_VIDEO: return PDMAUDIOPATH_IN_VIDEO;
+ case AC97_REC_AUX: return PDMAUDIOPATH_IN_AUX;
+ case AC97_REC_LINE_IN: return PDMAUDIOPATH_IN_LINE;
+ case AC97_REC_PHONE: return PDMAUDIOPATH_IN_PHONE;
+ default:
+ break;
+ }
+
+ LogFlowFunc(("Unknown record source %d, using MIC\n", uIdx));
+ return PDMAUDIOPATH_IN_MIC;
+}
+
+
+/**
+ * Converts a PDM audio recording source to an AC'97 recording source index.
+ *
+ * @returns AC'97 recording source index.
+ * @param enmRecSrc PDM audio recording source to convert.
+ */
+static uint8_t ichac97R3RecSourceToIdx(PDMAUDIOPATH enmRecSrc)
+{
+ switch (enmRecSrc)
+ {
+ case PDMAUDIOPATH_IN_MIC: return AC97_REC_MIC;
+ case PDMAUDIOPATH_IN_CD: return AC97_REC_CD;
+ case PDMAUDIOPATH_IN_VIDEO: return AC97_REC_VIDEO;
+ case PDMAUDIOPATH_IN_AUX: return AC97_REC_AUX;
+ case PDMAUDIOPATH_IN_LINE: return AC97_REC_LINE_IN;
+ case PDMAUDIOPATH_IN_PHONE: return AC97_REC_PHONE;
+ default:
+ AssertMsgFailedBreak(("%d\n", enmRecSrc));
+ }
+
+ LogFlowFunc(("Unknown audio recording source %d using MIC\n", enmRecSrc));
+ return AC97_REC_MIC;
+}
+
+
+/**
+ * Performs an AC'97 mixer record select to switch to a different recording
+ * source.
+ *
+ * @param pThis The shared AC'97 state.
+ * @param val AC'97 recording source index to set.
+ */
+static void ichac97R3MixerRecordSelect(PAC97STATE pThis, uint32_t val)
+{
+ uint8_t rs = val & AC97_REC_MASK;
+ uint8_t ls = (val >> 8) & AC97_REC_MASK;
+
+ PDMAUDIOPATH const ars = ichac97R3IdxToRecSource(rs);
+ PDMAUDIOPATH const als = ichac97R3IdxToRecSource(ls);
+
+ rs = ichac97R3RecSourceToIdx(ars);
+ ls = ichac97R3RecSourceToIdx(als);
+
+ LogRel(("AC97: Record select to left=%s, right=%s\n", PDMAudioPathGetName(ars), PDMAudioPathGetName(als)));
+
+ ichac97MixerSet(pThis, AC97_Record_Select, rs | (ls << 8));
+}
+
+/**
+ * Resets the AC'97 mixer.
+ *
+ * @returns VBox status code.
+ * @param pThis The shared AC'97 state.
+ * @param pThisCC The ring-3 AC'97 state.
+ */
+static int ichac97R3MixerReset(PAC97STATE pThis, PAC97STATER3 pThisCC)
+{
+ LogFlowFuncEnter();
+
+ RT_ZERO(pThis->mixer_data);
+
+ /* Note: Make sure to reset all registers first before bailing out on error. */
+
+ ichac97MixerSet(pThis, AC97_Reset , 0x0000); /* 6940 */
+ ichac97MixerSet(pThis, AC97_Master_Volume_Mono_Mute , 0x8000);
+ ichac97MixerSet(pThis, AC97_PC_BEEP_Volume_Mute , 0x0000);
+
+ ichac97MixerSet(pThis, AC97_Phone_Volume_Mute , 0x8008);
+ ichac97MixerSet(pThis, AC97_Mic_Volume_Mute , 0x8008);
+ ichac97MixerSet(pThis, AC97_CD_Volume_Mute , 0x8808);
+ ichac97MixerSet(pThis, AC97_Aux_Volume_Mute , 0x8808);
+ ichac97MixerSet(pThis, AC97_Record_Gain_Mic_Mute , 0x8000);
+ ichac97MixerSet(pThis, AC97_General_Purpose , 0x0000);
+ ichac97MixerSet(pThis, AC97_3D_Control , 0x0000);
+ ichac97MixerSet(pThis, AC97_Powerdown_Ctrl_Stat , 0x000f);
+
+ /* Configure Extended Audio ID (EAID) + Control & Status (EACS) registers. */
+ const uint16_t fEAID = AC97_EAID_REV1 | AC97_EACS_VRA | AC97_EACS_VRM; /* Our hardware is AC'97 rev2.3 compliant. */
+ const uint16_t fEACS = AC97_EACS_VRA | AC97_EACS_VRM; /* Variable Rate PCM Audio (VRA) + Mic-In (VRM) capable. */
+
+ LogRel(("AC97: Mixer reset (EAID=0x%x, EACS=0x%x)\n", fEAID, fEACS));
+
+ ichac97MixerSet(pThis, AC97_Extended_Audio_ID, fEAID);
+ ichac97MixerSet(pThis, AC97_Extended_Audio_Ctrl_Stat, fEACS);
+ ichac97MixerSet(pThis, AC97_PCM_Front_DAC_Rate , 0xbb80 /* 48000 Hz by default */);
+ ichac97MixerSet(pThis, AC97_PCM_Surround_DAC_Rate , 0xbb80 /* 48000 Hz by default */);
+ ichac97MixerSet(pThis, AC97_PCM_LFE_DAC_Rate , 0xbb80 /* 48000 Hz by default */);
+ ichac97MixerSet(pThis, AC97_PCM_LR_ADC_Rate , 0xbb80 /* 48000 Hz by default */);
+ ichac97MixerSet(pThis, AC97_MIC_ADC_Rate , 0xbb80 /* 48000 Hz by default */);
+
+ if (pThis->enmCodecModel == AC97CODEC_AD1980)
+ {
+ /* Analog Devices 1980 (AD1980) */
+ ichac97MixerSet(pThis, AC97_Reset , 0x0010); /* Headphones. */
+ ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x4144);
+ ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x5370);
+ ichac97MixerSet(pThis, AC97_Headphone_Volume_Mute , 0x8000);
+ }
+ else if (pThis->enmCodecModel == AC97CODEC_AD1981B)
+ {
+ /* Analog Devices 1981B (AD1981B) */
+ ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x4144);
+ ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x5374);
+ }
+ else
+ {
+ /* Sigmatel 9700 (STAC9700) */
+ ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x8384);
+ ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x7600); /* 7608 */
+ }
+ ichac97R3MixerRecordSelect(pThis, 0);
+
+ /* The default value is 8000h, which corresponds to 0 dB attenuation with mute on. */
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Master_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER, 0x8000);
+
+ /* The default value for stereo registers is 8808h, which corresponds to 0 dB gain with mute on.*/
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_PCM_Out_Volume_Mute, PDMAUDIOMIXERCTL_FRONT, 0x8808);
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Line_In_Volume_Mute, PDMAUDIOMIXERCTL_LINE_IN, 0x8808);
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Mic_Volume_Mute, PDMAUDIOMIXERCTL_MIC_IN, 0x8008);
+
+ /* The default for record controls is 0 dB gain with mute on. */
+ ichac97R3MixerSetGain(pThis, pThisCC, AC97_Record_Gain_Mute, PDMAUDIOMIXERCTL_LINE_IN, 0x8000);
+ ichac97R3MixerSetGain(pThis, pThisCC, AC97_Record_Gain_Mic_Mute, PDMAUDIOMIXERCTL_MIC_IN, 0x8000);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ichac97IoPortNamRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ RT_NOREF(pvUser);
+ Assert(offPort < 256);
+
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_READ);
+
+ VBOXSTRICTRC rc = VINF_SUCCESS;
+ switch (cb)
+ {
+ case 1:
+ LogRel2(("AC97: Warning: Unimplemented NAM read offPort=%#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamReads);
+ pThis->cas = 0;
+ *pu32 = UINT32_MAX;
+ break;
+
+ case 2:
+ pThis->cas = 0;
+ *pu32 = ichac97MixerGet(pThis, offPort);
+ break;
+
+ case 4:
+ LogRel2(("AC97: Warning: Unimplemented NAM read offPort=%#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamReads);
+ pThis->cas = 0;
+ *pu32 = UINT32_MAX;
+ break;
+
+ default:
+ AssertFailed();
+ rc = VERR_IOM_IOPORT_UNUSED;
+ break;
+ }
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ichac97IoPortNamWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+#ifdef IN_RING3
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+#endif
+ RT_NOREF(pvUser);
+
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+
+ VBOXSTRICTRC rc = VINF_SUCCESS;
+ switch (cb)
+ {
+ case 1:
+ LogRel2(("AC97: Warning: Unimplemented NAM write offPort=%#x <- %#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamWrites);
+ pThis->cas = 0;
+ break;
+
+ case 2:
+ {
+ pThis->cas = 0;
+ switch (offPort)
+ {
+ case AC97_Reset:
+#ifdef IN_RING3
+ ichac97R3Reset(pDevIns);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Powerdown_Ctrl_Stat:
+ u32 &= ~0xf;
+ u32 |= ichac97MixerGet(pThis, offPort) & 0xf;
+ ichac97MixerSet(pThis, offPort, u32);
+ break;
+ case AC97_Master_Volume_Mute:
+ if (pThis->enmCodecModel == AC97CODEC_AD1980)
+ {
+ if (ichac97MixerGet(pThis, AC97_AD_Misc) & AC97_AD_MISC_LOSEL)
+ break; /* Register controls surround (rear), do nothing. */
+ }
+#ifdef IN_RING3
+ ichac97R3MixerSetVolume(pThis, pThisCC, offPort, PDMAUDIOMIXERCTL_VOLUME_MASTER, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Headphone_Volume_Mute:
+ if (pThis->enmCodecModel == AC97CODEC_AD1980)
+ {
+ if (ichac97MixerGet(pThis, AC97_AD_Misc) & AC97_AD_MISC_HPSEL)
+ {
+ /* Register controls PCM (front) outputs. */
+#ifdef IN_RING3
+ ichac97R3MixerSetVolume(pThis, pThisCC, offPort, PDMAUDIOMIXERCTL_VOLUME_MASTER, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ }
+ }
+ break;
+ case AC97_PCM_Out_Volume_Mute:
+#ifdef IN_RING3
+ ichac97R3MixerSetVolume(pThis, pThisCC, offPort, PDMAUDIOMIXERCTL_FRONT, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Line_In_Volume_Mute:
+#ifdef IN_RING3
+ ichac97R3MixerSetVolume(pThis, pThisCC, offPort, PDMAUDIOMIXERCTL_LINE_IN, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Record_Select:
+#ifdef IN_RING3
+ ichac97R3MixerRecordSelect(pThis, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Record_Gain_Mute:
+#ifdef IN_RING3
+ /* Newer Ubuntu guests rely on that when controlling gain and muting
+ * the recording (capturing) levels. */
+ ichac97R3MixerSetGain(pThis, pThisCC, offPort, PDMAUDIOMIXERCTL_LINE_IN, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Record_Gain_Mic_Mute:
+#ifdef IN_RING3
+ /* Ditto; see note above. */
+ ichac97R3MixerSetGain(pThis, pThisCC, offPort, PDMAUDIOMIXERCTL_MIC_IN, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Vendor_ID1:
+ case AC97_Vendor_ID2:
+ LogFunc(("Attempt to write vendor ID to %#x\n", u32));
+ break;
+ case AC97_Extended_Audio_ID:
+ LogFunc(("Attempt to write extended audio ID to %#x\n", u32));
+ break;
+ case AC97_Extended_Audio_Ctrl_Stat:
+#ifdef IN_RING3
+ /*
+ * Handle VRA bits.
+ */
+ if (!(u32 & AC97_EACS_VRA)) /* Check if VRA bit is not set. */
+ {
+ ichac97MixerSet(pThis, AC97_PCM_Front_DAC_Rate, 0xbb80); /* Set default (48000 Hz). */
+ /** @todo r=bird: Why reopen it now? Can't we put that off till it's
+ * actually used? */
+ ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX],
+ &pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX], true /* fForce */);
+
+ ichac97MixerSet(pThis, AC97_PCM_LR_ADC_Rate, 0xbb80); /* Set default (48000 Hz). */
+ /** @todo r=bird: Why reopen it now? Can't we put that off till it's
+ * actually used? */
+ ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX],
+ &pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX], true /* fForce */);
+ }
+ else
+ LogRel2(("AC97: Variable rate audio (VRA) is not supported\n"));
+
+ /*
+ * Handle VRM bits.
+ */
+ if (!(u32 & AC97_EACS_VRM)) /* Check if VRM bit is not set. */
+ {
+ ichac97MixerSet(pThis, AC97_MIC_ADC_Rate, 0xbb80); /* Set default (48000 Hz). */
+ /** @todo r=bird: Why reopen it now? Can't we put that off till it's
+ * actually used? */
+ ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX],
+ &pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX], true /* fForce */);
+ }
+ else
+ LogRel2(("AC97: Variable rate microphone audio (VRM) is not supported\n"));
+
+ LogRel2(("AC97: Setting extended audio control to %#x\n", u32));
+ ichac97MixerSet(pThis, AC97_Extended_Audio_Ctrl_Stat, u32);
+#else /* !IN_RING3 */
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_PCM_Front_DAC_Rate: /* Output slots 3, 4, 6. */
+#ifdef IN_RING3
+ if (ichac97MixerGet(pThis, AC97_Extended_Audio_Ctrl_Stat) & AC97_EACS_VRA)
+ {
+ LogRel2(("AC97: Setting front DAC rate to 0x%x\n", u32));
+ ichac97MixerSet(pThis, offPort, u32);
+ /** @todo r=bird: Why reopen it now? Can't we put that off till it's
+ * actually used? */
+ ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX],
+ &pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX], true /* fForce */);
+ }
+ else
+ LogRel2(("AC97: Setting front DAC rate (0x%x) when VRA is not set is forbidden, ignoring\n", u32));
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_MIC_ADC_Rate: /* Input slot 6. */
+#ifdef IN_RING3
+ if (ichac97MixerGet(pThis, AC97_Extended_Audio_Ctrl_Stat) & AC97_EACS_VRM)
+ {
+ LogRel2(("AC97: Setting microphone ADC rate to 0x%x\n", u32));
+ ichac97MixerSet(pThis, offPort, u32);
+ /** @todo r=bird: Why reopen it now? Can't we put that off till it's
+ * actually used? */
+ ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX],
+ &pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX], true /* fForce */);
+ }
+ else
+ LogRel2(("AC97: Setting microphone ADC rate (0x%x) when VRM is not set is forbidden, ignoring\n", u32));
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_PCM_LR_ADC_Rate: /* Input slots 3, 4. */
+#ifdef IN_RING3
+ if (ichac97MixerGet(pThis, AC97_Extended_Audio_Ctrl_Stat) & AC97_EACS_VRA)
+ {
+ LogRel2(("AC97: Setting line-in ADC rate to 0x%x\n", u32));
+ ichac97MixerSet(pThis, offPort, u32);
+ /** @todo r=bird: Why reopen it now? Can't we put that off till it's
+ * actually used? */
+ ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX],
+ &pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX], true /* fForce */);
+ }
+ else
+ LogRel2(("AC97: Setting line-in ADC rate (0x%x) when VRA is not set is forbidden, ignoring\n", u32));
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ default:
+ /* Most of these are to register we don't care about like AC97_CD_Volume_Mute
+ and AC97_Master_Volume_Mono_Mute or things we don't need to handle specially.
+ Thus this is not a 'warning' but an 'info log message. */
+ LogRel2(("AC97: Info: Unimplemented NAM write offPort=%#x <- %#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamWrites);
+ ichac97MixerSet(pThis, offPort, u32);
+ break;
+ }
+ break;
+ }
+
+ case 4:
+ LogRel2(("AC97: Warning: Unimplemented NAM write offPort=%#x <- %#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamWrites);
+ pThis->cas = 0;
+ break;
+
+ default:
+ AssertMsgFailed(("Unhandled NAM write offPort=%#x, cb=%u u32=%#x\n", offPort, cb, u32));
+ break;
+ }
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ return rc;
+}
+
+#ifdef IN_RING3
+
+
+/*********************************************************************************************************************************
+* State Saving & Loading *
+*********************************************************************************************************************************/
+
+/**
+ * Saves (serializes) an AC'97 stream using SSM.
+ *
+ * @param pDevIns Device instance.
+ * @param pSSM Saved state manager (SSM) handle to use.
+ * @param pStream AC'97 stream to save.
+ */
+static void ichac97R3SaveStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PAC97STREAM pStream)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ pHlp->pfnSSMPutU32(pSSM, pStream->Regs.bdbar);
+ pHlp->pfnSSMPutU8( pSSM, pStream->Regs.civ);
+ pHlp->pfnSSMPutU8( pSSM, pStream->Regs.lvi);
+ pHlp->pfnSSMPutU16(pSSM, pStream->Regs.sr);
+ pHlp->pfnSSMPutU16(pSSM, pStream->Regs.picb);
+ pHlp->pfnSSMPutU8( pSSM, pStream->Regs.piv);
+ pHlp->pfnSSMPutU8( pSSM, pStream->Regs.cr);
+ pHlp->pfnSSMPutS32(pSSM, pStream->Regs.bd_valid);
+ pHlp->pfnSSMPutU32(pSSM, pStream->Regs.bd.addr);
+ pHlp->pfnSSMPutU32(pSSM, pStream->Regs.bd.ctl_len);
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) ichac97R3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ LogFlowFuncEnter();
+
+ pHlp->pfnSSMPutU32(pSSM, pThis->glob_cnt);
+ pHlp->pfnSSMPutU32(pSSM, pThis->glob_sta);
+ pHlp->pfnSSMPutU32(pSSM, pThis->cas);
+
+ /*
+ * The order that the streams are saved here is fixed, so don't change.
+ */
+ /** @todo r=andy For the next saved state version, add unique stream identifiers and a stream count. */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ ichac97R3SaveStream(pDevIns, pSSM, &pThis->aStreams[i]);
+
+ pHlp->pfnSSMPutMem(pSSM, pThis->mixer_data, sizeof(pThis->mixer_data));
+
+ /* The stream order is against fixed and set in stone. */
+ uint8_t afActiveStrms[AC97SOUNDSOURCE_MAX];
+ afActiveStrms[AC97SOUNDSOURCE_PI_INDEX] = ichac97R3StreamIsEnabled(pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX]);
+ afActiveStrms[AC97SOUNDSOURCE_PO_INDEX] = ichac97R3StreamIsEnabled(pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX]);
+ afActiveStrms[AC97SOUNDSOURCE_MC_INDEX] = ichac97R3StreamIsEnabled(pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX]);
+ AssertCompile(RT_ELEMENTS(afActiveStrms) == 3);
+ pHlp->pfnSSMPutMem(pSSM, afActiveStrms, sizeof(afActiveStrms));
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Loads an AC'97 stream from SSM.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pSSM Saved state manager (SSM) handle to use.
+ * @param pStream AC'97 stream to load.
+ */
+static int ichac97R3LoadStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PAC97STREAM pStream)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ pHlp->pfnSSMGetU32(pSSM, &pStream->Regs.bdbar);
+ pHlp->pfnSSMGetU8( pSSM, &pStream->Regs.civ);
+ pHlp->pfnSSMGetU8( pSSM, &pStream->Regs.lvi);
+ pHlp->pfnSSMGetU16(pSSM, &pStream->Regs.sr);
+ pHlp->pfnSSMGetU16(pSSM, &pStream->Regs.picb);
+ pHlp->pfnSSMGetU8( pSSM, &pStream->Regs.piv);
+ pHlp->pfnSSMGetU8( pSSM, &pStream->Regs.cr);
+ pHlp->pfnSSMGetS32(pSSM, &pStream->Regs.bd_valid);
+ pHlp->pfnSSMGetU32(pSSM, &pStream->Regs.bd.addr);
+ return pHlp->pfnSSMGetU32(pSSM, &pStream->Regs.bd.ctl_len);
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) ichac97R3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ LogRel2(("ichac97LoadExec: uVersion=%RU32, uPass=0x%x\n", uVersion, uPass));
+
+ AssertMsgReturn (uVersion == AC97_SAVED_STATE_VERSION, ("%RU32\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ pHlp->pfnSSMGetU32(pSSM, &pThis->glob_cnt);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->glob_sta);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->cas);
+
+ /*
+ * The order the streams are loaded here is critical (defined by
+ * AC97SOUNDSOURCE_XX_INDEX), so don't touch!
+ */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ int rc = ichac97R3LoadStream(pDevIns, pSSM, &pThis->aStreams[i]);
+ AssertRCReturn(rc, rc);
+ }
+
+ pHlp->pfnSSMGetMem(pSSM, pThis->mixer_data, sizeof(pThis->mixer_data));
+
+ ichac97R3MixerRecordSelect(pThis, ichac97MixerGet(pThis, AC97_Record_Select));
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Master_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER,
+ ichac97MixerGet(pThis, AC97_Master_Volume_Mute));
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_PCM_Out_Volume_Mute, PDMAUDIOMIXERCTL_FRONT,
+ ichac97MixerGet(pThis, AC97_PCM_Out_Volume_Mute));
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Line_In_Volume_Mute, PDMAUDIOMIXERCTL_LINE_IN,
+ ichac97MixerGet(pThis, AC97_Line_In_Volume_Mute));
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Mic_Volume_Mute, PDMAUDIOMIXERCTL_MIC_IN,
+ ichac97MixerGet(pThis, AC97_Mic_Volume_Mute));
+ ichac97R3MixerSetGain(pThis, pThisCC, AC97_Record_Gain_Mic_Mute, PDMAUDIOMIXERCTL_MIC_IN,
+ ichac97MixerGet(pThis, AC97_Record_Gain_Mic_Mute));
+ ichac97R3MixerSetGain(pThis, pThisCC, AC97_Record_Gain_Mute, PDMAUDIOMIXERCTL_LINE_IN,
+ ichac97MixerGet(pThis, AC97_Record_Gain_Mute));
+ if (pThis->enmCodecModel == AC97CODEC_AD1980)
+ if (ichac97MixerGet(pThis, AC97_AD_Misc) & AC97_AD_MISC_HPSEL)
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Headphone_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER,
+ ichac97MixerGet(pThis, AC97_Headphone_Volume_Mute));
+
+ /*
+ * Again the stream order is set is stone.
+ */
+ uint8_t afActiveStrms[AC97SOUNDSOURCE_MAX];
+ int rc = pHlp->pfnSSMGetMem(pSSM, afActiveStrms, sizeof(afActiveStrms));
+ AssertRCReturn(rc, rc);
+
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ const bool fEnable = RT_BOOL(afActiveStrms[i]);
+ const PAC97STREAM pStream = &pThis->aStreams[i];
+ const PAC97STREAMR3 pStreamCC = &pThisCC->aStreams[i];
+
+ rc = ichac97R3StreamEnable(pDevIns, pThis, pThisCC, pStream, pStreamCC, fEnable);
+ AssertRC(rc);
+ if ( fEnable
+ && RT_SUCCESS(rc))
+ {
+ /*
+ * We need to make sure to update the stream's next transfer (if any) when
+ * restoring from a saved state.
+ *
+ * Otherwise pStream->cDmaPeriodTicks always will be 0 and thus streams won't
+ * resume when running while the saved state has been taken.
+ *
+ * Also see oem2ticketref:52.
+ */
+ ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC);
+
+ /* Re-arm the timer for this stream. */
+ /** @todo r=aeichner This causes a VM hang upon saved state resume when NetBSD is used as a guest
+ * Stopping the timer if cDmaPeriodTicks is 0 is a workaround but needs further investigation,
+ * see @bugref{9759} for more information. */
+ if (pStream->cDmaPeriodTicks)
+ ichac97R3TimerSet(pDevIns, pStream, pStream->cDmaPeriodTicks);
+ else
+ PDMDevHlpTimerStop(pDevIns, pStream->hTimer);
+ }
+
+ /* Keep going. */
+ }
+
+ pThis->bup_flag = 0;
+ pThis->last_samp = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Debug Info Items *
+*********************************************************************************************************************************/
+
+/** Used by ichac97R3DbgInfoStream and ichac97R3DbgInfoBDL. */
+static int ichac97R3DbgLookupStrmIdx(PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ if (pszArgs && *pszArgs)
+ {
+ int32_t idxStream;
+ int rc = RTStrToInt32Full(pszArgs, 0, &idxStream);
+ if (RT_SUCCESS(rc) && idxStream >= -1 && idxStream < AC97_MAX_STREAMS)
+ return idxStream;
+ pHlp->pfnPrintf(pHlp, "Argument '%s' is not a valid stream number!\n", pszArgs);
+ }
+ return -1;
+}
+
+
+/**
+ * Generic buffer descriptor list dumper.
+ */
+static void ichac97R3DbgPrintBdl(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream,
+ PCDBGFINFOHLP pHlp, const char *pszPrefix)
+{
+ uint8_t const bLvi = pStream->Regs.lvi;
+ uint8_t const bCiv = pStream->Regs.civ;
+ pHlp->pfnPrintf(pHlp, "%sBDL for stream #%u: @ %#RX32 LB 0x100; CIV=%#04x LVI=%#04x:\n",
+ pszPrefix, pStream->u8SD, pStream->Regs.bdbar, bCiv, bLvi);
+ if (pStream->Regs.bdbar != 0)
+ {
+ /* Read all in one go. */
+ AC97BDLE aBdl[AC97_MAX_BDLE];
+ RT_ZERO(aBdl);
+ PDMDevHlpPCIPhysRead(pDevIns, pStream->Regs.bdbar, aBdl, sizeof(aBdl));
+
+ /* Get the audio props for the stream so we can translate the sizes correctly. */
+ PDMAUDIOPCMPROPS Props;
+ ichach97R3CalcStreamProps(pThis, pStream->u8SD, &Props);
+
+ /* Dump them. */
+ uint64_t cbTotal = 0;
+ uint64_t cbValid = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(aBdl); i++)
+ {
+ aBdl[i].addr = RT_LE2H_U32(aBdl[i].addr);
+ aBdl[i].ctl_len = RT_LE2H_U32(aBdl[i].ctl_len);
+
+ bool const fValid = bCiv <= bLvi
+ ? i >= bCiv && i <= bLvi
+ : i >= bCiv || i <= bLvi;
+
+ uint32_t const cb = (aBdl[i].ctl_len & AC97_BD_LEN_MASK) * PDMAudioPropsSampleSize(&Props); /** @todo or frame size? OSDev says frame... */
+ cbTotal += cb;
+ if (fValid)
+ cbValid += cb;
+
+ char szFlags[64];
+ szFlags[0] = '\0';
+ if (aBdl[i].ctl_len & ~(AC97_BD_LEN_MASK | AC97_BD_IOC | AC97_BD_BUP))
+ RTStrPrintf(szFlags, sizeof(szFlags), " !!fFlags=%#x!!\n", aBdl[i].ctl_len & ~AC97_BD_LEN_MASK);
+
+ pHlp->pfnPrintf(pHlp, "%s %cBDLE%02u: %#010RX32 L %#06x / LB %#RX32 / %RU64ms%s%s%s%s\n",
+ pszPrefix, fValid ? ' ' : '?', i, aBdl[i].addr,
+ aBdl[i].ctl_len & AC97_BD_LEN_MASK, cb, PDMAudioPropsBytesToMilli(&Props, cb),
+ aBdl[i].ctl_len & AC97_BD_IOC ? " ioc" : "",
+ aBdl[i].ctl_len & AC97_BD_BUP ? " bup" : "",
+ szFlags, !(aBdl[i].addr & 3) ? "" : " !!Addr!!");
+ }
+
+ pHlp->pfnPrintf(pHlp, "%sTotal: %#RX64 bytes (%RU64), %RU64 ms; Valid: %#RX64 bytes (%RU64), %RU64 ms\n", pszPrefix,
+ cbTotal, cbTotal, PDMAudioPropsBytesToMilli(&Props, cbTotal),
+ cbValid, cbValid, PDMAudioPropsBytesToMilli(&Props, cbValid) );
+ }
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, ac97bdl}
+ */
+static DECLCALLBACK(void) ichac97R3DbgInfoBDL(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ int idxStream = ichac97R3DbgLookupStrmIdx(pHlp, pszArgs);
+ if (idxStream != -1)
+ ichac97R3DbgPrintBdl(pDevIns, pThis, &pThis->aStreams[idxStream], pHlp, "");
+ else
+ for (idxStream = 0; idxStream < AC97_MAX_STREAMS; ++idxStream)
+ ichac97R3DbgPrintBdl(pDevIns, pThis, &pThis->aStreams[idxStream], pHlp, "");
+}
+
+
+/** Worker for ichac97R3DbgInfoStream. */
+static void ichac97R3DbgPrintStream(PCDBGFINFOHLP pHlp, PAC97STREAM pStream, PAC97STREAMR3 pStreamR3)
+{
+ char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX];
+ pHlp->pfnPrintf(pHlp, "Stream #%d: %s\n", pStream->u8SD,
+ PDMAudioStrmCfgToString(&pStreamR3->State.Cfg, szTmp, sizeof(szTmp)));
+ pHlp->pfnPrintf(pHlp, " BDBAR %#010RX32\n", pStream->Regs.bdbar);
+ pHlp->pfnPrintf(pHlp, " CIV %#04RX8\n", pStream->Regs.civ);
+ pHlp->pfnPrintf(pHlp, " LVI %#04RX8\n", pStream->Regs.lvi);
+ pHlp->pfnPrintf(pHlp, " SR %#06RX16\n", pStream->Regs.sr);
+ pHlp->pfnPrintf(pHlp, " PICB %#06RX16\n", pStream->Regs.picb);
+ pHlp->pfnPrintf(pHlp, " PIV %#04RX8\n", pStream->Regs.piv);
+ pHlp->pfnPrintf(pHlp, " CR %#04RX8\n", pStream->Regs.cr);
+ if (pStream->Regs.bd_valid)
+ {
+ pHlp->pfnPrintf(pHlp, " BD.ADDR %#010RX32\n", pStream->Regs.bd.addr);
+ pHlp->pfnPrintf(pHlp, " BD.LEN %#04RX16\n", (uint16_t)pStream->Regs.bd.ctl_len);
+ pHlp->pfnPrintf(pHlp, " BD.CTL %#04RX16\n", (uint16_t)(pStream->Regs.bd.ctl_len >> 16));
+ }
+
+ pHlp->pfnPrintf(pHlp, " offRead %#RX64\n", pStreamR3->State.offRead);
+ pHlp->pfnPrintf(pHlp, " offWrite %#RX64\n", pStreamR3->State.offWrite);
+ pHlp->pfnPrintf(pHlp, " uTimerHz %RU16\n", pStreamR3->State.uTimerHz);
+ pHlp->pfnPrintf(pHlp, " cDmaPeriodTicks %RU64\n", pStream->cDmaPeriodTicks);
+ pHlp->pfnPrintf(pHlp, " cbDmaPeriod %#RX32\n", pStream->cbDmaPeriod);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, ac97stream}
+ */
+static DECLCALLBACK(void) ichac97R3DbgInfoStream(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ int idxStream = ichac97R3DbgLookupStrmIdx(pHlp, pszArgs);
+ if (idxStream != -1)
+ ichac97R3DbgPrintStream(pHlp, &pThis->aStreams[idxStream], &pThisCC->aStreams[idxStream]);
+ else
+ for (idxStream = 0; idxStream < AC97_MAX_STREAMS; ++idxStream)
+ ichac97R3DbgPrintStream(pHlp, &pThis->aStreams[idxStream], &pThisCC->aStreams[idxStream]);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, ac97mixer}
+ */
+static DECLCALLBACK(void) ichac97R3DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ 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 *) ichac97R3QueryInterface(struct PDMIBASE *pInterface, const char *pszIID)
+{
+ PAC97STATER3 pThisCC = RT_FROM_MEMBER(pInterface, AC97STATER3, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase);
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDEVREG *
+*********************************************************************************************************************************/
+
+/**
+ * Destroys all AC'97 audio streams of the device.
+ *
+ * @param pDevIns The device AC'97 instance.
+ * @param pThis The shared AC'97 state.
+ * @param pThisCC The ring-3 AC'97 state.
+ */
+static void ichac97R3StreamsDestroy(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC)
+{
+ LogFlowFuncEnter();
+
+ /*
+ * Destroy all AC'97 streams.
+ */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ ichac97R3StreamDestroy(pThisCC, &pThis->aStreams[i], &pThisCC->aStreams[i]);
+
+ /*
+ * Destroy all sinks.
+ */
+ if (pThisCC->pSinkLineIn)
+ {
+ ichac97R3MixerRemoveDrvStreams(pDevIns, pThisCC, pThisCC->pSinkLineIn, PDMAUDIODIR_IN, PDMAUDIOPATH_IN_LINE);
+
+ AudioMixerSinkDestroy(pThisCC->pSinkLineIn, pDevIns);
+ pThisCC->pSinkLineIn = NULL;
+ }
+
+ if (pThisCC->pSinkMicIn)
+ {
+ ichac97R3MixerRemoveDrvStreams(pDevIns, pThisCC, pThisCC->pSinkMicIn, PDMAUDIODIR_IN, PDMAUDIOPATH_IN_MIC);
+
+ AudioMixerSinkDestroy(pThisCC->pSinkMicIn, pDevIns);
+ pThisCC->pSinkMicIn = NULL;
+ }
+
+ if (pThisCC->pSinkOut)
+ {
+ ichac97R3MixerRemoveDrvStreams(pDevIns, pThisCC, pThisCC->pSinkOut, PDMAUDIODIR_OUT, PDMAUDIOPATH_OUT_FRONT);
+
+ AudioMixerSinkDestroy(pThisCC->pSinkOut, pDevIns);
+ pThisCC->pSinkOut = NULL;
+ }
+}
+
+
+/**
+ * Powers off the device.
+ *
+ * @param pDevIns Device instance to power off.
+ */
+static DECLCALLBACK(void) ichac97R3PowerOff(PPDMDEVINS pDevIns)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+
+ LogRel2(("AC97: Powering off ...\n"));
+
+ /* Note: Involves mixer stream / sink destruction, so also do this here
+ * instead of in ichac97R3Destruct(). */
+ ichac97R3StreamsDestroy(pDevIns, pThis, pThisCC);
+
+ /*
+ * Note: Destroy the mixer while powering off and *not* in ichac97R3Destruct,
+ * giving the mixer the chance to release any references held to
+ * PDM audio streams it maintains.
+ */
+ if (pThisCC->pMixer)
+ {
+ AudioMixerDestroy(pThisCC->pMixer, pDevIns);
+ pThisCC->pMixer = NULL;
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ *
+ * @remarks The original sources didn't install a reset handler, but it seems to
+ * make sense to me so we'll do it.
+ */
+static DECLCALLBACK(void) ichac97R3Reset(PPDMDEVINS pDevIns)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+
+ LogRel(("AC97: Reset\n"));
+
+ /*
+ * Reset the mixer too. The Windows XP driver seems to rely on
+ * this. At least it wants to read the vendor id before it resets
+ * the codec manually.
+ */
+ ichac97R3MixerReset(pThis, pThisCC);
+
+ /*
+ * Reset all streams.
+ */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ ichac97R3StreamEnable(pDevIns, pThis, pThisCC, &pThis->aStreams[i], &pThisCC->aStreams[i], false /* fEnable */);
+ ichac97R3StreamReset(pThis, &pThis->aStreams[i], &pThisCC->aStreams[i]);
+ }
+
+ /*
+ * Reset mixer sinks.
+ *
+ * Do the reset here instead of in ichac97R3StreamReset();
+ * the mixer sink(s) might still have data to be processed when an audio stream gets reset.
+ */
+ AudioMixerSinkReset(pThisCC->pSinkLineIn);
+ AudioMixerSinkReset(pThisCC->pSinkMicIn);
+ AudioMixerSinkReset(pThisCC->pSinkOut);
+}
+
+
+/**
+ * Adds a specific AC'97 driver to the driver chain.
+ *
+ * Only called from ichac97R3Attach().
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThisCC The ring-3 AC'97 device state.
+ * @param pDrv The AC'97 driver to add.
+ */
+static int ichac97R3MixerAddDrv(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, PAC97DRIVER pDrv)
+{
+ int rc = VINF_SUCCESS;
+
+ if (AudioHlpStreamCfgIsValid(&pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg))
+ rc = ichac97R3MixerAddDrvStream(pDevIns, pThisCC->pSinkLineIn,
+ &pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg, pDrv);
+
+ if (AudioHlpStreamCfgIsValid(&pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg))
+ {
+ int rc2 = ichac97R3MixerAddDrvStream(pDevIns, pThisCC->pSinkOut,
+ &pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ if (AudioHlpStreamCfgIsValid(&pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg))
+ {
+ int rc2 = ichac97R3MixerAddDrvStream(pDevIns, pThisCC->pSinkMicIn,
+ &pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Worker for ichac97R3Construct() and ichac97R3Attach().
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThisCC The ring-3 AC'97 device state.
+ * @param uLUN The logical unit which is being attached.
+ * @param ppDrv Attached driver instance on success. Optional.
+ */
+static int ichac97R3AttachInternal(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, unsigned uLUN, PAC97DRIVER *ppDrv)
+{
+ /*
+ * Allocate a new driver structure and try attach the driver.
+ */
+ PAC97DRIVER pDrv = (PAC97DRIVER)RTMemAllocZ(sizeof(AC97DRIVER));
+ AssertPtrReturn(pDrv, VERR_NO_MEMORY);
+ RTStrPrintf(pDrv->szDesc, sizeof(pDrv->szDesc), "Audio driver port (AC'97) for LUN #%u", uLUN);
+
+ PPDMIBASE pDrvBase;
+ int rc = PDMDevHlpDriverAttach(pDevIns, uLUN, &pThisCC->IBase, &pDrvBase, pDrv->szDesc);
+ if (RT_SUCCESS(rc))
+ {
+ pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR);
+ AssertPtr(pDrv->pConnector);
+ if (RT_VALID_PTR(pDrv->pConnector))
+ {
+ pDrv->pDrvBase = pDrvBase;
+ pDrv->uLUN = uLUN;
+
+ /* Attach to driver list if not attached yet. */
+ if (!pDrv->fAttached)
+ {
+ RTListAppend(&pThisCC->lstDrv, &pDrv->Node);
+ pDrv->fAttached = true;
+ }
+
+ if (ppDrv)
+ *ppDrv = pDrv;
+
+ /*
+ * While we're here, give the windows backends a hint about our typical playback
+ * configuration.
+ */
+ if ( pDrv->pConnector
+ && pDrv->pConnector->pfnStreamConfigHint)
+ {
+ /* 48kHz */
+ PDMAUDIOSTREAMCFG Cfg;
+ RT_ZERO(Cfg);
+ Cfg.enmDir = PDMAUDIODIR_OUT;
+ Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
+ Cfg.Device.cMsSchedulingHint = 5;
+ Cfg.Backend.cFramesPreBuffering = UINT32_MAX;
+ PDMAudioPropsInit(&Cfg.Props, 2, true /*fSigned*/, 2, 48000);
+ RTStrPrintf(Cfg.szName, sizeof(Cfg.szName), "output 48kHz 2ch S16 (HDA config hint)");
+
+ pDrv->pConnector->pfnStreamConfigHint(pDrv->pConnector, &Cfg); /* (may trash CfgReq) */
+# if 0
+ /* 44.1kHz */
+ RT_ZERO(Cfg);
+ Cfg.enmDir = PDMAUDIODIR_OUT;
+ Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
+ Cfg.Device.cMsSchedulingHint = 10;
+ Cfg.Backend.cFramesPreBuffering = UINT32_MAX;
+ PDMAudioPropsInit(&Cfg.Props, 2, true /*fSigned*/, 2, 44100);
+ RTStrPrintf(Cfg.szName, sizeof(Cfg.szName), "output 44.1kHz 2ch S16 (HDA config hint)");
+
+ pDrv->pConnector->pfnStreamConfigHint(pDrv->pConnector, &Cfg); /* (may trash CfgReq) */
+# endif
+ }
+
+ LogFunc(("LUN#%u: returns VINF_SUCCESS (pCon=%p)\n", uLUN, pDrv->pConnector));
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pDrv);
+ rc = VERR_PDM_MISSING_INTERFACE_BELOW;
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ LogFunc(("No attached driver for LUN #%u\n", uLUN));
+ else
+ LogFunc(("Attached driver for LUN #%u failed: %Rrc\n", uLUN, rc));
+ RTMemFree(pDrv);
+
+ LogFunc(("LUN#%u: rc=%Rrc\n", uLUN, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnAttach}
+ */
+static DECLCALLBACK(int) ichac97R3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ RT_NOREF(fFlags);
+ LogFunc(("iLUN=%u, fFlags=%#x\n", iLUN, fFlags));
+
+ DEVAC97_LOCK(pDevIns, pThis);
+
+ PAC97DRIVER pDrv;
+ int rc = ichac97R3AttachInternal(pDevIns, pThisCC, iLUN, &pDrv);
+ if (RT_SUCCESS(rc))
+ {
+ int rc2 = ichac97R3MixerAddDrv(pDevIns, pThisCC, pDrv);
+ if (RT_FAILURE(rc2))
+ LogFunc(("ichac97R3MixerAddDrv failed with %Rrc (ignored)\n", rc2));
+ }
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+
+ return rc;
+}
+
+
+/**
+ * Removes a specific AC'97 driver from the driver chain and destroys its
+ * associated streams.
+ *
+ * Only called from ichac97R3Detach().
+ *
+ * @param pDevIns The device instance.
+ * @param pThisCC The ring-3 AC'97 device state.
+ * @param pDrv AC'97 driver to remove.
+ */
+static void ichac97R3MixerRemoveDrv(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, PAC97DRIVER pDrv)
+{
+ if (pDrv->MicIn.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThisCC->pSinkMicIn, pDrv->MicIn.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->MicIn.pMixStrm, pDevIns, true /*fImmediate*/);
+ pDrv->MicIn.pMixStrm = NULL;
+ }
+
+ if (pDrv->LineIn.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThisCC->pSinkLineIn, pDrv->LineIn.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->LineIn.pMixStrm, pDevIns, true /*fImmediate*/);
+ pDrv->LineIn.pMixStrm = NULL;
+ }
+
+ if (pDrv->Out.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThisCC->pSinkOut, pDrv->Out.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->Out.pMixStrm, pDevIns, true /*fImmediate*/);
+ pDrv->Out.pMixStrm = NULL;
+ }
+
+ RTListNodeRemove(&pDrv->Node);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDetach}
+ */
+static DECLCALLBACK(void) ichac97R3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ RT_NOREF(fFlags);
+
+ LogFunc(("iLUN=%u, fFlags=0x%x\n", iLUN, fFlags));
+
+ DEVAC97_LOCK(pDevIns, pThis);
+
+ PAC97DRIVER pDrv;
+ RTListForEach(&pThisCC->lstDrv, pDrv, AC97DRIVER, Node)
+ {
+ if (pDrv->uLUN == iLUN)
+ {
+ /* Remove the driver from our list and destory it's associated streams.
+ This also will un-set the driver as a recording source (if associated). */
+ ichac97R3MixerRemoveDrv(pDevIns, pThisCC, pDrv);
+ LogFunc(("Detached LUN#%u\n", pDrv->uLUN));
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+
+ RTMemFree(pDrv);
+ return;
+ }
+ }
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ LogFunc(("LUN#%u was not found\n", iLUN));
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) ichac97R3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+
+ LogFlowFuncEnter();
+
+ PAC97DRIVER pDrv, pDrvNext;
+ RTListForEachSafe(&pThisCC->lstDrv, pDrv, pDrvNext, AC97DRIVER, Node)
+ {
+ RTListNodeRemove(&pDrv->Node);
+ RTMemFree(pDrv);
+ }
+
+ /* Sanity. */
+ Assert(RTListIsEmpty(&pThisCC->lstDrv));
+
+ /* We don't always go via PowerOff, so make sure the mixer is destroyed. */
+ if (pThisCC->pMixer)
+ {
+ AudioMixerDestroy(pThisCC->pMixer, pDevIns);
+ pThisCC->pMixer = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) ichac97R3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ Assert(iInstance == 0); RT_NOREF(iInstance);
+
+ /*
+ * Initialize data so we can run the destructor without scewing up.
+ */
+ pThisCC->pDevIns = pDevIns;
+ pThisCC->IBase.pfnQueryInterface = ichac97R3QueryInterface;
+ RTListInit(&pThisCC->lstDrv);
+
+ /*
+ * Validate and read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "BufSizeInMs|BufSizeOutMs|Codec|TimerHz|DebugEnabled|DebugPathOut", "");
+
+ /** @devcfgm{ac97,BufSizeInMs,uint16_t,0,2000,0,ms}
+ * The size of the DMA buffer for input streams expressed in milliseconds. */
+ int rc = pHlp->pfnCFGMQueryU16Def(pCfg, "BufSizeInMs", &pThis->cMsCircBufIn, 0);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC97 configuration error: failed to read 'BufSizeInMs' as 16-bit unsigned integer"));
+ if (pThis->cMsCircBufIn > 2000)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE,
+ N_("AC97 configuration error: 'BufSizeInMs' is out of bound, max 2000 ms"));
+
+ /** @devcfgm{ac97,BufSizeOutMs,uint16_t,0,2000,0,ms}
+ * The size of the DMA buffer for output streams expressed in milliseconds. */
+ rc = pHlp->pfnCFGMQueryU16Def(pCfg, "BufSizeOutMs", &pThis->cMsCircBufOut, 0);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC97 configuration error: failed to read 'BufSizeOutMs' as 16-bit unsigned integer"));
+ if (pThis->cMsCircBufOut > 2000)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE,
+ N_("AC97 configuration error: 'BufSizeOutMs' is out of bound, max 2000 ms"));
+
+ /** @devcfgm{ac97,TimerHz,uint16_t,10,1000,100,ms}
+ * Currently the approximate rate at which the asynchronous I/O threads move
+ * data from/to the DMA buffer, thru the mixer and drivers stack, and
+ * to/from the host device/whatever. (It does NOT govern any DMA timer rate any
+ * more as might be hinted at by the name.) */
+ rc = pHlp->pfnCFGMQueryU16Def(pCfg, "TimerHz", &pThis->uTimerHz, AC97_TIMER_HZ_DEFAULT);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC'97 configuration error: failed to read 'TimerHz' as a 16-bit unsigned integer"));
+ if (pThis->uTimerHz < 10 || pThis->uTimerHz > 1000)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE,
+ N_("AC'97 configuration error: 'TimerHz' is out of range (10-1000 Hz)"));
+
+ if (pThis->uTimerHz != AC97_TIMER_HZ_DEFAULT)
+ LogRel(("AC97: Using custom device timer rate: %RU16 Hz\n", pThis->uTimerHz));
+
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThisCC->Dbg.fEnabled, false);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC97 configuration error: failed to read debugging enabled flag as boolean"));
+
+ rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "DebugPathOut", &pThisCC->Dbg.pszOutPath, NULL);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC97 configuration error: failed to read debugging output path flag as string"));
+
+ if (pThisCC->Dbg.fEnabled)
+ LogRel2(("AC97: Debug output will be saved to '%s'\n", pThisCC->Dbg.pszOutPath));
+
+ /*
+ * The AD1980 codec (with corresponding PCI subsystem vendor ID) is whitelisted
+ * in the Linux kernel; Linux makes no attempt to measure the data rate and assumes
+ * 48 kHz rate, which is exactly what we need. Same goes for AD1981B.
+ */
+ char szCodec[20];
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "Codec", &szCodec[0], sizeof(szCodec), "STAC9700");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("AC'97 configuration error: Querying \"Codec\" as string failed"));
+ if (!strcmp(szCodec, "STAC9700"))
+ pThis->enmCodecModel = AC97CODEC_STAC9700;
+ else if (!strcmp(szCodec, "AD1980"))
+ pThis->enmCodecModel = AC97CODEC_AD1980;
+ else if (!strcmp(szCodec, "AD1981B"))
+ pThis->enmCodecModel = AC97CODEC_AD1981B;
+ else
+ return PDMDevHlpVMSetError(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, RT_SRC_POS,
+ N_("AC'97 configuration error: The \"Codec\" value \"%s\" is unsupported"), szCodec);
+
+ LogRel(("AC97: Using codec '%s'\n", szCodec));
+
+ /*
+ * Use an own critical section for the device instead of the default
+ * one provided by PDM. This allows fine-grained locking in combination
+ * with TM when timer-specific stuff is being called in e.g. the MMIO handlers.
+ */
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CritSect, RT_SRC_POS, "AC'97");
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Initialize data (most of it anyway).
+ */
+ /* PCI Device */
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PCIDevSetVendorId(pPciDev, 0x8086); /* 00 ro - intel. */ Assert(pPciDev->abConfig[0x00] == 0x86); Assert(pPciDev->abConfig[0x01] == 0x80);
+ PCIDevSetDeviceId(pPciDev, 0x2415); /* 02 ro - 82801 / 82801aa(?). */ Assert(pPciDev->abConfig[0x02] == 0x15); Assert(pPciDev->abConfig[0x03] == 0x24);
+ PCIDevSetCommand(pPciDev, 0x0000); /* 04 rw,ro - pcicmd. */ Assert(pPciDev->abConfig[0x04] == 0x00); Assert(pPciDev->abConfig[0x05] == 0x00);
+ PCIDevSetStatus(pPciDev, VBOX_PCI_STATUS_DEVSEL_MEDIUM | VBOX_PCI_STATUS_FAST_BACK); /* 06 rwc?,ro? - pcists. */ Assert(pPciDev->abConfig[0x06] == 0x80); Assert(pPciDev->abConfig[0x07] == 0x02);
+ PCIDevSetRevisionId(pPciDev, 0x01); /* 08 ro - rid. */ Assert(pPciDev->abConfig[0x08] == 0x01);
+ PCIDevSetClassProg(pPciDev, 0x00); /* 09 ro - pi. */ Assert(pPciDev->abConfig[0x09] == 0x00);
+ PCIDevSetClassSub(pPciDev, 0x01); /* 0a ro - scc; 01 == Audio. */ Assert(pPciDev->abConfig[0x0a] == 0x01);
+ PCIDevSetClassBase(pPciDev, 0x04); /* 0b ro - bcc; 04 == multimedia.*/Assert(pPciDev->abConfig[0x0b] == 0x04);
+ PCIDevSetHeaderType(pPciDev, 0x00); /* 0e ro - headtyp. */ Assert(pPciDev->abConfig[0x0e] == 0x00);
+ PCIDevSetBaseAddress(pPciDev, 0, /* 10 rw - nambar - native audio mixer base. */
+ true /* fIoSpace */, false /* fPrefetchable */, false /* f64Bit */, 0x00000000); Assert(pPciDev->abConfig[0x10] == 0x01); Assert(pPciDev->abConfig[0x11] == 0x00); Assert(pPciDev->abConfig[0x12] == 0x00); Assert(pPciDev->abConfig[0x13] == 0x00);
+ PCIDevSetBaseAddress(pPciDev, 1, /* 14 rw - nabmbar - native audio bus mastering. */
+ true /* fIoSpace */, false /* fPrefetchable */, false /* f64Bit */, 0x00000000); Assert(pPciDev->abConfig[0x14] == 0x01); Assert(pPciDev->abConfig[0x15] == 0x00); Assert(pPciDev->abConfig[0x16] == 0x00); Assert(pPciDev->abConfig[0x17] == 0x00);
+ PCIDevSetInterruptLine(pPciDev, 0x00); /* 3c rw. */ Assert(pPciDev->abConfig[0x3c] == 0x00);
+ PCIDevSetInterruptPin(pPciDev, 0x01); /* 3d ro - INTA#. */ Assert(pPciDev->abConfig[0x3d] == 0x01);
+
+ if (pThis->enmCodecModel == AC97CODEC_AD1980)
+ {
+ PCIDevSetSubSystemVendorId(pPciDev, 0x1028); /* 2c ro - Dell.) */
+ PCIDevSetSubSystemId(pPciDev, 0x0177); /* 2e ro. */
+ }
+ else if (pThis->enmCodecModel == AC97CODEC_AD1981B)
+ {
+ PCIDevSetSubSystemVendorId(pPciDev, 0x1028); /* 2c ro - Dell.) */
+ PCIDevSetSubSystemId(pPciDev, 0x01ad); /* 2e ro. */
+ }
+ else
+ {
+ PCIDevSetSubSystemVendorId(pPciDev, 0x8086); /* 2c ro - Intel.) */
+ PCIDevSetSubSystemId(pPciDev, 0x0000); /* 2e ro. */
+ }
+
+ /*
+ * Register the PCI device and associated I/O regions.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 0 /*iPciRegion*/, 256 /*cPorts*/,
+ ichac97IoPortNamWrite, ichac97IoPortNamRead, NULL /*pvUser*/,
+ "ICHAC97 NAM", NULL /*paExtDescs*/, &pThis->hIoPortsNam);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 1 /*iPciRegion*/, 64 /*cPorts*/,
+ ichac97IoPortNabmWrite, ichac97IoPortNabmRead, NULL /*pvUser*/,
+ "ICHAC97 NABM", g_aNabmPorts, &pThis->hIoPortsNabm);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Saved state.
+ */
+ rc = PDMDevHlpSSMRegister(pDevIns, AC97_SAVED_STATE_VERSION, sizeof(*pThis), ichac97R3SaveExec, ichac97R3LoadExec);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * 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 = ichac97R3AttachInternal(pDevIns, pThisCC, iLun, NULL /* ppDrv */);
+ if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ LogFunc(("cLUNs=%u\n", iLun));
+ break;
+ }
+ AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc);
+ }
+
+ uint32_t fMixer = AUDMIXER_FLAGS_NONE;
+ if (pThisCC->Dbg.fEnabled)
+ fMixer |= AUDMIXER_FLAGS_DEBUG;
+
+ rc = AudioMixerCreate("AC'97 Mixer", 0 /* uFlags */, &pThisCC->pMixer);
+ AssertRCReturn(rc, rc);
+
+ rc = AudioMixerCreateSink(pThisCC->pMixer, "Line In",
+ PDMAUDIODIR_IN, pDevIns, &pThisCC->pSinkLineIn);
+ AssertRCReturn(rc, rc);
+ rc = AudioMixerCreateSink(pThisCC->pMixer, "Microphone In",
+ PDMAUDIODIR_IN, pDevIns, &pThisCC->pSinkMicIn);
+ AssertRCReturn(rc, rc);
+ rc = AudioMixerCreateSink(pThisCC->pMixer, "PCM Output",
+ PDMAUDIODIR_OUT, pDevIns, &pThisCC->pSinkOut);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create all hardware streams.
+ */
+ AssertCompile(RT_ELEMENTS(pThis->aStreams) == AC97_MAX_STREAMS);
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ rc = ichac97R3StreamConstruct(pThisCC, &pThis->aStreams[i], &pThisCC->aStreams[i], i /* SD# */);
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * Create the emulation timers (one per stream).
+ *
+ * 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 AC'97 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[] = { "AC97 PI", "AC97 PO", "AC97 MC" };
+ AssertCompile(RT_ELEMENTS(s_apszNames) == AC97_MAX_STREAMS);
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, ichac97R3Timer, &pThis->aStreams[i],
+ TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_RING0, s_apszNames[i], &pThis->aStreams[i].hTimer);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpTimerSetCritSect(pDevIns, pThis->aStreams[i].hTimer, &pThis->CritSect);
+ AssertRCReturn(rc, rc);
+ }
+
+ ichac97R3Reset(pDevIns);
+
+ /*
+ * Info items.
+ */
+ //PDMDevHlpDBGFInfoRegister(pDevIns, "ac97", "AC'97 registers. (ac97 [register case-insensitive])", ichac97R3DbgInfo);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ac97bdl", "AC'97 buffer descriptor list (BDL). (ac97bdl [stream number])",
+ ichac97R3DbgInfoBDL);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ac97stream", "AC'97 stream info. (ac97stream [stream number])", ichac97R3DbgInfoStream);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ac97mixer", "AC'97 mixer state.", ichac97R3DbgInfoMixer);
+
+ /*
+ * Register statistics.
+ */
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUnimplementedNabmReads, STAMTYPE_COUNTER, "UnimplementedNabmReads", STAMUNIT_OCCURENCES, "Unimplemented NABM register reads.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUnimplementedNabmWrites, STAMTYPE_COUNTER, "UnimplementedNabmWrites", STAMUNIT_OCCURENCES, "Unimplemented NABM register writes.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUnimplementedNamReads, STAMTYPE_COUNTER, "UnimplementedNamReads", STAMUNIT_OCCURENCES, "Unimplemented NAM register reads.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUnimplementedNamWrites, STAMTYPE_COUNTER, "UnimplementedNamWrites", STAMUNIT_OCCURENCES, "Unimplemented NAM register writes.");
+# ifdef VBOX_WITH_STATISTICS
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTimer, STAMTYPE_PROFILE, "Timer", STAMUNIT_TICKS_PER_CALL, "Profiling ichac97Timer.");
+# endif
+ for (unsigned idxStream = 0; idxStream < RT_ELEMENTS(pThis->aStreams); idxStream++)
+ {
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].cbDmaPeriod, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Bytes to transfer in the current DMA period.", "Stream%u/cbTransferChunk", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].Regs.cr, STAMTYPE_X8, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE,
+ "Control register (CR), bit 0 is the run bit.", "Stream%u/reg-CR", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].Regs.sr, STAMTYPE_X16, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE,
+ "Status register (SR).", "Stream%u/reg-SR", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.Cfg.Props.uHz, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_HZ,
+ "The stream frequency.", "Stream%u/Hz", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.Cfg.Props.cbFrame, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "The frame size.", "Stream%u/FrameSize", 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, &pThisCC->aStreams[idxStream].State.StatDmaBufSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Size of the internal DMA buffer.", "Stream%u/DMABufSize", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaBufUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Number of bytes used in the internal DMA buffer.", "Stream%u/DMABufUsed", 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 (ichac97R3GetDirFromSD(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.StatDmaSkippedDch, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "DMA transfer period skipped, controller halted (DCH).", "Stream%u/DMASkippedDch", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaSkippedPendingBcis, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "DMA transfer period skipped because of BCIS pending.", "Stream%u/DMASkippedPendingBCIS", idxStream);
+
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatStart, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "Starting the stream.", "Stream%u/Start", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatStop, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "Stopping the stream.", "Stream%u/Stop", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatReset, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "Resetting the stream.", "Stream%u/Reset", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatReSetUpChanged, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "ichac97R3StreamReSetUp when recreating the streams.", "Stream%u/ReSetUp-Change", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatReSetUpSame, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "ichac97R3StreamReSetUp when no change.", "Stream%u/ReSetUp-NoChange", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatWriteCr, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "CR register writes.", "Stream%u/WriteCr", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatWriteLviRecover, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "LVI register writes recovering from underflow.", "Stream%u/WriteLviRecover", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].StatWriteLvi, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "LVI register writes (non-recoving).", "Stream%u/WriteLvi", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].StatWriteSr1, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "SR register 1-byte writes.", "Stream%u/WriteSr-1byte", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].StatWriteSr2, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "SR register 2-byte writes.", "Stream%u/WriteSr-2byte", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].StatWriteBdBar, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "BDBAR register writes.", "Stream%u/WriteBdBar", idxStream);
+ }
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) ichac97RZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+
+ int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsNam, ichac97IoPortNamWrite, ichac97IoPortNamRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsNabm, ichac97IoPortNabmWrite, ichac97IoPortNabmRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RING3 */
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceICHAC97 =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "ichac97",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE
+ | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION /* stream clearnup with working drivers */,
+ /* .fClass = */ PDM_DEVREG_CLASS_AUDIO,
+ /* .cMaxInstances = */ 1,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(AC97STATE),
+ /* .cbInstanceCC = */ CTX_EXPR(sizeof(AC97STATER3), 0, 0),
+ /* .cbInstanceRC = */ 0,
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "ICH AC'97 Audio Controller",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ ichac97R3Construct,
+ /* .pfnDestruct = */ ichac97R3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ ichac97R3Reset,
+ /* .pfnSuspend = */ NULL,
+ /* .pfnResume = */ NULL,
+ /* .pfnAttach = */ ichac97R3Attach,
+ /* .pfnDetach = */ ichac97R3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ ichac97R3PowerOff,
+ /* .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 = */ ichac97RZConstruct,
+ /* .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 = */ ichac97RZConstruct,
+ /* .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 */
+