diff options
Diffstat (limited to 'Adlib.cpp')
-rw-r--r-- | Adlib.cpp | 783 |
1 files changed, 783 insertions, 0 deletions
diff --git a/Adlib.cpp b/Adlib.cpp new file mode 100644 index 0000000..cd4d6e2 --- /dev/null +++ b/Adlib.cpp @@ -0,0 +1,783 @@ +/* + * VirtualBox ExtensionPack Skeleton + * Copyright (C) 2006-2020 Oracle Corporation + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * VMusic - a VirtualBox extension pack with various music devices + * Copyright (C) 2022 Javier S. Pedro + * + * 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; either version 2 + * of the License, or (at your option) any later version. + + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_ENABLED 1 +#define LOG_ENABLE_FLOW 1 +#define LOG_GROUP LOG_GROUP_DEV_SB16 +#include <VBox/vmm/pdmdev.h> +#ifndef IN_RING3 +# include <VBox/vmm/pdmapi.h> +#endif +#include <VBox/AssertGuest.h> +#include <VBox/version.h> +#include <iprt/assert.h> +#include <iprt/mem.h> + +#include "opl3.h" + +#if RT_OPSYS == RT_OPSYS_LINUX +#include "pcmalsa.h" +typedef PCMOutAlsa PCMOutBackend; +#elif RT_OPSYS == RT_OPSYS_WINDOWS +#include "pcmwin.h" +typedef PCMOutWin PCMOutBackend; +#endif + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +#define ADLIB_DEFAULT_IO_BASE 0x388 + +#define ADLIB_DEFAULT_OUT_DEVICE "default" +#define ADLIB_DEFAULT_SAMPLE_RATE 22055 /* Hz */ +#define ADLIB_NUM_CHANNELS 2 /* as we are actually using OPL3 */ + +enum { + ADLIB_PORT_ADDR = 0, + ADLIB_PORT_STATUS = 0, + ADLIB_PORT_DATA = 1, + ADLIB_PORT_ADDR2 = 2, + ADLIB_PORT_DATA2 = 3 +}; + +/** The saved state version. */ +#define ADLIB_SAVED_STATE_VERSION 1 + +/** The render thread will shutdown if this time passes since the last OPL register write. */ +#define ADLIB_RENDER_THREAD_TIMEOUT 5000 /* in millisec */ + +#define OPL2_NUM_IO_PORTS 2 +#define OPL3_NUM_IO_PORTS 4 + +#define OPL_TIMER1_PERIOD 80 /* microseconds */ +#define OPL_TIMER2_PERIOD 320 + +enum { + OPL_REG_WAVEFORM_ENABLE = 0x01, + OPL_REG_TIMER1 = 0x02, + OPL_REG_TIMER2 = 0x03, + OPL_REG_TIMER_CTRL = 0x04, + OPL_REG_FM_MODE = 0x08 +}; + +/** Device configuration & state. */ +typedef struct { + /* Device configuration. */ + /** Whether to emulate an OPL3. */ + bool fOPL3; + /** Base port. */ + RTIOPORT uPort; + /** Base port for mirror (e.g. SB16 compatibility). May be 0. */ + RTIOPORT uMirrorPort; + /** Sample rate for PCM output. */ + uint16_t uSampleRate; + /** Device for PCM output. */ + R3PTRTYPE(char *) pszOutDevice; + + /* Runtime state. */ + /** Audio output device */ + PCMOutBackend pcmOut; + /** Thread that connects to PCM out, renders and pushes audio data. */ + RTTHREAD hRenderThread; + /** Buffer for the rendering thread to use. */ + R3PTRTYPE(uint8_t *) pbRenderBuf; + size_t uRenderBufSize; + /** Flag to signal render thread to shut down. */ + bool volatile fShutdown; + /** Flag from render thread indicated it has shutdown (e.g. due to error or timeout). */ + bool volatile fStopped; + /** (System clock) timestamp of last OPL chip access. */ + uint64_t tmLastWrite; + + /** To protect access to opl3_chip from the render thread and main thread. */ + RTCRITSECT critSect; + /** Handle to nuked. */ + opl3_chip opl; + + /** Current selected register index */ + uint16_t oplReg; + + /** OPL timer status */ + uint8_t timer1Value, timer2Value; + uint64_t timer1Expire, timer2Expire; /* (virtual clock) timestamps */ + bool timer1Enable, timer2Enable; + + IOMIOPORTHANDLE hIoPorts; + IOMIOPORTHANDLE hMirrorPorts; +} ADLIBSTATE; +typedef ADLIBSTATE *PADLIBSTATE; + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + +static uint64_t adlibCalculateTimerExpire(PPDMDEVINS pDevIns, uint8_t value, uint64_t period) +{ + uint64_t delay_usec = (0x100 - value) * period; + if (delay_usec < 100) delay_usec = 0; // short delay: Likely just checking for OPL precense; fire timer now. + uint64_t freq = PDMDevHlpTMTimeVirtGetFreq(pDevIns); + uint64_t delay_ticks = (delay_usec * freq) / 1000000UL /*1usec in hz*/; + uint64_t now_ticks = PDMDevHlpTMTimeVirtGet(pDevIns); + Log3Func(("value=%02x delay_usec=%llu virtfreq=%llu now_ticks=%llu delay_ticks=%llu\n", value, delay_usec, freq, now_ticks, delay_ticks)); + return now_ticks + delay_ticks; +} + +/** + * @callback_method_impl{FNRTTHREAD} + */ +static DECLCALLBACK(int) adlibRenderThread(RTTHREAD ThreadSelf, void *pvUser) +{ + RT_NOREF(ThreadSelf); + PADLIBSTATE pThis = (PADLIBSTATE)pvUser; + PCMOutBackend *pPcmOut = &pThis->pcmOut; + + // Compute the max number of frames we can store on our temporary buffer. + int16_t *buf = (int16_t*) pThis->pbRenderBuf; + ssize_t buf_size = pThis->uRenderBufSize; + ssize_t buf_samples = buf_size / sizeof(int16_t); + ssize_t buf_frames = buf_samples / ADLIB_NUM_CHANNELS; + + Log(("adlib: Starting render thread\n")); + + int rc = pPcmOut->open(pThis->pszOutDevice, pThis->uSampleRate, ADLIB_NUM_CHANNELS); + AssertLogRelRCReturn(rc, rc); + + while (!ASMAtomicReadBool(&pThis->fShutdown) + && ASMAtomicReadU64(&pThis->tmLastWrite) + ADLIB_RENDER_THREAD_TIMEOUT >= RTTimeSystemMilliTS()) { + ssize_t avail = pPcmOut->avail(); + + if (avail < 0) { + LogWarn(("adlib: render thread avail err=%d\n", avail)); + break; + } + if (avail == 0) { + rc = pPcmOut->wait(); + AssertLogRelRCBreak(rc); + avail = pPcmOut->avail(); + if (avail < 0) { + LogWarn(("adlib: render thread wait avail err=%d\n", avail)); + break; + } + } + + avail = RT_MIN(avail, buf_frames); + + Log3(("rendering %ld frames\n", avail)); + + RTCritSectEnter(&pThis->critSect); + OPL3_GenerateStream(&pThis->opl, buf, avail); + RTCritSectLeave(&pThis->critSect); + + ssize_t written_frames = pPcmOut->write(buf, avail); + if (written_frames < 0) { + LogWarn(("adlib: render thread write err=%d\n", written_frames)); + break; + } + } + + rc = pPcmOut->close(); + AssertLogRelRC(rc); + + Log(("adlib: Stopping render thread\n")); + + ASMAtomicWriteBool(&pThis->fStopped, true); + + return VINF_SUCCESS; +} + +/** Waits for the render thread to finish and reaps it. */ +static int adlibReapRenderThread(PPDMDEVINS pDevIns, RTMSINTERVAL millies = 1000) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + if (pThis->hRenderThread != NIL_RTTHREAD) { + int rc = RTThreadWait(pThis->hRenderThread, millies, NULL); + if (RT_SUCCESS(rc)) { + pThis->hRenderThread = NIL_RTTHREAD; + } else { + LogWarn(("adlib%d: render thread did not terminate (%Rrc)\n", pDevIns->iInstance, rc)); + AssertRCReturn(rc, rc); + } + } + + return VINF_SUCCESS; +} + +/** Raises signal for render thread to stop; potentially waits for it. */ +static int adlibStopRenderThread(PPDMDEVINS pDevIns, bool wait = false) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + ASMAtomicWriteBool(&pThis->fShutdown, true); + + if (wait) { + int rc = adlibReapRenderThread(pDevIns, 30000); + AssertRCReturn(rc, rc); + } + + return VINF_SUCCESS; +} + +static int adlibResetRenderThread(PPDMDEVINS pDevIns) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + // Thread has to be shutdown + AssertReturn(ASMAtomicReadBool(&pThis->fShutdown), VERR_INVALID_STATE); + + int rc = adlibReapRenderThread(pDevIns); + if (RT_SUCCESS(rc)) { + pThis->fShutdown = false; + pThis->fStopped = false; + } else { + LogWarn(("adlib%d: can't reset render thread, it did not terminate (%Rrc)\n", rc)); + } + + return rc; +} + +static void adlibWakeRenderThread(PPDMDEVINS pDevIns) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + ASMAtomicWriteU64(&pThis->tmLastWrite, RTTimeSystemMilliTS()); + + AssertReturnVoid(!ASMAtomicReadBool(&pThis->fShutdown)); + + // Reap any existing render thread if it had stopped + if (ASMAtomicReadBool(&pThis->fStopped)) { + int rc = adlibReapRenderThread(pDevIns); + AssertRCReturnVoid(rc); + } + + // If there is no existing render thread, start a new one + if (pThis->hRenderThread == NIL_RTTHREAD) { + pThis->fShutdown = false; + pThis->fStopped = false; + + Log2(("Creating render thread\n")); + + int rc = RTThreadCreateF(&pThis->hRenderThread, adlibRenderThread, pThis, 0, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, + "adlib%u_render", pDevIns->iInstance); + if (RT_FAILURE(rc)) { + LogWarn(("adlib%d: could not start render thread (%Rrc)\n", pDevIns->iInstance, rc)); + AssertRCReturnVoid(rc); + } + } +} + +static uint8_t adlibReadStatus(PPDMDEVINS pDevIns) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + uint8_t status = 0; + + /* The status byte has the following structure: + Bit 7 - set if either timer has expired. + 6 - set if timer 1 has expired. + 5 - set if timer 2 has expired. */ + + uint64_t tmNow = PDMDevHlpTMTimeVirtGet(pDevIns); + + Log3Func(("tmNow=%llu timer1=%llu timer2=%llu\n", tmNow, + pThis->timer1Enable ? pThis->timer1Expire : 0, + pThis->timer2Enable ? pThis->timer2Expire : 0)); + + if (pThis->timer1Enable && tmNow > pThis->timer1Expire) { + status |= RT_BIT(7) | RT_BIT(6); + } + if (pThis->timer2Enable && tmNow > pThis->timer2Expire) { + status |= RT_BIT(7) | RT_BIT(5); + } + + Log2Func(("status=0x%x\n", status)); + + return status; +} + +static void adlibWriteRegister(PPDMDEVINS pDevIns, uint16_t reg, uint8_t value) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + // Any write to a register causes the render thread to be waken up + adlibWakeRenderThread(pDevIns); + + Log2Func(("0x%x = 0x%x\n", reg, value)); + + switch (reg) + { + case OPL_REG_TIMER1: + /* Timer 1 Data. If Timer 1 is enabled, the value in this + register will be incremented until it overflows. Upon + overflow, the sound card will signal a TIMER interrupt + (INT 08) and set bits 7 and 6 in its status byte. The + value for this timer is incremented every eighty (80) + microseconds. */ + pThis->timer1Value = value; + if (pThis->timer1Enable) { + pThis->timer1Expire = adlibCalculateTimerExpire(pDevIns, pThis->timer1Value, OPL_TIMER1_PERIOD); + } + break; + + case OPL_REG_TIMER2: + /* Timer 2 Data. If Timer 2 is enabled, the value in this + register will be incremented until it overflows. Upon + overflow, the sound card will signal a TIMER interrupt + (INT 08) and set bits 7 and 5 in its status byte. The + value for this timer is incremented every three hundred + twenty (320) microseconds. */ + pThis->timer2Value = value; + if (pThis->timer2Enable) { + pThis->timer2Expire = adlibCalculateTimerExpire(pDevIns, pThis->timer2Value, OPL_TIMER2_PERIOD); + } + break; + + case OPL_REG_TIMER_CTRL: + /* Timer Control Byte + bit 7 - Resets the flags for timers 1 & 2. If set, + all other bits are ignored. + bit 6 - Masks Timer 1. If set, bit 0 is ignored. + bit 5 - Masks Timer 2. If set, bit 1 is ignored. + bit 1 - When clear, Timer 2 does not operate. + When set, the value from byte 03 is loaded into + Timer 2, and incrementation begins. + bit 0 - When clear, Timer 1 does not operate. + When set, the value from byte 02 is loaded into + Timer 1, and incrementation begins. */ + if (value & RT_BIT(7)) { + pThis->timer1Enable = false; + pThis->timer2Enable = false; + } else { + if (!(value & RT_BIT(6))) { + pThis->timer1Enable = value & RT_BIT(0); + if (pThis->timer1Enable) { + pThis->timer1Expire = adlibCalculateTimerExpire(pDevIns, pThis->timer1Value, OPL_TIMER1_PERIOD); + } + } + if (!(value & RT_BIT(5))) { + pThis->timer2Enable = value & RT_BIT(1); + if (pThis->timer2Enable) { + pThis->timer2Expire = adlibCalculateTimerExpire(pDevIns, pThis->timer2Value, OPL_TIMER2_PERIOD); + } + } + } + break; + + default: + RTCritSectEnter(&pThis->critSect); + OPL3_WriteRegBuffered(&pThis->opl, reg, value); + RTCritSectLeave(&pThis->critSect); + break; + } +} + +/** + * @callback_method_impl{FNIOMIOPORTNEWIN} + */ +static DECLCALLBACK(VBOXSTRICTRC) adlibIoPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb) +{ + RT_NOREF(pvUser); + if (cb == 1) + { + uint32_t uValue; + + switch (offPort) + { + case ADLIB_PORT_STATUS: + uValue = adlibReadStatus(pDevIns); + break; + default: + ASSERT_GUEST_MSG_FAILED(("invalid port %#x\n", offPort)); + uValue = 0xff; + break; + } + + Log3Func(("read port %u: %#04x\n", offPort, uValue)); + + *pu32 = uValue; + return VINF_SUCCESS; + } + ASSERT_GUEST_MSG_FAILED(("offPort=%#x cb=%d\n", offPort, cb)); + return VERR_IOM_IOPORT_UNUSED; +} + +/** + * @callback_method_impl{FNIOMIOPORTNEWOUT} + */ +static DECLCALLBACK(VBOXSTRICTRC) adlibIoPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb) +{ + RT_NOREF(pvUser); + if (cb == 1) + { + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + Log3Func(("write port %u: %#04x\n", offPort, u32)); + + uint8_t val = u32; + + switch (offPort) + { + case ADLIB_PORT_ADDR: + pThis->oplReg = val; + break; + case ADLIB_PORT_ADDR2: + pThis->oplReg = val | 0x100; + break; + case ADLIB_PORT_DATA: + case ADLIB_PORT_DATA2: + adlibWriteRegister(pDevIns, pThis->oplReg, val); + break; + + default: + ASSERT_GUEST_MSG_FAILED(("invalid port %#x\n", offPort)); + break; + } + } + else + ASSERT_GUEST_MSG_FAILED(("offPort=%#x cb=%d\n", offPort, cb)); + return VINF_SUCCESS; +} + +# ifdef IN_RING3 + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) adlibR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + // TODO + NOREF(pThis); + NOREF(pHlp); + NOREF(pSSMHandle); + + return 0; +} + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) adlibR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle, uint32_t uVersion, uint32_t uPass) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + Assert(uPass == SSM_PASS_FINAL); + NOREF(uPass); + + // TODO + NOREF(pThis); + NOREF(pHlp); + NOREF(pSSMHandle); + + if (uVersion > ADLIB_SAVED_STATE_VERSION) + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + + return 0; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) adlibR3Reset(PPDMDEVINS pDevIns) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + RTCritSectEnter(&pThis->critSect); + OPL3_Reset(&pThis->opl, pThis->uSampleRate); + RTCritSectLeave(&pThis->critSect); + pThis->oplReg = 0; + pThis->timer1Enable = false; + pThis->timer1Expire = 0; + pThis->timer1Value = 0; + pThis->timer2Enable = false; + pThis->timer2Expire = 0; + pThis->timer2Value = 0; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnSuspend} + */ +static DECLCALLBACK(void) adlibR3Suspend(PPDMDEVINS pDevIns) +{ + adlibStopRenderThread(pDevIns); +} + +/** + * @interface_method_impl{PDMDEVREG,pfnResume} + */ +static DECLCALLBACK(void) adlibR3Resume(PPDMDEVINS pDevIns) +{ + adlibResetRenderThread(pDevIns); +} + +/** + * @interface_method_impl{PDMDEVREG,pfnPowerOff} + */ +static DECLCALLBACK(void) adlibR3PowerOff(PPDMDEVINS pDevIns) +{ + adlibStopRenderThread(pDevIns); +} + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct} + */ +static DECLCALLBACK(int) adlibR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + int rc; + + Assert(iInstance == 0); + + /* + * Validate and read the configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "OPL3|Port|MirrorPort|OutDevice|SampleRate", ""); + + rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "OPL3", &pThis->fOPL3, true); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"OPL3\" from the config")); + + rc = pHlp->pfnCFGMQueryPortDef(pCfg, "Port", &pThis->uPort, ADLIB_DEFAULT_IO_BASE); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"Port\" from the config")); + + rc = pHlp->pfnCFGMQueryPortDef(pCfg, "MirrorPort", &pThis->uMirrorPort, 0); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"MirrorPort\" from the config")); + + rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "OutDevice", &pThis->pszOutDevice, ADLIB_DEFAULT_OUT_DEVICE); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"OutDevice\" from the config")); + + rc = pHlp->pfnCFGMQueryU16Def(pCfg, "SampleRate", &pThis->uSampleRate, ADLIB_DEFAULT_SAMPLE_RATE); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"SampleRate\" from the config")); + + /* + * Initialize the device state. + */ + adlibR3Reset(pDevIns); + + /* Initialize now the buffer that will be used by the render thread. */ + // Give it as much space as it could possibly need, like half a second + pThis->uRenderBufSize = (pThis->uSampleRate * ADLIB_NUM_CHANNELS * sizeof(uint16_t)) / 2; + pThis->pbRenderBuf = (uint8_t *) RTMemAllocZ(pThis->uRenderBufSize); + AssertReturn(pThis->pbRenderBuf, VERR_NO_MEMORY); + + /* Prepare the render thread, but not create it yet. */ + pThis->fShutdown = false; + pThis->fStopped = false; + pThis->hRenderThread = NIL_RTTHREAD; + pThis->tmLastWrite = 0; + rc = RTCritSectInit(&pThis->critSect); + AssertRCReturn(rc, rc); + + /* + * Register I/O ports. + */ + static const IOMIOPORTDESC s_aDescs[] = + { + { "Status", "Address", "Status register", "Primary index register" }, // base + 00h + { NULL, "Data", NULL, "Primary data register" }, // base + 01h + { NULL, "Address2", NULL, "Secondary index register (OPL3)" }, // base + 02h + { NULL, "Data2", NULL, "Secondary data register (OPL3)" }, // base + 03h + { NULL } + }; + const unsigned int numPorts = pThis->fOPL3 ? OPL3_NUM_IO_PORTS : OPL2_NUM_IO_PORTS; + rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pThis->uPort, numPorts, adlibIoPortWrite, adlibIoPortRead, + "Adlib", s_aDescs, &pThis->hIoPorts); + AssertRCReturn(rc, rc); + + if (pThis->uMirrorPort) { + rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pThis->uMirrorPort, numPorts, adlibIoPortWrite, adlibIoPortRead, + "AdlibMirror", s_aDescs, &pThis->hMirrorPorts); + AssertRCReturn(rc, rc); + } else { + pThis->hMirrorPorts = 0; + } + + /* + * Register saved state. + */ + rc = PDMDevHlpSSMRegister(pDevIns, ADLIB_SAVED_STATE_VERSION, sizeof(*pThis), adlibR3SaveExec, adlibR3LoadExec); + AssertRCReturn(rc, rc); + + LogRel(("adlib%i: Configured on ports 0x%x-0x%x\n", iInstance, pThis->uPort, pThis->uPort + numPorts - 1)); + if (pThis->uMirrorPort && pThis->hMirrorPorts) { + LogRel(("adlib%i: Mirrored on ports 0x%x-0x%x\n", iInstance, pThis->uMirrorPort, pThis->uMirrorPort + numPorts - 1)); + } + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnDestruct} + */ +static DECLCALLBACK(int) adlibR3Destruct(PPDMDEVINS pDevIns) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + /* Shutdown AND terminate the render thread. */ + adlibStopRenderThread(pDevIns, true); + + if (pThis->pbRenderBuf) { + RTMemFree(pThis->pbRenderBuf); + pThis->pbRenderBuf = NULL; + } + + if (pThis->pszOutDevice) { + PDMDevHlpMMHeapFree(pDevIns, pThis->pszOutDevice); + pThis->pszOutDevice = NULL; + } + + return VINF_SUCCESS; +} + +# endif /* !IN_RING3 */ + + +/** + * The device registration structure. + */ +static const PDMDEVREG g_DeviceAdlib = +{ + /* .u32Version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "adlib", + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_NEW_STYLE, + /* .fClass = */ PDM_DEVREG_CLASS_AUDIO, + /* .cMaxInstances = */ 1, + /* .uSharedVersion = */ 42, + /* .cbInstanceShared = */ sizeof(ADLIBSTATE), + /* .cbInstanceCC = */ 0, + /* .cbInstanceRC = */ 0, + /* .cMaxPciDevices = */ 0, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "Adlib.", +# if defined(IN_RING3) + /* .pszRCMod = */ "", + /* .pszR0Mod = */ "", + /* .pfnConstruct = */ adlibR3Construct, + /* .pfnDestruct = */ adlibR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ adlibR3Reset, + /* .pfnSuspend = */ adlibR3Suspend, + /* .pfnResume = */ adlibR3Resume, + /* .pfnAttach = */ NULL, + /* .pfnDetach = */ NULL, + /* .pfnQueryInterface = */ NULL, + /* .pfnInitComplete = */ NULL, + /* .pfnPowerOff = */ adlibR3PowerOff, + /* .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 = */ NULL, + /* .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 = */ NULL, + /* .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 +}; + +# ifdef VBOX_IN_EXTPACK_R3 + +/** + * @callback_method_impl{FNPDMVBOXDEVICESREGISTER} + */ +extern "C" DECLEXPORT(int) VBoxDevicesRegister(PPDMDEVREGCB pCallbacks, uint32_t u32Version) +{ + AssertLogRelMsgReturn(u32Version >= VBOX_VERSION, + ("u32Version=%#x VBOX_VERSION=%#x\n", u32Version, VBOX_VERSION), + VERR_EXTPACK_VBOX_VERSION_MISMATCH); + AssertLogRelMsgReturn(pCallbacks->u32Version == PDM_DEVREG_CB_VERSION, + ("pCallbacks->u32Version=%#x PDM_DEVREG_CB_VERSION=%#x\n", pCallbacks->u32Version, PDM_DEVREG_CB_VERSION), + VERR_VERSION_MISMATCH); + + return pCallbacks->pfnRegister(pCallbacks, &g_DeviceAdlib); +} + +# endif /* !VBOX_IN_EXTPACK_R3 */ + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ |