diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
commit | 16f504a9dca3fe3b70568f67b7d41241ae485288 (patch) | |
tree | c60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Devices/Audio/DevIchAc97.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.tar.xz virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.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.cpp | 4800 |
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 */ + |