diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e0e25a950c..3a6ad22001 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -45,6 +45,7 @@ jobs:
7z x -y msys64.7z "-oC:\tools\"
echo "Updating MSYS2 environment..."
C:\tools\msys64\usr\bin\bash.exe -lc "pacman -Syu --noconfirm"
+ C:\tools\msys64\usr\bin\bash.exe -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-libsamplerate"
# - name: Initialize Compiler Cache
# id: cache
# uses: actions/cache@v1
@@ -99,6 +100,7 @@ jobs:
libgtk-3-dev \
libpixman-1-dev \
libsdl2-dev \
+ libsamplerate0-dev \
ccache
- name: Initialize Compiler Cache
id: cache
@@ -152,6 +154,7 @@ jobs:
libepoxy \
pixman \
pkg-config \
+ libsamplerate \
sdl2
- name: Initialize Compiler Cache
id: cache
diff --git a/configure b/configure
index 2d117495df..f6feb995fe 100755
--- a/configure
+++ b/configure
@@ -4564,6 +4564,15 @@ else
feature_not_found "openssl" "Install openssl-dev"
fi
+##########################################
+# libsamplerate probe
+if $pkg_config --exists samplerate; then
+ QEMU_CFLAGS="$QEMU_CFLAGS $($pkg_config --cflags samplerate)"
+ LIBS="$LIBS $($pkg_config --libs samplerate)"
+else
+ feature_not_found "samplerate" "Install libsamplerate0-dev"
+fi
+
##########################################
# libxml2 probe
if test "$libxml2" != "no" ; then
diff --git a/hw/audio/ac97.c b/hw/audio/ac97.c
index cb74e32aaa..0a5f5bd59f 100644
--- a/hw/audio/ac97.c
+++ b/hw/audio/ac97.c
@@ -100,7 +100,8 @@ enum {
#define GS_MINT (1<<7) /* ro */
#define GS_POINT (1<<6) /* ro */
#define GS_PIINT (1<<5) /* ro */
-#define GS_RSRVD ((1<<4)|(1<<3))
+#define GS_SOINT (1<<4) /* ro */
+#define GS_RSRVD (1<<3)
#define GS_MOINT (1<<2) /* ro */
#define GS_MIINT (1<<1) /* ro */
#define GS_GSCI 1 /* rwc */
@@ -112,6 +113,7 @@ enum {
GS_MINT| \
GS_POINT| \
GS_PIINT| \
+ GS_SOINT| \
GS_RSRVD| \
GS_MOINT| \
GS_MIINT)
@@ -176,7 +178,7 @@ enum {
CAS = 0x34
};
-#define GET_BM(index) (((index) >> 4) & 3)
+#define GET_BM(index) (((index) >> 4) & 7)
static void po_callback (void *opaque, int free);
static void pi_callback (void *opaque, int avail);
@@ -274,6 +276,9 @@ static void voice_set_active (AC97LinkState *s, int bm_index, int on)
AUD_set_active_in (s->voice_mc, on);
break;
+ case SO_INDEX:
+ break;
+
default:
AUD_log ("ac97", "invalid bm_index(%d) in voice_set_active\n", bm_index);
break;
@@ -368,6 +373,9 @@ static void open_voice (AC97LinkState *s, int index, int freq)
&as
);
break;
+
+ case SO_INDEX:
+ break;
}
}
else {
@@ -387,6 +395,9 @@ static void open_voice (AC97LinkState *s, int index, int freq)
AUD_close_in (&s->card, s->voice_mc);
s->voice_mc = NULL;
break;
+
+ case SO_INDEX:
+ break;
}
}
}
@@ -659,6 +670,7 @@ static void nam_writew (void *opaque, uint32_t addr, uint32_t val)
default:
dolog ("U nam writew %#x <- %#x\n", addr, val);
mixer_store (s, index, val);
+ assert(0);
break;
}
}
@@ -729,6 +741,7 @@ static uint32_t nabm_readb (void *opaque, uint32_t addr)
break;
default:
dolog ("U nabm readb %#x -> %#x\n", addr, val);
+ assert(0);
break;
}
return val;
@@ -760,6 +773,7 @@ static uint32_t nabm_readw (void *opaque, uint32_t addr)
break;
default:
dolog ("U nabm readw %#x -> %#x\n", addr, val);
+ val = nabm_readb(opaque, addr) | (nabm_readb(opaque, addr + 1) << 8);
break;
}
return val;
@@ -809,6 +823,7 @@ static uint32_t nabm_readl (void *opaque, uint32_t addr)
break;
default:
dolog ("U nabm readl %#x -> %#x\n", addr, val);
+ val = nabm_readw(opaque, addr) | (nabm_readw(opaque, addr + 2) << 16);
break;
}
return val;
@@ -894,6 +909,8 @@ static void nabm_writew (void *opaque, uint32_t addr, uint32_t val)
break;
default:
dolog ("U nabm writew %#x <- %#x\n", addr, val);
+ nabm_writeb(opaque, addr, val & 0xff);
+ nabm_writeb(opaque, addr + 1, (val >> 8) & 0xff);
break;
}
}
@@ -929,6 +946,8 @@ static void nabm_writel (void *opaque, uint32_t addr, uint32_t val)
break;
default:
dolog ("U nabm writel %#x <- %#x\n", addr, val);
+ nabm_writew(opaque, addr, val & 0xffff);
+ nabm_writew(opaque, addr + 2, (val >> 16) & 0xffff);
break;
}
}
diff --git a/hw/xbox/Makefile.objs b/hw/xbox/Makefile.objs
index b1581e6378..f20a76c035 100644
--- a/hw/xbox/Makefile.objs
+++ b/hw/xbox/Makefile.objs
@@ -3,10 +3,9 @@ obj-y += chihiro.o
obj-y += xbox_pci.o acpi_xbox.o
obj-y += amd_smbus.o smbus_xbox_smc.o smbus_cx25871.o smbus_adm1032.o smbus_storage.o eeprom_generation.o
obj-y += nvnet.o
-obj-y += mcpx_apu.o mcpx_aci.o
obj-y += lpc47m157.o
obj-y += xid.o
obj-y += chihiro-usb.o
-obj-y += dsp/
obj-y += nv2a/
+obj-y += mcpx/
diff --git a/hw/xbox/adpcm.h b/hw/xbox/adpcm.h
deleted file mode 100644
index e59c5c2bfc..0000000000
--- a/hw/xbox/adpcm.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * ADPCM decoder
- *
- * Copyright (c) 2017 Jannik Vogel
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see .
- */
-
-
-// See https://wiki.multimedia.cx/index.php/IMA_ADPCM for more information
-
-#include
-#include
-
-static int8_t ima_index_table[16] = {
- -1, -1, -1, -1, 2, 4, 6, 8,
- -1, -1, -1, -1, 2, 4, 6, 8
-};
-
-static uint16_t ima_step_table[89] = {
- 7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
- 19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
- 50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
- 130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
- 337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
- 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
- 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
- 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
- 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
-};
-
-typedef struct {
- int32_t predictor;
- int8_t step_index;
- uint16_t step;
-} ADPCMDecoder;
-
-static void adpcm_decoder_initialize(ADPCMDecoder* d, int16_t predictor, int8_t step_index) {
- d->predictor = predictor;
- d->step_index = step_index;
-}
-
-// The upper portion of the `nibble` argument is ignored.
-static int16_t adpcm_decoder_step(ADPCMDecoder* d, uint8_t nibble) {
-
- // Get step and prepare index for next sample
- if (d->step_index < 0) {
- d->step_index = 0;
- } else if (d->step_index > 88) {
- d->step_index = 88;
- }
- d->step = ima_step_table[d->step_index];
- d->step_index += ima_index_table[nibble & 0xF];
-
- // Calculate diff
- int32_t diff = d->step >> 3;
- if (nibble & 1) {
- diff += d->step >> 2;
- }
- if (nibble & 2) {
- diff += d->step >> 1;
- }
- if (nibble & 4) {
- diff += d->step;
- }
- if (nibble & 8) {
- diff = -diff;
- }
-
- // Update predictor and clamp to signed 16 bit
- d->predictor += diff;
- if (d->predictor < -0x8000) {
- d->predictor = -0x8000;
- } else if (d->predictor > 0x7FFF) {
- d->predictor = 0x7FFF;
- }
-
- return d->predictor;
-}
diff --git a/hw/xbox/adpcm_block.h b/hw/xbox/adpcm_block.h
deleted file mode 100644
index 5c7516a509..0000000000
--- a/hw/xbox/adpcm_block.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * ADPCM decoder
- *
- * Copyright (c) 2017 Jannik Vogel
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see .
- */
-
-#include "adpcm.h"
-
-static int16_t adpcm_decode_block_setup(ADPCMDecoder* decoder, uint32_t word) {
- int16_t predictor = word & 0xFFFF;
- uint8_t step_index = (word >> 16) & 0xFF;
- adpcm_decoder_initialize(decoder, predictor, step_index);
- return predictor;
-}
-
-static int16_t* adpcm_decode_word(ADPCMDecoder* decoder, int16_t* samples, uint32_t word, int first, int last) {
- for(int i = 0; i < 8; i++) {
- if (i >= first) {
- samples++;
- }
- if (i <= last) {
- *samples = adpcm_decoder_step(decoder, word);
- word >>= 4;
- }
- }
- return samples;
-}
-
-// For stereo we decode 2x 32 bit each iteration (as 32 bits).
-static void adpcm_decode_stereo_block(int16_t* samples_l, int16_t* samples_r, const uint8_t* data, unsigned int first, unsigned int last) {
- uint32_t* word = (uint32_t*)data;
- ADPCMDecoder decoder_l;
- ADPCMDecoder decoder_r;
- *samples_l = adpcm_decode_block_setup(&decoder_l, *word++);
- *samples_r = adpcm_decode_block_setup(&decoder_r, *word++);
- for(unsigned int i = 0; i < 8; i++) {
- for(unsigned int j = 0; j < 2; j++) {
- if (j == 0) {
- samples_l = adpcm_decode_word(&decoder_l, samples_l, *word++, first, last);
- } else {
- samples_r = adpcm_decode_word(&decoder_r, samples_r, *word++, first, last);
- }
- }
- first -= 8;
- last -= 8;
- }
-}
-
-// For mono we decode 32 bit at once in each iteration.
-// We could do 64 bits here, but if we parallelize this algorithm (later) we
-// would limit ourselves to 64 bit operands. However, most of ADPCM is 16 to
-// 32 bits (for overflows). So we stick with 32 bit and should even consider
-// going back to 16 bit (if enough decoders run at once)!
-static void adpcm_decode_mono_block(int16_t* samples, const uint8_t* data, unsigned int first, unsigned int last) {
- uint32_t* word = (uint32_t*)data;
- ADPCMDecoder decoder;
- *samples = adpcm_decode_block_setup(&decoder, *word++);
- for(unsigned int i = 0; i < 8; i++) {
- samples = adpcm_decode_word(&decoder, samples, *word++, first, last);
- first -= 8;
- last -= 8;
- }
-}
diff --git a/hw/xbox/mcpx/Makefile.objs b/hw/xbox/mcpx/Makefile.objs
new file mode 100644
index 0000000000..f57042de5c
--- /dev/null
+++ b/hw/xbox/mcpx/Makefile.objs
@@ -0,0 +1,4 @@
+obj-y += apu.o aci.o
+apu.o-cflags := $(SDL_CFLAGS)
+
+obj-y += dsp/
diff --git a/hw/xbox/mcpx_aci.c b/hw/xbox/mcpx/aci.c
similarity index 98%
rename from hw/xbox/mcpx_aci.c
rename to hw/xbox/mcpx/aci.c
index f0142d59f3..7f94273a5f 100644
--- a/hw/xbox/mcpx_aci.c
+++ b/hw/xbox/mcpx/aci.c
@@ -2,7 +2,7 @@
* QEMU MCPX Audio Codec Interface implementation
*
* Copyright (c) 2012 espes
- * Copyright (c) 2020 Matt Borgerson
+ * Copyright (c) 2020-2021 Matt Borgerson
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
diff --git a/hw/xbox/mcpx/adpcm.h b/hw/xbox/mcpx/adpcm.h
new file mode 100644
index 0000000000..feaa12d10b
--- /dev/null
+++ b/hw/xbox/mcpx/adpcm.h
@@ -0,0 +1,143 @@
+/*
+ * ADPCM decoder from the ADPCM-XQ project: https://github.com/dbry/adpcm-xq
+ *
+ * Copyright (c) David Bryant
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Conifer Software nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ADPCM_DECODE_H
+#define ADPCM_DECODE_H
+
+/********************************* 4-bit ADPCM decoder ********************************/
+
+/* Decode the block of ADPCM data into PCM. This requires no context because ADPCM blocks
+ * are indeppendently decodable. This assumes that a single entire block is always decoded;
+ * it must be called multiple times for multiple blocks and cannot resume in the middle of a
+ * block.
+ *
+ * Parameters:
+ * outbuf destination for interleaved PCM samples
+ * inbuf source ADPCM block
+ * inbufsize size of source ADPCM block
+ * channels number of channels in block (must be determined from other context)
+ *
+ * Returns number of converted composite samples (total samples divided by number of channels)
+ */
+
+static int adpcm_decode_block (int16_t *outbuf, const uint8_t *inbuf, size_t inbufsize, int channels)
+{
+ #define CLIP(data, min, max) \
+ if ((data) > (max)) data = max; \
+ else if ((data) < (min)) data = min;
+
+ /* step table */
+ static const uint16_t step_table[89] = {
+ 7, 8, 9, 10, 11, 12, 13, 14,
+ 16, 17, 19, 21, 23, 25, 28, 31,
+ 34, 37, 41, 45, 50, 55, 60, 66,
+ 73, 80, 88, 97, 107, 118, 130, 143,
+ 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658,
+ 724, 796, 876, 963, 1060, 1166, 1282, 1411,
+ 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
+ 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
+ 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+ 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
+ 32767
+ };
+
+ /* step index tables */
+ static const int index_table[] = {
+ /* adpcm data size is 4 */
+ -1, -1, -1, -1, 2, 4, 6, 8
+ };
+
+ int ch, samples = 1, chunks;
+ int32_t pcmdata[2];
+ int8_t index[2];
+
+ if (inbufsize < (uint32_t) channels * 4)
+ return 0;
+
+ for (ch = 0; ch < channels; ch++) {
+ *outbuf++ = pcmdata[ch] = (int16_t) (inbuf [0] | (inbuf [1] << 8));
+ index[ch] = inbuf [2];
+
+ if (index [ch] < 0 || index [ch] > 88 || inbuf [3]) // sanitize the input a little...
+ return 0;
+
+ inbufsize -= 4;
+ inbuf += 4;
+ }
+
+ chunks = inbufsize / (channels * 4);
+ samples += chunks * 8;
+
+ while (chunks--) {
+ int ch, i;
+
+ for (ch = 0; ch < channels; ++ch) {
+
+ for (i = 0; i < 4; ++i) {
+ int step = step_table [index [ch]], delta = step >> 3;
+
+ if (*inbuf & 1) delta += (step >> 2);
+ if (*inbuf & 2) delta += (step >> 1);
+ if (*inbuf & 4) delta += step;
+ if (*inbuf & 8) delta = -delta;
+
+ pcmdata[ch] += delta;
+ index[ch] += index_table [*inbuf & 0x7];
+ CLIP(index[ch], 0, 88);
+ CLIP(pcmdata[ch], -32768, 32767);
+ outbuf [i * 2 * channels] = pcmdata[ch];
+
+ step = step_table [index [ch]], delta = step >> 3;
+
+ if (*inbuf & 0x10) delta += (step >> 2);
+ if (*inbuf & 0x20) delta += (step >> 1);
+ if (*inbuf & 0x40) delta += step;
+ if (*inbuf & 0x80) delta = -delta;
+
+ pcmdata[ch] += delta;
+ index[ch] += index_table [(*inbuf >> 4) & 0x7];
+ CLIP(index[ch], 0, 88);
+ CLIP(pcmdata[ch], -32768, 32767);
+ outbuf [(i * 2 + 1) * channels] = pcmdata[ch];
+
+ inbuf++;
+ }
+
+ outbuf++;
+ }
+
+ outbuf += channels * 7;
+ }
+
+ return samples;
+}
+
+#endif
diff --git a/hw/xbox/mcpx/apu.c b/hw/xbox/mcpx/apu.c
new file mode 100644
index 0000000000..f183350732
--- /dev/null
+++ b/hw/xbox/mcpx/apu.c
@@ -0,0 +1,2638 @@
+/*
+ * QEMU MCPX Audio Processing Unit implementation
+ *
+ * Copyright (c) 2012 espes
+ * Copyright (c) 2018-2019 Jannik Vogel
+ * Copyright (c) 2019-2021 Matt Borgerson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+
+#include "qemu/osdep.h"
+#include
+#include
+#include
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "cpu.h"
+#include "migration/vmstate.h"
+#include "sysemu/runstate.h"
+#include "audio/audio.h"
+#include "qemu/fifo8.h"
+#include "ui/xemu-settings.h"
+
+#include "dsp/dsp.h"
+#include "dsp/dsp_dma.h"
+#include "dsp/dsp_cpu.h"
+#include "dsp/dsp_state.h"
+#include "apu.h"
+#include "apu_regs.h"
+#include "apu_debug.h"
+#include "adpcm.h"
+#include "svf.h"
+#include "fpconv.h"
+
+#define GET_MASK(v, mask) (((v) & (mask)) >> ctz32(mask))
+
+#define SET_MASK(v, mask, val) \
+ do { \
+ (v) &= ~(mask); \
+ (v) |= ((val) << ctz32(mask)) & (mask); \
+ } while (0)
+
+#define CASE_4(v, step) \
+ case (v): \
+ case (v)+(step): \
+ case (v)+(step)*2: \
+ case (v)+(step)*3
+
+// #define DEBUG_MCPX
+
+#ifdef DEBUG_MCPX
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+ do { } while (0)
+#endif
+
+#define MCPX_APU_DEVICE(obj) \
+ OBJECT_CHECK(MCPXAPUState, (obj), "mcpx-apu")
+
+typedef struct MCPXAPUVPSSLData {
+ uint32_t base[MCPX_HW_SSLS_PER_VOICE];
+ uint8_t count[MCPX_HW_SSLS_PER_VOICE];
+ int ssl_index;
+ int ssl_seg;
+} MCPXAPUVPSSLData;
+
+typedef struct MCPXAPUVoiceFilter {
+ uint16_t voice;
+ float resample_buf[NUM_SAMPLES_PER_FRAME * 2];
+ SRC_STATE *resampler;
+ sv_filter svf[2];
+} MCPXAPUVoiceFilter;
+
+typedef struct MCPXAPUState {
+ PCIDevice dev;
+ bool exiting;
+ bool set_irq;
+
+ QemuThread apu_thread;
+ QemuMutex lock;
+ QemuCond cond;
+
+ MemoryRegion *ram;
+ uint8_t *ram_ptr;
+ MemoryRegion mmio;
+
+ /* Setup Engine */
+ struct {
+ } se;
+
+ /* Voice Processor */
+ struct {
+ MemoryRegion mmio;
+ MCPXAPUVoiceFilter filters[MCPX_HW_MAX_VOICES];
+ QemuSpin out_buf_lock;
+ Fifo8 out_buf;
+
+ // FIXME: Where are these stored?
+ int ssl_base_page;
+ MCPXAPUVPSSLData ssl[MCPX_HW_MAX_VOICES];
+ uint8_t hrtf_headroom;
+ uint8_t hrtf_submix[4];
+ uint8_t submix_headroom[NUM_MIXBINS];
+ float sample_buf[NUM_SAMPLES_PER_FRAME][2];
+ uint64_t voice_locked[4];
+ QemuSpin voice_spinlocks[MCPX_HW_MAX_VOICES];
+ } vp;
+
+ /* Global Processor */
+ struct {
+ bool realtime;
+ MemoryRegion mmio;
+ DSPState *dsp;
+ uint32_t regs[0x10000];
+ } gp;
+
+ /* Encode Processor */
+ struct {
+ bool realtime;
+ MemoryRegion mmio;
+ DSPState *dsp;
+ uint32_t regs[0x10000];
+ } ep;
+
+ uint32_t regs[0x20000];
+
+ uint32_t inbuf_sge_handle; //FIXME: Where is this stored?
+ uint32_t outbuf_sge_handle; //FIXME: Where is this stored?
+
+ int mon;
+ int ep_frame_div;
+ int sleep_acc;
+ int frame_count;
+ int64_t frame_count_time;
+ int16_t apu_fifo_output[256][2]; // 1 EP frame (0x400 bytes), 8 buffered
+} MCPXAPUState;
+
+static MCPXAPUState *g_state; // Used via debug handlers
+static struct McpxApuDebug g_dbg, g_dbg_cache;
+static int g_dbg_voice_monitor = -1;
+static uint64_t g_dbg_muted_voices[4];
+static const int16_t ep_silence[256][2] = { 0 };
+
+static float clampf(float v, float min, float max);
+static float attenuate(uint16_t vol);
+
+static void mcpx_debug_begin_frame(void);
+static void mcpx_debug_end_frame(void);
+static bool voice_should_mute(uint16_t v);
+static uint32_t voice_get_mask(MCPXAPUState *d, uint16_t voice_handle,
+ hwaddr offset, uint32_t mask);
+static void voice_set_mask(MCPXAPUState *d, uint16_t voice_handle,
+ hwaddr offset, uint32_t mask, uint32_t val);
+static uint64_t mcpx_apu_read(void *opaque, hwaddr addr, unsigned int size);
+static void mcpx_apu_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size);
+static void voice_off(MCPXAPUState *d, uint16_t v);
+static void voice_lock(MCPXAPUState *d, uint16_t v, bool lock);
+static bool is_voice_locked(MCPXAPUState *d, uint16_t v);
+static void fe_method(MCPXAPUState *d, uint32_t method, uint32_t argument);
+static uint64_t vp_read(void *opaque, hwaddr addr, unsigned int size);
+static void vp_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size);
+static void scatter_gather_rw(MCPXAPUState *d, hwaddr sge_base,
+ unsigned int max_sge, uint8_t *ptr, uint32_t addr,
+ size_t len, bool dir);
+static void gp_scratch_rw(void *opaque, uint8_t *ptr, uint32_t addr, size_t len,
+ bool dir);
+static void ep_scratch_rw(void *opaque, uint8_t *ptr, uint32_t addr, size_t len,
+ bool dir);
+static uint32_t circular_scatter_gather_rw(MCPXAPUState *d, hwaddr sge_base,
+ unsigned int max_sge, uint8_t *ptr,
+ uint32_t base, uint32_t end,
+ uint32_t cur, size_t len, bool dir);
+static void gp_fifo_rw(void *opaque, uint8_t *ptr, unsigned int index,
+ size_t len, bool dir);
+static bool ep_sink_samples(MCPXAPUState *d, uint8_t *ptr, size_t len);
+static void ep_fifo_rw(void *opaque, uint8_t *ptr, unsigned int index,
+ size_t len, bool dir);
+static void proc_rst_write(DSPState *dsp, uint32_t oldval, uint32_t val);
+static uint64_t gp_read(void *opaque, hwaddr addr, unsigned int size);
+static void gp_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size);
+static uint64_t ep_read(void *opaque, hwaddr addr, unsigned int size);
+static void ep_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size);
+static float voice_step_envelope(MCPXAPUState *d, uint16_t v,
+ uint32_t reg_0, uint32_t reg_a,
+ uint32_t rr_reg, uint32_t rr_mask,
+ uint32_t lvl_reg, uint32_t lvl_mask,
+ uint32_t count_mask, uint32_t cur_mask);
+static hwaddr get_data_ptr(hwaddr sge_base, unsigned int max_sge,
+ uint32_t addr);
+static void set_notify_status(MCPXAPUState *d, uint32_t v, int notifier,
+ int status);
+static long voice_resample_callback(void *cb_data, float **data);
+static int voice_resample(MCPXAPUState *d, uint16_t v, float samples[][2],
+ int requested_num, float rate);
+static void voice_reset_filters(MCPXAPUState *d, uint16_t v);
+static void voice_process(MCPXAPUState *d,
+ float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME],
+ uint16_t v);
+static int voice_get_samples(MCPXAPUState *d, uint32_t v, float samples[][2],
+ int num_samples_requested);
+static void se_frame(MCPXAPUState *d);
+static void update_irq(MCPXAPUState *d);
+static void sleep_ns(int64_t ns);
+static void mcpx_vp_out_cb(void *opaque, uint8_t *stream, int free_b);
+static void mcpx_apu_realize(PCIDevice *dev, Error **errp);
+static void mcpx_apu_exitfn(PCIDevice *dev);
+static void mcpx_apu_reset(MCPXAPUState *d);
+static void mcpx_apu_vm_state_change(void *opaque, int running, RunState state);
+static int mcpx_apu_post_save(void *opaque);
+static int mcpx_apu_pre_load(void *opaque);
+static int mcpx_apu_post_load(void *opaque, int version_id);
+static void qdev_mcpx_apu_reset(DeviceState *dev);
+static void mcpx_apu_register(void);
+static void *mcpx_apu_frame_thread(void *arg);
+
+
+const struct McpxApuDebug *mcpx_apu_get_debug_info(void)
+{
+ return &g_dbg_cache;
+}
+
+static void mcpx_debug_begin_frame(void)
+{
+ for (int i = 0; i < MCPX_HW_MAX_VOICES; i++) {
+ g_dbg.vp.v[i].active = false;
+ }
+}
+
+static void mcpx_debug_end_frame(void)
+{
+ g_dbg_cache = g_dbg;
+}
+
+void mcpx_apu_debug_set_gp_realtime_enabled(bool run)
+{
+ g_state->gp.realtime = run;
+}
+
+void mcpx_apu_debug_set_ep_realtime_enabled(bool run)
+{
+ g_state->ep.realtime = run;
+}
+
+int mcpx_apu_debug_get_monitor(void)
+{
+ return g_state->mon;
+}
+
+void mcpx_apu_debug_set_monitor(int new_mon)
+{
+ g_state->mon = new_mon;
+}
+
+void mcpx_apu_debug_isolate_voice(uint16_t v)
+{
+ g_dbg_voice_monitor = v;
+}
+
+void mcpx_apu_debug_clear_isolations(void)
+{
+ g_dbg_voice_monitor = -1;
+}
+
+static bool voice_should_mute(uint16_t v)
+{
+ bool m = (g_dbg_voice_monitor >= 0) && (v != g_dbg_voice_monitor);
+ return m || mcpx_apu_debug_is_muted(v);
+}
+
+bool mcpx_apu_debug_is_muted(uint16_t v)
+{
+ assert(v < MCPX_HW_MAX_VOICES);
+ return g_dbg_muted_voices[v / 64] & (1LL << (v % 64));
+}
+
+void mcpx_apu_debug_toggle_mute(uint16_t v)
+{
+ assert(v < MCPX_HW_MAX_VOICES);
+ g_dbg_muted_voices[v / 64] ^= (1LL << (v % 64));
+}
+
+static float clampf(float v, float min, float max)
+{
+ if (v < min) {
+ return min;
+ } else if (v > max) {
+ return max;
+ } else {
+ return v;
+ }
+}
+
+static float attenuate(uint16_t vol)
+{
+ vol &= 0xFFF;
+ return (vol == 0xFFF) ? 0.0 : powf(10.0f, vol/(64.0 * -20.0f));
+}
+
+static uint32_t voice_get_mask(MCPXAPUState *d, uint16_t voice_handle,
+ hwaddr offset, uint32_t mask)
+{
+ hwaddr voice = d->regs[NV_PAPU_VPVADDR] + voice_handle * NV_PAVS_SIZE;
+ return (ldl_le_phys(&address_space_memory, voice + offset) & mask) >>
+ ctz32(mask);
+}
+
+static void voice_set_mask(MCPXAPUState *d, uint16_t voice_handle,
+ hwaddr offset, uint32_t mask, uint32_t val)
+{
+ hwaddr voice = d->regs[NV_PAPU_VPVADDR]
+ + voice_handle * NV_PAVS_SIZE;
+ uint32_t v = ldl_le_phys(&address_space_memory, voice + offset) & ~mask;
+ stl_le_phys(&address_space_memory, voice + offset,
+ v | ((val << ctz32(mask)) & mask));
+}
+
+static void update_irq(MCPXAPUState *d)
+{
+ if (d->regs[NV_PAPU_FECTL] & NV_PAPU_FECTL_FEMETHMODE_TRAPPED) {
+ atomic_or(&d->regs[NV_PAPU_ISTS], NV_PAPU_ISTS_FETINTSTS);
+ }
+ if ((d->regs[NV_PAPU_IEN] & NV_PAPU_ISTS_GINTSTS) &&
+ ((d->regs[NV_PAPU_ISTS] & ~NV_PAPU_ISTS_GINTSTS) &
+ d->regs[NV_PAPU_IEN])) {
+ atomic_or(&d->regs[NV_PAPU_ISTS], NV_PAPU_ISTS_GINTSTS);
+ // fprintf(stderr, "mcpx irq raise ien=%08x ists=%08x\n",
+ // d->regs[NV_PAPU_IEN], d->regs[NV_PAPU_ISTS]);
+ pci_irq_assert(&d->dev);
+ } else {
+ atomic_and(&d->regs[NV_PAPU_ISTS], ~NV_PAPU_ISTS_GINTSTS);
+ // fprintf(stderr, "mcpx irq lower ien=%08x ists=%08x\n",
+ // d->regs[NV_PAPU_IEN], d->regs[NV_PAPU_ISTS]);
+ pci_irq_deassert(&d->dev);
+ }
+}
+
+static uint64_t mcpx_apu_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ MCPXAPUState *d = opaque;
+
+ uint64_t r = 0;
+ switch (addr) {
+ case NV_PAPU_XGSCNT:
+ r = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 100; //???
+ break;
+ default:
+ if (addr < 0x20000) {
+ r = atomic_read(&d->regs[addr]);
+ }
+ break;
+ }
+
+ DPRINTF("mcpx apu: read [0x%" HWADDR_PRIx "] (%s) -> 0x%lx\n", addr,
+ get_reg_str(addr), r);
+
+ return r;
+}
+
+static void mcpx_apu_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size)
+{
+ MCPXAPUState *d = opaque;
+
+ DPRINTF("mcpx apu: [0x%" HWADDR_PRIx "] (%s) = 0x%lx\n", addr,
+ get_reg_str(addr), val);
+
+ switch (addr) {
+ case NV_PAPU_ISTS:
+ /* the bits of the interrupts to clear are written */
+ atomic_and(&d->regs[NV_PAPU_ISTS], ~val);
+ update_irq(d);
+ qemu_cond_broadcast(&d->cond);
+ break;
+ case NV_PAPU_FECTL:
+ case NV_PAPU_SECTL:
+ atomic_set(&d->regs[addr], val);
+ qemu_cond_broadcast(&d->cond);
+ break;
+ case NV_PAPU_FEMEMDATA:
+ /* 'magic write'
+ * This value is expected to be written to FEMEMADDR on completion of
+ * something to do with notifies. Just do it now :/ */
+ stl_le_phys(&address_space_memory, d->regs[NV_PAPU_FEMEMADDR], val);
+ // fprintf(stderr, "MAGIC WRITE\n");
+ atomic_set(&d->regs[addr], val);
+ break;
+ default:
+ if (addr < 0x20000) {
+ atomic_set(&d->regs[addr], val);
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps mcpx_apu_mmio_ops = {
+ .read = mcpx_apu_read,
+ .write = mcpx_apu_write,
+};
+
+static void voice_off(MCPXAPUState *d, uint16_t v)
+{
+ voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE, 0);
+
+ bool stream = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
+ NV_PAVS_VOICE_CFG_FMT_DATA_TYPE);
+ int notifier = MCPX_HW_NOTIFIER_SSLA_DONE;
+ if (stream) {
+ assert(v < MCPX_HW_MAX_VOICES);
+ assert(d->vp.ssl[v].ssl_index <= 1);
+ notifier += d->vp.ssl[v].ssl_index;
+ }
+ set_notify_status(d, v, notifier, NV1BA0_NOTIFICATION_STATUS_DONE_SUCCESS);
+}
+
+static void voice_lock(MCPXAPUState *d, uint16_t v, bool lock)
+{
+ assert(v < MCPX_HW_MAX_VOICES);
+ qemu_spin_lock(&d->vp.voice_spinlocks[v]);
+ uint64_t mask = 1LL << (v % 64);
+ if (lock) {
+ d->vp.voice_locked[v / 64] |= mask;
+ } else {
+ d->vp.voice_locked[v / 64] &= ~mask;
+ }
+ qemu_spin_unlock(&d->vp.voice_spinlocks[v]);
+ qemu_cond_broadcast(&d->cond);
+}
+
+static bool is_voice_locked(MCPXAPUState *d, uint16_t v)
+{
+ assert(v < MCPX_HW_MAX_VOICES);
+ uint64_t mask = 1LL << (v % 64);
+ return (atomic_read(&d->vp.voice_locked[v / 64]) & mask) != 0;
+}
+
+static void fe_method(MCPXAPUState *d, uint32_t method, uint32_t argument)
+{
+ unsigned int slot;
+
+ DPRINTF("mcpx fe_method 0x%x 0x%x\n", method, argument);
+
+ //assert((d->regs[NV_PAPU_FECTL] & NV_PAPU_FECTL_FEMETHMODE) == 0);
+
+ d->regs[NV_PAPU_FEDECMETH] = method;
+ d->regs[NV_PAPU_FEDECPARAM] = argument;
+ unsigned int selected_handle, list;
+ switch (method) {
+ case NV1BA0_PIO_VOICE_LOCK:
+ voice_lock(d, d->regs[NV_PAPU_FECV], argument & 1);
+ break;
+ case NV1BA0_PIO_SET_ANTECEDENT_VOICE:
+ d->regs[NV_PAPU_FEAV] = argument;
+ break;
+ case NV1BA0_PIO_VOICE_ON:
+ selected_handle = argument & NV1BA0_PIO_VOICE_ON_HANDLE;
+ DPRINTF("VOICE %d ON\n", selected_handle);
+
+ bool locked = is_voice_locked(d, selected_handle);
+ if (!locked) {
+ voice_lock(d, selected_handle, true);
+ }
+
+ list = GET_MASK(d->regs[NV_PAPU_FEAV], NV_PAPU_FEAV_LST);
+ if (list != NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_INHERIT) {
+ /* voice is added to the top of the selected list */
+ unsigned int top_reg = voice_list_regs[list - 1].top;
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_TAR_PITCH_LINK,
+ NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE,
+ d->regs[top_reg]);
+ d->regs[top_reg] = selected_handle;
+ } else {
+ unsigned int antecedent_voice =
+ GET_MASK(d->regs[NV_PAPU_FEAV], NV_PAPU_FEAV_VALUE);
+ /* voice is added after the antecedent voice */
+ assert(antecedent_voice != 0xFFFF);
+
+ uint32_t next_handle = voice_get_mask(
+ d, antecedent_voice, NV_PAVS_VOICE_TAR_PITCH_LINK,
+ NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE);
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_TAR_PITCH_LINK,
+ NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE,
+ next_handle);
+ voice_set_mask(d, antecedent_voice, NV_PAVS_VOICE_TAR_PITCH_LINK,
+ NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE,
+ selected_handle);
+ }
+
+ // FIXME: Should set CBO here?
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_OFFSET,
+ NV_PAVS_VOICE_PAR_OFFSET_CBO, 0);
+ d->vp.ssl[selected_handle].ssl_seg = 0; // FIXME: verify this
+ d->vp.ssl[selected_handle].ssl_index = 0; // FIXME: verify this
+
+ unsigned int ea_start = GET_MASK(argument, NV1BA0_PIO_VOICE_ON_ENVA);
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_EACUR, ea_start);
+ if (ea_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_DELAY) {
+ uint16_t delay_time =
+ voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENV0,
+ NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME);
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT,
+ NV_PAVS_VOICE_CUR_ECNT_EACOUNT, delay_time * 16);
+ } else if (ea_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_ATTACK) {
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT,
+ NV_PAVS_VOICE_CUR_ECNT_EACOUNT, 0);
+ } else if (ea_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_HOLD) {
+ uint16_t hold_time =
+ voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENVA,
+ NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME);
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT,
+ NV_PAVS_VOICE_CUR_ECNT_EACOUNT, hold_time * 16);
+ }
+ // FIXME: Will count be overwritten in other cases too?
+
+ unsigned int ef_start = GET_MASK(argument, NV1BA0_PIO_VOICE_ON_ENVF);
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_EFCUR, ef_start);
+ if (ef_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_DELAY) {
+ uint16_t delay_time =
+ voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENV1,
+ NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME);
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT,
+ NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, delay_time * 16);
+ } else if (ef_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_ATTACK) {
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT,
+ NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, 0);
+ } else if (ef_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_HOLD) {
+ uint16_t hold_time =
+ voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENVF,
+ NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME);
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT,
+ NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, hold_time * 16);
+ }
+ // FIXME: Will count be overwritten in other cases too?
+
+ voice_reset_filters(d, selected_handle);
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE, 1);
+
+ if (!locked) {
+ voice_lock(d, selected_handle, false);
+ }
+
+ break;
+ case NV1BA0_PIO_VOICE_RELEASE: {
+ selected_handle = argument & NV1BA0_PIO_VOICE_ON_HANDLE;
+
+ // FIXME: What if already in release? Restart envelope?
+ // FIXME: Should release count ascend or descend?
+
+ bool locked = is_voice_locked(d, selected_handle);
+ if (!locked) {
+ voice_lock(d, selected_handle, true);
+ }
+
+ uint16_t rr;
+ rr = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_TAR_LFO_ENV,
+ NV_PAVS_VOICE_TAR_LFO_ENV_EA_RELEASERATE);
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT,
+ NV_PAVS_VOICE_CUR_ECNT_EACOUNT, rr * 16);
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_EACUR,
+ NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE);
+
+ rr = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_MISC,
+ NV_PAVS_VOICE_CFG_MISC_EF_RELEASERATE);
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT,
+ NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, rr * 16);
+ voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_EFCUR,
+ NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE);
+
+ if (!locked) {
+ voice_lock(d, selected_handle, false);
+ }
+
+ break;
+ }
+ case NV1BA0_PIO_VOICE_OFF:
+ voice_off(d, argument & NV1BA0_PIO_VOICE_OFF_HANDLE);
+ break;
+ case NV1BA0_PIO_VOICE_PAUSE:
+ voice_set_mask(d, argument & NV1BA0_PIO_VOICE_PAUSE_HANDLE,
+ NV_PAVS_VOICE_PAR_STATE, NV_PAVS_VOICE_PAR_STATE_PAUSED,
+ (argument & NV1BA0_PIO_VOICE_PAUSE_ACTION) != 0);
+ break;
+ case NV1BA0_PIO_SET_CURRENT_VOICE:
+ d->regs[NV_PAPU_FECV] = argument;
+ break;
+ case NV1BA0_PIO_SET_VOICE_CFG_VBIN:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_VBIN,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_CFG_FMT:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_FMT,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_CFG_ENV0:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_ENV0,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_CFG_ENVA:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_ENVA,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_CFG_ENV1:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_ENV1,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_CFG_ENVF:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_ENVF,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_CFG_MISC:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_MISC,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_TAR_VOLA:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_VOLA,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_TAR_VOLB:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_VOLB,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_TAR_VOLC:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_VOLC,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_LFO_ENV:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_LFO_ENV,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_TAR_FCA:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_FCA,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_TAR_FCB:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_FCB,
+ 0xFFFFFFFF, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_TAR_PITCH:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_PITCH_LINK,
+ NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH,
+ (argument & NV1BA0_PIO_SET_VOICE_TAR_PITCH_STEP) >> 16);
+ break;
+ case NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CUR_PSL_START,
+ NV_PAVS_VOICE_CUR_PSL_START_BA, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CUR_PSH_SAMPLE,
+ NV_PAVS_VOICE_CUR_PSH_SAMPLE_LBO, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_BUF_CBO:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_PAR_OFFSET,
+ NV_PAVS_VOICE_PAR_OFFSET_CBO, argument);
+ break;
+ case NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO:
+ voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_PAR_NEXT,
+ NV_PAVS_VOICE_PAR_NEXT_EBO, argument);
+ break;
+ case NV1BA0_PIO_SET_CURRENT_INBUF_SGE:
+ d->inbuf_sge_handle = argument & NV1BA0_PIO_SET_CURRENT_INBUF_SGE_HANDLE;
+ break;
+ case NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET: {
+ // FIXME: Is there an upper limit for the SGE table size?
+ // FIXME: NV_PAPU_VPSGEADDR is probably bad, as outbuf SGE use the same
+ // handle range (or that is also wrong)
+ hwaddr sge_address =
+ d->regs[NV_PAPU_VPSGEADDR] + d->inbuf_sge_handle * 8;
+ stl_le_phys(&address_space_memory, sge_address,
+ argument &
+ NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER);
+ DPRINTF("Wrote inbuf SGE[0x%X] = 0x%08X\n", d->inbuf_sge_handle,
+ argument & NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER);
+ break;
+ }
+ CASE_4(NV1BA0_PIO_SET_OUTBUF_BA, 8): // 8 byte pitch, 4 entries
+#ifdef DEBUG_MCPX
+ slot = (method - NV1BA0_PIO_SET_OUTBUF_BA) / 8;
+ //FIXME: Use NV1BA0_PIO_SET_OUTBUF_BA_ADDRESS = 0x007FFF00 ?
+ DPRINTF("outbuf_ba[%d]: 0x%08X\n", slot, argument);
+#endif
+ //assert(false); //FIXME: Enable assert! no idea what this reg does
+ break;
+ CASE_4(NV1BA0_PIO_SET_OUTBUF_LEN, 8): // 8 byte pitch, 4 entries
+#ifdef DEBUG_MCPX
+ slot = (method - NV1BA0_PIO_SET_OUTBUF_LEN) / 8;
+ //FIXME: Use NV1BA0_PIO_SET_OUTBUF_LEN_VALUE = 0x007FFF00 ?
+ DPRINTF("outbuf_len[%d]: 0x%08X\n", slot, argument);
+#endif
+ //assert(false); //FIXME: Enable assert! no idea what this reg does
+ break;
+ case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE:
+ d->outbuf_sge_handle =
+ argument & NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_HANDLE;
+ break;
+ case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET: {
+ // FIXME: Is there an upper limit for the SGE table size?
+ // FIXME: NV_PAPU_VPSGEADDR is probably bad, as inbuf SGE use the same
+ // handle range (or that is also wrong)
+ // NV_PAPU_EPFADDR EP outbufs
+ // NV_PAPU_GPFADDR GP outbufs
+ // But how does it know which outbuf is being written?!
+ hwaddr sge_address =
+ d->regs[NV_PAPU_VPSGEADDR] + d->outbuf_sge_handle * 8;
+ stl_le_phys(&address_space_memory, sge_address,
+ argument &
+ NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER);
+ DPRINTF("Wrote outbuf SGE[0x%X] = 0x%08X\n", d->outbuf_sge_handle,
+ argument & NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER);
+ break;
+ }
+ case NV1BA0_PIO_SET_VOICE_SSL_A: {
+ int ssl = 0;
+ int current_voice = d->regs[NV_PAPU_FECV];
+ assert(current_voice < MCPX_HW_MAX_VOICES);
+ d->vp.ssl[current_voice].base[ssl] =
+ GET_MASK(argument, NV1BA0_PIO_SET_VOICE_SSL_A_BASE);
+ d->vp.ssl[current_voice].count[ssl] =
+ GET_MASK(argument, NV1BA0_PIO_SET_VOICE_SSL_A_COUNT);
+ // d->vp.ssl[current_voice].ssl_index = 0;
+ DPRINTF("SSL%c Base = %x, Count = %d\n", 'A' + ssl,
+ d->vp.ssl[current_voice].base[ssl],
+ d->vp.ssl[current_voice].count[ssl]);
+ break;
+ }
+ // FIXME: Refactor into above
+ case NV1BA0_PIO_SET_VOICE_SSL_B: {
+ int ssl = 1;
+ int current_voice = d->regs[NV_PAPU_FECV];
+ assert(current_voice < MCPX_HW_MAX_VOICES);
+ d->vp.ssl[current_voice].base[ssl] =
+ GET_MASK(argument, NV1BA0_PIO_SET_VOICE_SSL_A_BASE);
+ d->vp.ssl[current_voice].count[ssl] =
+ GET_MASK(argument, NV1BA0_PIO_SET_VOICE_SSL_A_COUNT);
+ // d->vp.ssl[current_voice].ssl_index = 0;
+ DPRINTF("SSL%c Base = %x, Count = %d\n", 'A' + ssl,
+ d->vp.ssl[current_voice].base[ssl],
+ d->vp.ssl[current_voice].count[ssl]);
+ break;
+ }
+ case NV1BA0_PIO_SET_CURRENT_SSL: {
+ assert((argument & 0x3f) == 0);
+ assert(argument < (MCPX_HW_MAX_SSL_PRDS*NV_PSGE_SIZE));
+ d->vp.ssl_base_page = argument;
+ break;
+ }
+ case NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET ...
+ NV1BA0_PIO_SET_SSL_SEGMENT_LENGTH+8*64-1: {
+ // 64 offset/base pairs relative to segment base
+ // FIXME: Entries are 64b, assuming they are stored
+ // like this <[offset,length],...>
+ assert((method & 0x3) == 0);
+ hwaddr addr = d->regs[NV_PAPU_VPSSLADDR]
+ + (d->vp.ssl_base_page * 8)
+ + (method - NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET);
+ stl_le_phys(&address_space_memory, addr, argument);
+ DPRINTF(" ssl_segment[%x + %x].%s = %x\n",
+ d->vp.ssl_base_page,
+ (method - NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET)/8,
+ method & 4 ? "length" : "offset",
+ argument);
+ break;
+ }
+ case NV1BA0_PIO_SET_HRTF_SUBMIXES:
+ d->vp.hrtf_submix[0] = (argument >> 0) & 0x1f;
+ d->vp.hrtf_submix[1] = (argument >> 8) & 0x1f;
+ d->vp.hrtf_submix[2] = (argument >> 16) & 0x1f;
+ d->vp.hrtf_submix[3] = (argument >> 24) & 0x1f;
+ break;
+ case NV1BA0_PIO_SET_HRTF_HEADROOM:
+ d->vp.hrtf_headroom = argument & NV1BA0_PIO_SET_HRTF_HEADROOM_AMOUNT;
+ break;
+ case NV1BA0_PIO_SET_SUBMIX_HEADROOM ...
+ NV1BA0_PIO_SET_SUBMIX_HEADROOM+4*(NUM_MIXBINS-1):
+ assert((method & 3) == 0);
+ slot = (method-NV1BA0_PIO_SET_SUBMIX_HEADROOM)/4;
+ d->vp.submix_headroom[slot] =
+ argument & NV1BA0_PIO_SET_SUBMIX_HEADROOM_AMOUNT;
+ break;
+ case SE2FE_IDLE_VOICE:
+ if (d->regs[NV_PAPU_FETFORCE1] & NV_PAPU_FETFORCE1_SE2FE_IDLE_VOICE) {
+ d->regs[NV_PAPU_FECTL] &= ~NV_PAPU_FECTL_FEMETHMODE;
+ d->regs[NV_PAPU_FECTL] |= NV_PAPU_FECTL_FEMETHMODE_TRAPPED;
+ d->regs[NV_PAPU_FECTL] &= ~NV_PAPU_FECTL_FETRAPREASON;
+ d->regs[NV_PAPU_FECTL] |= NV_PAPU_FECTL_FETRAPREASON_REQUESTED;
+ DPRINTF("idle voice %d\n", argument);
+ d->set_irq = true;
+ } else {
+ assert(false);
+ }
+ break;
+ default:
+ assert(false);
+ break;
+ }
+}
+
+static uint64_t vp_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ DPRINTF("mcpx apu VP: read [0x%" HWADDR_PRIx "] (%s)\n", addr,
+ get_method_str(addr));
+
+ switch (addr) {
+ case NV1BA0_PIO_FREE:
+ /* we don't simulate the queue for now,
+ * pretend to always be empty */
+ return 0x80;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void vp_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size)
+{
+ MCPXAPUState *d = opaque;
+
+ DPRINTF("mcpx apu VP: [0x%" HWADDR_PRIx "] %s = 0x%lx\n", addr,
+ get_method_str(addr), val);
+
+ switch (addr) {
+ case NV1BA0_PIO_SET_ANTECEDENT_VOICE:
+ case NV1BA0_PIO_VOICE_LOCK:
+ case NV1BA0_PIO_VOICE_ON:
+ case NV1BA0_PIO_VOICE_RELEASE:
+ case NV1BA0_PIO_VOICE_OFF:
+ case NV1BA0_PIO_VOICE_PAUSE:
+ case NV1BA0_PIO_SET_CURRENT_VOICE:
+ case NV1BA0_PIO_SET_VOICE_CFG_VBIN:
+ case NV1BA0_PIO_SET_VOICE_CFG_FMT:
+ case NV1BA0_PIO_SET_VOICE_CFG_ENV0:
+ case NV1BA0_PIO_SET_VOICE_CFG_ENVA:
+ case NV1BA0_PIO_SET_VOICE_CFG_ENV1:
+ case NV1BA0_PIO_SET_VOICE_CFG_ENVF:
+ case NV1BA0_PIO_SET_VOICE_CFG_MISC:
+ case NV1BA0_PIO_SET_VOICE_TAR_VOLA:
+ case NV1BA0_PIO_SET_VOICE_TAR_VOLB:
+ case NV1BA0_PIO_SET_VOICE_TAR_VOLC:
+ case NV1BA0_PIO_SET_VOICE_LFO_ENV:
+ case NV1BA0_PIO_SET_VOICE_TAR_FCA:
+ case NV1BA0_PIO_SET_VOICE_TAR_FCB:
+ case NV1BA0_PIO_SET_VOICE_TAR_PITCH:
+ case NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE:
+ case NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO:
+ case NV1BA0_PIO_SET_VOICE_BUF_CBO:
+ case NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO:
+ case NV1BA0_PIO_SET_CURRENT_INBUF_SGE:
+ case NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET:
+ CASE_4(NV1BA0_PIO_SET_OUTBUF_BA, 8): // 8 byte pitch, 4 entries
+ CASE_4(NV1BA0_PIO_SET_OUTBUF_LEN, 8): // 8 byte pitch, 4 entries
+ case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE:
+ case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET:
+ case NV1BA0_PIO_SET_CURRENT_SSL:
+ case NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET ...
+ NV1BA0_PIO_SET_SSL_SEGMENT_LENGTH+8*64-1:
+ case NV1BA0_PIO_SET_VOICE_SSL_A:
+ case NV1BA0_PIO_SET_VOICE_SSL_B:
+ case NV1BA0_PIO_SET_HRTF_SUBMIXES:
+ case NV1BA0_PIO_SET_HRTF_HEADROOM:
+ case NV1BA0_PIO_SET_SUBMIX_HEADROOM ...
+ NV1BA0_PIO_SET_SUBMIX_HEADROOM+4*(NUM_MIXBINS-1):
+ /* TODO: these should instead be queueing up fe commands */
+ fe_method(d, addr, val);
+ break;
+
+ case NV1BA0_PIO_GET_VOICE_POSITION:
+ case NV1BA0_PIO_SET_CONTEXT_DMA_NOTIFY:
+ case NV1BA0_PIO_SET_CURRENT_SSL_CONTEXT_DMA:
+ DPRINTF("unhandled method: %" HWADDR_PRIx " = %" HWADDR_PRIx "\n", addr,
+ val);
+ assert(0);
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps vp_ops = {
+ .read = vp_read,
+ .write = vp_write,
+};
+
+static void scatter_gather_rw(MCPXAPUState *d, hwaddr sge_base,
+ unsigned int max_sge, uint8_t *ptr, uint32_t addr,
+ size_t len, bool dir)
+{
+ unsigned int page_entry = addr / TARGET_PAGE_SIZE;
+ unsigned int offset_in_page = addr % TARGET_PAGE_SIZE;
+ unsigned int bytes_to_copy = TARGET_PAGE_SIZE - offset_in_page;
+
+ while (len > 0) {
+ assert(page_entry <= max_sge);
+
+ uint32_t prd_address = ldl_le_phys(&address_space_memory,
+ sge_base + page_entry * 8 + 0);
+ // uint32_t prd_control = ldl_le_phys(&address_space_memory,
+ // sge_base + page_entry * 8 + 4);
+
+ hwaddr paddr = prd_address + offset_in_page;
+
+ if (bytes_to_copy > len) {
+ bytes_to_copy = len;
+ }
+
+ assert(paddr + bytes_to_copy < memory_region_size(d->ram));
+
+ if (dir) {
+ memcpy(&d->ram_ptr[paddr], ptr, bytes_to_copy);
+ memory_region_set_dirty(d->ram, paddr, bytes_to_copy);
+ } else {
+ memcpy(ptr, &d->ram_ptr[paddr], bytes_to_copy);
+ }
+
+ ptr += bytes_to_copy;
+ len -= bytes_to_copy;
+
+ /* After the first iteration, we are page aligned */
+ page_entry += 1;
+ bytes_to_copy = TARGET_PAGE_SIZE;
+ offset_in_page = 0;
+ }
+}
+
+static void gp_scratch_rw(void *opaque, uint8_t *ptr, uint32_t addr, size_t len,
+ bool dir)
+{
+ MCPXAPUState *d = opaque;
+ // fprintf(stderr, "GP %s scratch 0x%x bytes (0x%x words) at %x (0x%x words)\n", dir ? "writing to" : "reading from", len, len/4, addr, addr/4);
+ scatter_gather_rw(d, d->regs[NV_PAPU_GPSADDR], d->regs[NV_PAPU_GPSMAXSGE],
+ ptr, addr, len, dir);
+}
+
+static void ep_scratch_rw(void *opaque, uint8_t *ptr, uint32_t addr, size_t len,
+ bool dir)
+{
+ MCPXAPUState *d = opaque;
+ // fprintf(stderr, "EP %s scratch 0x%x bytes (0x%x words) at %x (0x%x words)\n", dir ? "writing to" : "reading from", len, len/4, addr, addr/4);
+ scatter_gather_rw(d, d->regs[NV_PAPU_EPSADDR], d->regs[NV_PAPU_EPSMAXSGE],
+ ptr, addr, len, dir);
+}
+
+static uint32_t circular_scatter_gather_rw(MCPXAPUState *d, hwaddr sge_base,
+ unsigned int max_sge, uint8_t *ptr,
+ uint32_t base, uint32_t end,
+ uint32_t cur, size_t len, bool dir)
+{
+ while (len > 0) {
+ unsigned int bytes_to_copy = end - cur;
+
+ if (bytes_to_copy > len) {
+ bytes_to_copy = len;
+ }
+
+ DPRINTF("circular scatter gather %s in range 0x%x - 0x%x at 0x%x of "
+ "length 0x%x / 0x%lx bytes\n",
+ dir ? "write" : "read", base, end, cur, bytes_to_copy, len);
+
+ assert((cur >= base) && ((cur + bytes_to_copy) <= end));
+ scatter_gather_rw(d, sge_base, max_sge, ptr, cur, bytes_to_copy, dir);
+
+ ptr += bytes_to_copy;
+ len -= bytes_to_copy;
+
+ /* After the first iteration we might have to wrap */
+ cur += bytes_to_copy;
+ if (cur >= end) {
+ assert(cur == end);
+ cur = base;
+ }
+ }
+
+ return cur;
+}
+
+static void gp_fifo_rw(void *opaque, uint8_t *ptr, unsigned int index,
+ size_t len, bool dir)
+{
+ MCPXAPUState *d = opaque;
+ uint32_t base;
+ uint32_t end;
+ hwaddr cur_reg;
+ if (dir) {
+ assert(index < GP_OUTPUT_FIFO_COUNT);
+ base = GET_MASK(d->regs[NV_PAPU_GPOFBASE0 + 0x10 * index],
+ NV_PAPU_GPOFBASE0_VALUE);
+ end = GET_MASK(d->regs[NV_PAPU_GPOFEND0 + 0x10 * index],
+ NV_PAPU_GPOFEND0_VALUE);
+ cur_reg = NV_PAPU_GPOFCUR0 + 0x10 * index;
+ } else {
+ assert(index < GP_INPUT_FIFO_COUNT);
+ base = GET_MASK(d->regs[NV_PAPU_GPIFBASE0 + 0x10 * index],
+ NV_PAPU_GPOFBASE0_VALUE);
+ end = GET_MASK(d->regs[NV_PAPU_GPIFEND0 + 0x10 * index],
+ NV_PAPU_GPOFEND0_VALUE);
+ cur_reg = NV_PAPU_GPIFCUR0 + 0x10 * index;
+ }
+
+ uint32_t cur = GET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE);
+
+ // fprintf(stderr, "GP %s fifo #%d, base = %x, end = %x, cur = %x, len = %x\n",
+ // dir ? "writing to" : "reading from", index,
+ // base, end, cur, len);
+
+ /* DSP hangs if current >= end; but forces current >= base */
+ assert(cur < end);
+ if (cur < base) {
+ cur = base;
+ }
+
+ cur = circular_scatter_gather_rw(d,
+ d->regs[NV_PAPU_GPFADDR], d->regs[NV_PAPU_GPFMAXSGE],
+ ptr, base, end, cur, len, dir);
+
+ SET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE, cur);
+}
+
+static bool ep_sink_samples(MCPXAPUState *d, uint8_t *ptr, size_t len)
+{
+ if (d->mon == MCPX_APU_DEBUG_MON_AC97) {
+ return false;
+ } else if ((d->mon == MCPX_APU_DEBUG_MON_EP) ||
+ (d->mon == MCPX_APU_DEBUG_MON_GP_OR_EP)) {
+ assert(len == sizeof(d->apu_fifo_output));
+ memcpy(d->apu_fifo_output, ptr, len);
+ }
+
+ return true;
+}
+
+static void ep_fifo_rw(void *opaque, uint8_t *ptr, unsigned int index,
+ size_t len, bool dir)
+{
+ MCPXAPUState *d = opaque;
+ uint32_t base;
+ uint32_t end;
+ hwaddr cur_reg;
+ if (dir) {
+ assert(index < EP_OUTPUT_FIFO_COUNT);
+ base = GET_MASK(d->regs[NV_PAPU_EPOFBASE0 + 0x10 * index],
+ NV_PAPU_GPOFBASE0_VALUE);
+ end = GET_MASK(d->regs[NV_PAPU_EPOFEND0 + 0x10 * index],
+ NV_PAPU_GPOFEND0_VALUE);
+ cur_reg = NV_PAPU_EPOFCUR0 + 0x10 * index;
+ } else {
+ assert(index < EP_INPUT_FIFO_COUNT);
+ base = GET_MASK(d->regs[NV_PAPU_EPIFBASE0 + 0x10 * index],
+ NV_PAPU_GPOFBASE0_VALUE);
+ end = GET_MASK(d->regs[NV_PAPU_EPIFEND0 + 0x10 * index],
+ NV_PAPU_GPOFEND0_VALUE);
+ cur_reg = NV_PAPU_EPIFCUR0 + 0x10 * index;
+ }
+
+ uint32_t cur = GET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE);
+
+ // fprintf(stderr, "EP %s fifo #%d, base = %x, end = %x, cur = %x, len = %x\n",
+ // dir ? "writing to" : "reading from", index,
+ // base, end, cur, len);
+
+ if (dir && index == 0) {
+ bool did_sink = ep_sink_samples(d, ptr, len);
+ if (did_sink) {
+ /* Since we are sinking, push silence out */
+ assert(len <= sizeof(ep_silence));
+ ptr = (uint8_t*)ep_silence;
+ }
+ }
+
+ /* DSP hangs if current >= end; but forces current >= base */
+ if (cur >= end) {
+ cur = cur % (end - base);
+ }
+ if (cur < base) {
+ cur = base;
+ }
+
+ cur = circular_scatter_gather_rw(d,
+ d->regs[NV_PAPU_EPFADDR], d->regs[NV_PAPU_EPFMAXSGE],
+ ptr, base, end, cur, len, dir);
+
+ SET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE, cur);
+}
+
+static void proc_rst_write(DSPState *dsp, uint32_t oldval, uint32_t val)
+{
+ if (!(val & NV_PAPU_GPRST_GPRST) || !(val & NV_PAPU_GPRST_GPDSPRST)) {
+ dsp_reset(dsp);
+ } else if (
+ (!(oldval & NV_PAPU_GPRST_GPRST) || !(oldval & NV_PAPU_GPRST_GPDSPRST))
+ && ((val & NV_PAPU_GPRST_GPRST) && (val & NV_PAPU_GPRST_GPDSPRST))) {
+ dsp_bootstrap(dsp);
+ }
+}
+
+/* Global Processor - programmable DSP */
+static uint64_t gp_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ MCPXAPUState *d = opaque;
+
+ assert(size == 4);
+ assert(addr % 4 == 0);
+
+ uint64_t r = 0;
+ switch (addr) {
+ case NV_PAPU_GPXMEM ... NV_PAPU_GPXMEM + 0x1000 * 4 - 1: {
+ uint32_t xaddr = (addr - NV_PAPU_GPXMEM) / 4;
+ r = dsp_read_memory(d->gp.dsp, 'X', xaddr);
+ // fprintf(stderr, "read GP NV_PAPU_GPXMEM [%x] -> %x\n", xaddr, r);
+ break;
+ }
+ case NV_PAPU_GPMIXBUF ... NV_PAPU_GPMIXBUF + 0x400 * 4 - 1: {
+ uint32_t xaddr = (addr - NV_PAPU_GPMIXBUF) / 4;
+ r = dsp_read_memory(d->gp.dsp, 'X', GP_DSP_MIXBUF_BASE + xaddr);
+ // fprintf(stderr, "read GP NV_PAPU_GPMIXBUF [%x] -> %x\n", xaddr, r);
+ break;
+ }
+ case NV_PAPU_GPYMEM ... NV_PAPU_GPYMEM + 0x800 * 4 - 1: {
+ uint32_t yaddr = (addr - NV_PAPU_GPYMEM) / 4;
+ r = dsp_read_memory(d->gp.dsp, 'Y', yaddr);
+ // fprintf(stderr, "read GP NV_PAPU_GPYMEM [%x] -> %x\n", yaddr, r);
+ break;
+ }
+ case NV_PAPU_GPPMEM ... NV_PAPU_GPPMEM + 0x1000 * 4 - 1: {
+ uint32_t paddr = (addr - NV_PAPU_GPPMEM) / 4;
+ r = dsp_read_memory(d->gp.dsp, 'P', paddr);
+ // fprintf(stderr, "read GP NV_PAPU_GPPMEM [%x] -> %x\n", paddr, r);
+ break;
+ }
+ default:
+ r = d->gp.regs[addr];
+ break;
+ }
+ DPRINTF("mcpx apu GP: read [0x%" HWADDR_PRIx "] -> 0x%lx\n", addr, r);
+
+ return r;
+}
+
+static void gp_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size)
+{
+ MCPXAPUState *d = opaque;
+
+ qemu_mutex_lock(&d->lock);
+
+ assert(size == 4);
+ assert(addr % 4 == 0);
+
+ DPRINTF("mcpx apu GP: [0x%" HWADDR_PRIx "] = 0x%lx\n", addr, val);
+
+ switch (addr) {
+ case NV_PAPU_GPXMEM ... NV_PAPU_GPXMEM + 0x1000 * 4 - 1: {
+ uint32_t xaddr = (addr - NV_PAPU_GPXMEM) / 4;
+ // fprintf(stderr, "gp write xmem %x = %x\n", xaddr, val);
+ dsp_write_memory(d->gp.dsp, 'X', xaddr, val);
+ break;
+ }
+ case NV_PAPU_GPMIXBUF ... NV_PAPU_GPMIXBUF + 0x400 * 4 - 1: {
+ uint32_t xaddr = (addr - NV_PAPU_GPMIXBUF) / 4;
+ // fprintf(stderr, "gp write xmixbuf %x = %x\n", xaddr, val);
+ dsp_write_memory(d->gp.dsp, 'X', GP_DSP_MIXBUF_BASE + xaddr, val);
+ break;
+ }
+ case NV_PAPU_GPYMEM ... NV_PAPU_GPYMEM + 0x800 * 4 - 1: {
+ uint32_t yaddr = (addr - NV_PAPU_GPYMEM) / 4;
+ // fprintf(stderr, "gp write ymem %x = %x\n", yaddr, val);
+ dsp_write_memory(d->gp.dsp, 'Y', yaddr, val);
+ break;
+ }
+ case NV_PAPU_GPPMEM ... NV_PAPU_GPPMEM + 0x1000 * 4 - 1: {
+ uint32_t paddr = (addr - NV_PAPU_GPPMEM) / 4;
+ // fprintf(stderr, "gp write pmem %x = %x\n", paddr, val);
+ dsp_write_memory(d->gp.dsp, 'P', paddr, val);
+ break;
+ }
+ case NV_PAPU_GPRST:
+ proc_rst_write(d->gp.dsp, d->gp.regs[NV_PAPU_GPRST], val);
+ d->gp.regs[NV_PAPU_GPRST] = val;
+ break;
+ default:
+ d->gp.regs[addr] = val;
+ break;
+ }
+
+ qemu_mutex_unlock(&d->lock);
+}
+
+static const MemoryRegionOps gp_ops = {
+ .read = gp_read,
+ .write = gp_write,
+};
+
+/* Encode Processor - encoding DSP */
+static uint64_t ep_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ MCPXAPUState *d = opaque;
+
+ assert(size == 4);
+ assert(addr % 4 == 0);
+
+ uint64_t r = 0;
+ switch (addr) {
+ case NV_PAPU_EPXMEM ... NV_PAPU_EPXMEM + 0xC00 * 4 - 1: {
+ uint32_t xaddr = (addr - NV_PAPU_EPXMEM) / 4;
+ r = dsp_read_memory(d->ep.dsp, 'X', xaddr);
+ // fprintf(stderr, "read EP NV_PAPU_EPXMEM [%x] -> %x\n", xaddr, r);
+ break;
+ }
+ case NV_PAPU_EPYMEM ... NV_PAPU_EPYMEM + 0x100 * 4 - 1: {
+ uint32_t yaddr = (addr - NV_PAPU_EPYMEM) / 4;
+ r = dsp_read_memory(d->ep.dsp, 'Y', yaddr);
+ // fprintf(stderr, "read EP NV_PAPU_EPYMEM [%x] -> %x\n", yaddr, r);
+ break;
+ }
+ case NV_PAPU_EPPMEM ... NV_PAPU_EPPMEM + 0x1000 * 4 - 1: {
+ uint32_t paddr = (addr - NV_PAPU_EPPMEM) / 4;
+ r = dsp_read_memory(d->ep.dsp, 'P', paddr);
+ // fprintf(stderr, "read EP NV_PAPU_EPPMEM [%x] -> %x\n", paddr, r);
+ break;
+ }
+ default:
+ r = d->ep.regs[addr];
+ break;
+ }
+ DPRINTF("mcpx apu EP: read [0x%" HWADDR_PRIx "] -> 0x%lx\n", addr, r);
+
+ return r;
+}
+
+static void ep_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size)
+{
+ MCPXAPUState *d = opaque;
+
+ qemu_mutex_lock(&d->lock);
+
+ assert(size == 4);
+ assert(addr % 4 == 0);
+
+ DPRINTF("mcpx apu EP: [0x%" HWADDR_PRIx "] = 0x%lx\n", addr, val);
+
+ switch (addr) {
+ case NV_PAPU_EPXMEM ... NV_PAPU_EPXMEM + 0xC00 * 4 - 1: {
+ uint32_t xaddr = (addr - NV_PAPU_EPXMEM) / 4;
+ dsp_write_memory(d->ep.dsp, 'X', xaddr, val);
+ // fprintf(stderr, "ep write xmem %x = %x\n", xaddr, val);
+ break;
+ }
+ case NV_PAPU_EPYMEM ... NV_PAPU_EPYMEM + 0x100 * 4 - 1: {
+ uint32_t yaddr = (addr - NV_PAPU_EPYMEM) / 4;
+ dsp_write_memory(d->ep.dsp, 'Y', yaddr, val);
+ // fprintf(stderr, "ep write ymem %x = %x\n", yaddr, val);
+ break;
+ }
+ case NV_PAPU_EPPMEM ... NV_PAPU_EPPMEM + 0x1000 * 4 - 1: {
+ uint32_t paddr = (addr - NV_PAPU_EPPMEM) / 4;
+ // fprintf(stderr, "ep write pmem %x = %x\n", paddr, val);
+ dsp_write_memory(d->ep.dsp, 'P', paddr, val);
+ break;
+ }
+ case NV_PAPU_EPRST:
+ proc_rst_write(d->ep.dsp, d->ep.regs[NV_PAPU_EPRST], val);
+ d->ep.regs[NV_PAPU_EPRST] = val;
+ d->ep_frame_div = 0; /* FIXME: Still unsure about frame sync */
+ break;
+ default:
+ d->ep.regs[addr] = val;
+ break;
+ }
+
+ qemu_mutex_unlock(&d->lock);
+}
+
+static const MemoryRegionOps ep_ops = {
+ .read = ep_read,
+ .write = ep_write,
+};
+
+static hwaddr get_data_ptr(hwaddr sge_base, unsigned int max_sge, uint32_t addr)
+{
+ unsigned int entry = addr / TARGET_PAGE_SIZE;
+ assert(entry <= max_sge);
+ uint32_t prd_address =
+ ldl_le_phys(&address_space_memory, sge_base + entry * 4 * 2);
+ // uint32_t prd_control =
+ // ldl_le_phys(&address_space_memory, sge_base + entry * 4 * 2 + 4);
+ DPRINTF("Addr: 0x%08X, control: 0x%08X\n", prd_address, prd_control);
+ return prd_address + addr % TARGET_PAGE_SIZE;
+}
+
+static float voice_step_envelope(MCPXAPUState *d, uint16_t v, uint32_t reg_0,
+ uint32_t reg_a, uint32_t rr_reg, uint32_t rr_mask,
+ uint32_t lvl_reg, uint32_t lvl_mask,
+ uint32_t count_mask, uint32_t cur_mask)
+{
+ uint8_t cur = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask);
+ switch (cur) {
+ case NV_PAVS_VOICE_PAR_STATE_EFCUR_OFF:
+ voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, 0);
+ voice_set_mask(d, v, lvl_reg, lvl_mask, 0xFF);
+ return 1.0f;
+ case NV_PAVS_VOICE_PAR_STATE_EFCUR_DELAY: {
+ uint16_t count =
+ voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
+ voice_set_mask(d, v, lvl_reg, lvl_mask, 0x00); // FIXME: Confirm this?
+
+ if (count == 0) {
+ cur++;
+ voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
+ count = 0;
+ } else {
+ count--;
+ }
+ voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
+ return 0.0f;
+ }
+ case NV_PAVS_VOICE_PAR_STATE_EFCUR_ATTACK: {
+ uint16_t count =
+ voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
+ uint16_t attack_rate =
+ voice_get_mask(d, v, reg_0, NV_PAVS_VOICE_CFG_ENV0_EA_ATTACKRATE);
+
+ float value;
+ if (attack_rate == 0) {
+ // FIXME: [division by zero]
+ // Got crackling sound in hardware for amplitude env.
+ value = 255.0f;
+ } else {
+ if (count <= (attack_rate * 16)) {
+ value = (count * 0xFF) / (attack_rate * 16);
+ } else {
+ // FIXME: Overflow in hardware
+ // The actual value seems to overflow, but not sure how
+ value = 255.0f;
+ }
+ }
+ voice_set_mask(d, v, lvl_reg, lvl_mask, value);
+ // FIXME: Comparison could also be the other way around?! Test please.
+ if (count == (attack_rate * 16)) {
+ cur++;
+ voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
+ uint16_t hold_time =
+ voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME);
+ count = hold_time * 16; // FIXME: Skip next phase if count is 0?
+ // [other instances too]
+ } else {
+ count++;
+ }
+ voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
+ return value / 255.0f;
+ }
+ case NV_PAVS_VOICE_PAR_STATE_EFCUR_HOLD: {
+ uint16_t count =
+ voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
+ voice_set_mask(d, v, lvl_reg, lvl_mask, 0xFF);
+
+ if (count == 0) {
+ cur++;
+ voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
+ uint16_t decay_rate = voice_get_mask(
+ d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE);
+ count = decay_rate * 16;
+ } else {
+ count--;
+ }
+ voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
+ return 1.0f;
+ }
+ case NV_PAVS_VOICE_PAR_STATE_EFCUR_DECAY: {
+ uint16_t count =
+ voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
+ uint16_t decay_rate =
+ voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE);
+ uint8_t sustain_level =
+ voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL);
+
+ // FIXME: Decay should return a value no less than sustain
+ float value;
+ if (decay_rate == 0) {
+ value = 0.0f;
+ } else {
+ // FIXME: This formula and threshold is not accurate, but I can't
+ // get it any better for now
+ value = 255.0f * powf(0.99988799f, (decay_rate * 16 - count) *
+ 4096 / decay_rate);
+ }
+ if (value <= (sustain_level + 0.2f) || (value > 255.0f)) {
+ // FIXME: Should we still update lvl?
+ cur++;
+ voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
+ } else {
+ count--;
+ voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
+ voice_set_mask(d, v, lvl_reg, lvl_mask, value);
+ }
+ return value / 255.0f;
+ }
+ case NV_PAVS_VOICE_PAR_STATE_EFCUR_SUSTAIN: {
+ uint8_t sustain_level =
+ voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL);
+ voice_set_mask(
+ d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask,
+ 0x00); // FIXME: is this only set to 0 once or forced to zero?
+ voice_set_mask(d, v, lvl_reg, lvl_mask, sustain_level);
+ return sustain_level / 255.0f;
+ }
+ case NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE: {
+ uint16_t count =
+ voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
+ uint16_t release_rate = voice_get_mask(d, v, rr_reg, rr_mask);
+
+ if (release_rate == 0) {
+ count = 0;
+ }
+
+ float value = 0;
+ if (count == 0) {
+ voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, ++cur);
+ } else {
+ // FIXME: Appears to be an exponential but unsure about actual
+ // curve; performing standard decay of current level to T60 over the
+ // release interval which seems about right.
+ // FIXME: Based on sustain level or just decay of current level?
+ // FIXME: Update level? A very similar, alternative decay function
+ // (probably what the hw actually does): y(t)=2^(-10t), which would
+ // permit simpler attenuation more efficiently and update level on
+ // each round.
+ float pos = clampf(1 - count / (release_rate * 16.0), 0, 1);
+ uint8_t lvl = voice_get_mask(d, v, lvl_reg, lvl_mask);
+ value = powf(M_E, -6.91*pos)*lvl;
+ count--; // FIXME: Should release count ascend or descend?
+ voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
+ }
+
+ return value / 255.0f;
+ }
+ case NV_PAVS_VOICE_PAR_STATE_EFCUR_FORCE_RELEASE:
+ if (count_mask == NV_PAVS_VOICE_CUR_ECNT_EACOUNT) {
+ voice_off(d, v);
+ }
+ return 0.0f;
+ default:
+ fprintf(stderr, "Unknown envelope state 0x%x\n", cur);
+ assert(false);
+ return 0.0f;
+ }
+}
+
+static void set_notify_status(MCPXAPUState *d, uint32_t v, int notifier,
+ int status)
+{
+ hwaddr notify_offset = d->regs[NV_PAPU_FENADDR];
+ notify_offset += 16 * (MCPX_HW_NOTIFIER_BASE_OFFSET +
+ v * MCPX_HW_NOTIFIER_COUNT + notifier);
+ notify_offset += 15; // Final byte is status, same for all notifiers
+
+ // FIXME: Check notify enable
+ // FIXME: Set NV1BA0_NOTIFICATION_STATUS_IN_PROGRESS when appropriate
+ stb_phys(&address_space_memory, notify_offset, status);
+
+ // FIXME: Refactor this out of here
+ // FIXME: Actually provied current envelope state
+ stb_phys(&address_space_memory, notify_offset - 1, 1);
+
+ atomic_or(&d->regs[NV_PAPU_ISTS],
+ NV_PAPU_ISTS_FEVINTSTS | NV_PAPU_ISTS_FENINTSTS);
+ d->set_irq = true;
+}
+
+static long voice_resample_callback(void *cb_data, float **data)
+{
+ MCPXAPUVoiceFilter *filter = cb_data;
+ uint16_t v = filter->voice;
+ assert(v < MCPX_HW_MAX_VOICES);
+ MCPXAPUState *d = container_of(filter, MCPXAPUState, vp.filters[v]);
+
+ int sample_count = 0;
+ while (sample_count < NUM_SAMPLES_PER_FRAME) {
+ int active = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE);
+ if (!active) {
+ break;
+ }
+ int count = voice_get_samples(
+ d, v, (float(*)[2]) & filter->resample_buf[2 * sample_count],
+ NUM_SAMPLES_PER_FRAME - sample_count);
+ if (count < 0) {
+ break;
+ }
+ sample_count += count;
+ }
+
+ if (sample_count < NUM_SAMPLES_PER_FRAME) {
+ /* Starvation causes SRC hang on repeated calls. Provide silence. */
+ memset(&filter->resample_buf[2*sample_count], 0,
+ 2*(NUM_SAMPLES_PER_FRAME-sample_count)*sizeof(float));
+ sample_count = NUM_SAMPLES_PER_FRAME;
+ }
+
+ *data = filter->resample_buf;
+ return sample_count;
+}
+
+static int voice_resample(MCPXAPUState *d, uint16_t v, float samples[][2],
+ int requested_num, float rate)
+{
+ assert(v < MCPX_HW_MAX_VOICES);
+ MCPXAPUVoiceFilter *filter = &d->vp.filters[v];
+
+ if (filter->resampler == NULL) {
+ filter->voice = v;
+ int err;
+
+ /* Note: Using a sinc based resampler for quality. Unsure about
+ * hardware's actual interpolation method; it could just be linear, in
+ * which case using this resampler is overkill, but quality is good
+ * so use it for now.
+ */
+ // FIXME: Don't do 2ch resampling if this is a mono voice
+ filter->resampler = src_callback_new(&voice_resample_callback,
+ SRC_SINC_FASTEST, 2, &err, filter);
+ if (filter->resampler == NULL) {
+ fprintf(stderr, "src error: %s\n", src_strerror(err));
+ assert(0);
+ }
+ }
+
+ int count = src_callback_read(filter->resampler, rate, requested_num,
+ (float *)samples);
+ if (count == -1) {
+ DPRINTF("resample error\n");
+ }
+ if (count != requested_num) {
+ DPRINTF("resample returned fewer than expected: %d\n", count);
+
+ if (count == 0)
+ return -1;
+ }
+
+ return count;
+}
+
+static void voice_reset_filters(MCPXAPUState *d, uint16_t v)
+{
+ assert(v < MCPX_HW_MAX_VOICES);
+ memset(&d->vp.filters[v].svf, 0, sizeof(d->vp.filters[v].svf));
+ if (d->vp.filters[v].resampler) {
+ src_reset(d->vp.filters[v].resampler);
+ }
+}
+
+static void voice_process(MCPXAPUState *d,
+ float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME],
+ uint16_t v)
+{
+ assert(v < MCPX_HW_MAX_VOICES);
+ bool stereo = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
+ NV_PAVS_VOICE_CFG_FMT_STEREO);
+ unsigned int channels = stereo ? 2 : 1;
+ bool paused = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_PAUSED);
+
+ struct McpxApuDebugVoice *dbg = &g_dbg.vp.v[v];
+ dbg->active = true;
+ dbg->stereo = stereo;
+ dbg->paused = paused;
+
+ if (paused) {
+ return;
+ }
+
+ float ef_value = voice_step_envelope(
+ d, v, NV_PAVS_VOICE_CFG_ENV1, NV_PAVS_VOICE_CFG_ENVF,
+ NV_PAVS_VOICE_CFG_MISC, NV_PAVS_VOICE_CFG_MISC_EF_RELEASERATE,
+ NV_PAVS_VOICE_PAR_NEXT, NV_PAVS_VOICE_PAR_NEXT_EFLVL,
+ NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, NV_PAVS_VOICE_PAR_STATE_EFCUR);
+ assert(ef_value >= 0.0f);
+ assert(ef_value <= 1.0f);
+ int16_t p = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_PITCH_LINK,
+ NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH);
+ int8_t ps = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_ENV0,
+ NV_PAVS_VOICE_CFG_ENV0_EF_PITCHSCALE);
+ float rate = 1.0 / powf(2.0f, (p + ps * 32 * ef_value) / 4096.0f);
+ dbg->rate = rate;
+
+ float ea_value = voice_step_envelope(
+ d, v, NV_PAVS_VOICE_CFG_ENV0, NV_PAVS_VOICE_CFG_ENVA,
+ NV_PAVS_VOICE_TAR_LFO_ENV, NV_PAVS_VOICE_TAR_LFO_ENV_EA_RELEASERATE,
+ NV_PAVS_VOICE_PAR_OFFSET, NV_PAVS_VOICE_PAR_OFFSET_EALVL,
+ NV_PAVS_VOICE_CUR_ECNT_EACOUNT, NV_PAVS_VOICE_PAR_STATE_EACUR);
+ assert(ea_value >= 0.0f);
+ assert(ea_value <= 1.0f);
+
+ float samples[NUM_SAMPLES_PER_FRAME][2] = { 0 };
+ for (int sample_count = 0; sample_count < NUM_SAMPLES_PER_FRAME;) {
+ int active = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE);
+ if (!active) {
+ return;
+ }
+ int count = voice_resample(d, v, &samples[sample_count],
+ NUM_SAMPLES_PER_FRAME - sample_count, rate);
+ if (count < 0) {
+ break;
+ }
+ sample_count += count;
+ }
+
+ int active = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE);
+ if (!active) {
+ return;
+ }
+
+ int bin[8];
+ bin[0] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN,
+ NV_PAVS_VOICE_CFG_VBIN_V0BIN);
+ bin[1] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN,
+ NV_PAVS_VOICE_CFG_VBIN_V1BIN);
+ bin[2] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN,
+ NV_PAVS_VOICE_CFG_VBIN_V2BIN);
+ bin[3] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN,
+ NV_PAVS_VOICE_CFG_VBIN_V3BIN);
+ bin[4] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN,
+ NV_PAVS_VOICE_CFG_VBIN_V4BIN);
+ bin[5] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN,
+ NV_PAVS_VOICE_CFG_VBIN_V5BIN);
+ bin[6] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
+ NV_PAVS_VOICE_CFG_FMT_V6BIN);
+ bin[7] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
+ NV_PAVS_VOICE_CFG_FMT_V7BIN);
+
+ if (v < 64) {
+ bin[0] = d->vp.hrtf_submix[0];
+ bin[1] = d->vp.hrtf_submix[1];
+ bin[2] = d->vp.hrtf_submix[2];
+ bin[3] = d->vp.hrtf_submix[3];
+ }
+
+ uint16_t vol[8];
+ vol[0] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA,
+ NV_PAVS_VOICE_TAR_VOLA_VOLUME0);
+ vol[1] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA,
+ NV_PAVS_VOICE_TAR_VOLA_VOLUME1);
+ vol[2] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB,
+ NV_PAVS_VOICE_TAR_VOLB_VOLUME2);
+ vol[3] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB,
+ NV_PAVS_VOICE_TAR_VOLB_VOLUME3);
+ vol[4] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC,
+ NV_PAVS_VOICE_TAR_VOLC_VOLUME4);
+ vol[5] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC,
+ NV_PAVS_VOICE_TAR_VOLC_VOLUME5);
+
+ vol[6] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC,
+ NV_PAVS_VOICE_TAR_VOLC_VOLUME6_B11_8) << 8;
+ vol[6] |= voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB,
+ NV_PAVS_VOICE_TAR_VOLB_VOLUME6_B7_4) << 4;
+ vol[6] |= voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA,
+ NV_PAVS_VOICE_TAR_VOLA_VOLUME6_B3_0);
+ vol[7] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC,
+ NV_PAVS_VOICE_TAR_VOLC_VOLUME7_B11_8) << 8;
+ vol[7] |= voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB,
+ NV_PAVS_VOICE_TAR_VOLB_VOLUME7_B7_4) << 4;
+ vol[7] |= voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA,
+ NV_PAVS_VOICE_TAR_VOLA_VOLUME7_B3_0);
+
+ // FIXME: If phase negations means to flip the signal upside down
+ // we should modify volume of bin6 and bin7 here.
+
+ for (int i = 0; i < 8; i++) {
+ dbg->bin[i] = bin[i];
+ dbg->vol[i] = vol[i];
+ }
+
+ if (voice_should_mute(v)) {
+ return;
+ }
+
+ int fmode = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_MISC,
+ NV_PAVS_VOICE_CFG_MISC_FMODE);
+
+ // FIXME: Move to function
+ bool lpf = false;
+ if (v < 64) {
+ /* 1:DLS2+I3DL2 2:ParaEQ+I3DL2 3:I3DL2 */
+ lpf = (fmode == 1);
+ } else {
+ /* 0:Bypass 1:DLS2 2:ParaEQ 3(Mono):DLS2+ParaEQ 3(Stereo):Bypass */
+ lpf = stereo ? (fmode == 1) : (fmode & 1);
+ }
+ if (lpf) {
+ for (int ch = 0; ch < 2; ch++) {
+ // FIXME: Cutoff modulation via NV_PAVS_VOICE_CFG_ENV1_EF_FCSCALE
+ int16_t fc = voice_get_mask(
+ d, v, NV_PAVS_VOICE_TAR_FCA + (ch % channels) * 4,
+ NV_PAVS_VOICE_TAR_FCA_FC0);
+ float fc_f = clampf(pow(2, fc / 4096.0), 0.003906f, 1.0f);
+ uint16_t q = voice_get_mask(
+ d, v, NV_PAVS_VOICE_TAR_FCA + (ch % channels) * 4,
+ NV_PAVS_VOICE_TAR_FCA_FC1);
+ float q_f = clampf(q / (1.0 * 0x8000), 0.079407f, 1.0f);
+ sv_filter *filter = &d->vp.filters[v].svf[ch];
+ setup_svf(filter, fc_f, q_f, F_LP);
+ for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) {
+ samples[i][ch] = run_svf(filter, samples[i][ch]);
+ samples[i][ch] = fmin(fmax(samples[i][ch], -1.0), 1.0);
+ }
+ }
+ }
+
+ // FIXME: ParaEQ
+
+ for (int b = 0; b < 8; b++) {
+ float g = ea_value;
+ float hr;
+ if ((v < 64) && (b < 4)) {
+ // FIXME: Not sure if submix/voice headroom factor in for HRTF
+ // Note: Attenuate extra 6dB to simulate HRTF
+ hr = 1 << (d->vp.hrtf_headroom + 1);
+ } else {
+ hr = 1 << d->vp.submix_headroom[bin[b]];
+ }
+ g *= attenuate(vol[b])/hr;
+ for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) {
+ mixbins[bin[b]][i] += g*samples[i][b % channels];
+ }
+ }
+
+ if (d->mon == MCPX_APU_DEBUG_MON_VP) {
+ /* For VP mon, simply mix all voices together here, selecting the
+ * maximal volume used for any given mixbin as the overall volume for
+ * this voice.
+ */
+ float g = 0.0f;
+ for (int b = 0; b < 8; b++) {
+ float hr = 1 << d->vp.submix_headroom[bin[b]];
+ g = fmax(g, attenuate(vol[b]) / hr);
+ }
+ g *= ea_value;
+ for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) {
+ d->vp.sample_buf[i][0] += g*samples[i][0];
+ d->vp.sample_buf[i][1] += g*samples[i][1];
+ }
+ }
+}
+
+static int voice_get_samples(MCPXAPUState *d, uint32_t v, float samples[][2],
+ int num_samples_requested)
+{
+ assert(v < MCPX_HW_MAX_VOICES);
+ bool stereo = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
+ NV_PAVS_VOICE_CFG_FMT_STEREO);
+ unsigned int channels = stereo ? 2 : 1;
+ unsigned int sample_size = voice_get_mask(
+ d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE);
+ unsigned int container_sizes[4] = { 1, 2, 0, 4 }; /* B8, B16, ADPCM, B32 */
+ unsigned int container_size_index = voice_get_mask(
+ d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE);
+ unsigned int container_size = container_sizes[container_size_index];
+ bool stream = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
+ NV_PAVS_VOICE_CFG_FMT_DATA_TYPE);
+ bool paused = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_PAUSED);
+ bool loop =
+ voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_LOOP);
+ uint32_t ebo = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_NEXT,
+ NV_PAVS_VOICE_PAR_NEXT_EBO);
+ uint32_t cbo = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_OFFSET,
+ NV_PAVS_VOICE_PAR_OFFSET_CBO);
+ uint32_t lbo = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_PSH_SAMPLE,
+ NV_PAVS_VOICE_CUR_PSH_SAMPLE_LBO);
+ uint32_t ba = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_PSL_START,
+ NV_PAVS_VOICE_CUR_PSL_START_BA);
+ unsigned int samples_per_block =
+ 1 + voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
+ NV_PAVS_VOICE_CFG_FMT_SAMPLES_PER_BLOCK);
+ bool persist = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
+ NV_PAVS_VOICE_CFG_FMT_PERSIST);
+ bool multipass = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
+ NV_PAVS_VOICE_CFG_FMT_MULTIPASS);
+ bool linked = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
+ NV_PAVS_VOICE_CFG_FMT_LINKED); /* FIXME? */
+
+ int ssl_index = 0;
+ int ssl_seg = 0;
+ int page = 0;
+ int count = 0;
+ int seg_len = 0;
+ int seg_cs = 0;
+ int seg_spb = 0;
+ int seg_s = 0;
+ hwaddr segment_offset = 0;
+ uint32_t segment_length = 0;
+ size_t block_size;
+
+ int adpcm_block_index = -1;
+ uint32_t adpcm_block[36*2/4];
+ int16_t adpcm_decoded[65*2]; // FIXME: Move out of here
+
+ // FIXME: Only update if necessary
+ struct McpxApuDebugVoice *dbg = &g_dbg.vp.v[v];
+ dbg->container_size = container_size_index;
+ dbg->sample_size = sample_size;
+ dbg->stream = stream;
+ dbg->loop = loop;
+ dbg->ebo = ebo;
+ dbg->cbo = cbo;
+ dbg->lbo = lbo;
+ dbg->ba = ba;
+ dbg->samples_per_block = samples_per_block;
+ dbg->persist = persist;
+ dbg->multipass = multipass;
+ dbg->linked = linked;
+
+ // This is probably cleared when the first sample is played
+ // FIXME: How will this behave if CBO > EBO on first play?
+ // FIXME: How will this behave if paused?
+ voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_NEW_VOICE, 0);
+
+ if (paused) {
+ return -1;
+ }
+
+ if (stream) {
+ if (!persist) {
+ // FIXME: Confirm. Unsure if this should wait until end of SSL or
+ // terminate immediately. Definitely not before end of envelope.
+ int eacur = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_EACUR);
+ if (eacur < NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE) {
+ DPRINTF("Voice %d envelope not in release state (%d) and "
+ "persist is not set. Ending stream now!\n",
+ v, eacur);
+ voice_off(d, v);
+ return -1;
+ }
+ }
+
+ DPRINTF("**** STREAMING (%d) ****\n", v);
+ assert(!loop);
+
+ ssl_index = d->vp.ssl[v].ssl_index;
+ ssl_seg = d->vp.ssl[v].ssl_seg;
+ page = d->vp.ssl[v].base[ssl_index] + ssl_seg;
+ count = d->vp.ssl[v].count[ssl_index];
+
+ // Check to see if the stream has ended
+ if (count == 0) {
+ DPRINTF("Stream has ended\n");
+ voice_set_mask(d, v, NV_PAVS_VOICE_PAR_OFFSET,
+ NV_PAVS_VOICE_PAR_OFFSET_CBO, 0);
+ d->vp.ssl[v].ssl_seg = 0;
+ d->vp.ssl[v].ssl_index = 0;
+ voice_off(d, v);
+ return -1;
+ }
+
+ hwaddr addr = d->regs[NV_PAPU_VPSSLADDR] + page * 8;
+ segment_offset = ldl_le_phys(&address_space_memory, addr);
+ segment_length = ldl_le_phys(&address_space_memory, addr + 4);
+ assert(segment_offset != 0);
+ assert(segment_length != 0);
+ seg_len = (segment_length >> 0) & 0xffff;
+ seg_cs = (segment_length >> 16) & 3;
+ seg_spb = (segment_length >> 18) & 0x1f;
+ seg_s = (segment_length >> 23) & 1;
+ assert(seg_cs == container_size_index);
+ assert((seg_spb + 1) == samples_per_block);
+ assert(seg_s == stereo);
+ container_size_index = seg_cs;
+ if (seg_cs == NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_ADPCM) {
+ sample_size = NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24;
+ }
+
+ assert(seg_len > 0);
+ ebo = seg_len - 1; // FIXME: Confirm seg_len-1 is last valid sample index
+
+ DPRINTF("Segment: SSL%c[%d]\n", 'A' + ssl_index, ssl_seg);
+ DPRINTF("Page: %x\n", page);
+ DPRINTF("Count: %d\n", count);
+ DPRINTF("Segment offset: 0x%" HWADDR_PRIx "\n", segment_offset);
+ DPRINTF("Segment length: %x\n", segment_length);
+ DPRINTF("...len = 0x%x\n", seg_len);
+ DPRINTF("...cs = %d (%s)\n", seg_cs, container_size_str[seg_cs]);
+ DPRINTF("...spb = %d\n", seg_spb);
+ DPRINTF("...s = %d (%s)\n", seg_s, seg_s ? "stereo" : "mono");
+ } else {
+ DPRINTF("**** BUFFER (%d) ****\n", v);
+ }
+
+ bool adpcm =
+ (container_size_index == NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_ADPCM);
+
+ if (adpcm) {
+ block_size = 36;
+ DPRINTF("ADPCM:\n");
+ } else {
+ assert(container_size_index < 4);
+ assert(sample_size < 4);
+ block_size = container_size;
+ DPRINTF("PCM:\n");
+ DPRINTF(" Container Size: %s\n",
+ container_size_str[container_size_index]);
+ DPRINTF(" Sample Size: %s\n", sample_size_str[sample_size]);
+ }
+
+ DPRINTF("CBO=%d EBO=%d\n", cbo, ebo);
+
+ if (multipass) {
+ // FIXME
+ samples_per_block = 1;
+ }
+
+ block_size *= samples_per_block;
+
+ // FIXME: Restructure this loop
+ int sample_count = 0;
+ for (; (sample_count < num_samples_requested) && (cbo <= ebo);
+ sample_count++, cbo++) {
+ if (adpcm) {
+ unsigned int block_index = cbo / ADPCM_SAMPLES_PER_BLOCK;
+ unsigned int block_position = cbo % ADPCM_SAMPLES_PER_BLOCK;
+ if (adpcm_block_index != block_index) {
+ uint32_t linear_addr = block_index * block_size;
+ if (stream) {
+ hwaddr addr = segment_offset + linear_addr;
+ int max_seg_byte = (seg_len >> 6) * block_size;
+ assert(linear_addr + block_size <= max_seg_byte);
+ memcpy(adpcm_block, &d->ram_ptr[addr],
+ block_size); // FIXME: Use idiomatic DMA function
+ } else {
+ linear_addr += ba;
+ for (unsigned int word_index = 0;
+ word_index < (9 * samples_per_block); word_index++) {
+ hwaddr addr = get_data_ptr(d->regs[NV_PAPU_VPSGEADDR],
+ 0xFFFFFFFF, linear_addr);
+ adpcm_block[word_index] =
+ ldl_le_phys(&address_space_memory, addr);
+ linear_addr += 4;
+ }
+ }
+ adpcm_decode_block(adpcm_decoded, (uint8_t *)adpcm_block,
+ block_size, channels);
+ adpcm_block_index = block_index;
+ }
+
+ samples[sample_count][0] =
+ int16_to_float(adpcm_decoded[block_position * channels]);
+ if (stereo) {
+ samples[sample_count][1] = int16_to_float(
+ adpcm_decoded[block_position * channels + 1]);
+ }
+ } else {
+ // FIXME: Handle reading accross pages?!
+
+ hwaddr addr;
+ if (stream) {
+ addr = segment_offset + cbo * block_size;
+ } else {
+ uint32_t linear_addr = ba + cbo * block_size;
+ addr = get_data_ptr(d->regs[NV_PAPU_VPSGEADDR], 0xFFFFFFFF,
+ linear_addr);
+ }
+
+ for (unsigned int channel = 0; channel < channels; channel++) {
+ uint32_t ival;
+ float fval;
+ switch (sample_size) {
+ case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_U8:
+ ival = ldub_phys(&address_space_memory, addr);
+ fval = uint8_to_float(ival & 0xff);
+ break;
+ case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S16:
+ ival = lduw_le_phys(&address_space_memory, addr);
+ fval = int16_to_float(ival & 0xffff);
+ break;
+ case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24:
+ ival = ldl_le_phys(&address_space_memory, addr);
+ fval = int24_to_float(ival);
+ break;
+ case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S32:
+ ival = ldl_le_phys(&address_space_memory, addr);
+ fval = int32_to_float(ival);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ samples[sample_count][channel] = fval;
+ addr += container_size;
+ }
+ }
+
+ if (!stereo) {
+ samples[sample_count][1] = samples[sample_count][0];
+ }
+ }
+
+ if (cbo >= ebo) {
+ if (stream) {
+ d->vp.ssl[v].ssl_seg += 1;
+ cbo = 0;
+ if (d->vp.ssl[v].ssl_seg < d->vp.ssl[v].count[ssl_index]) {
+ DPRINTF("SSL%c[%d]\n", 'A' + ssl_index, d->vp.ssl[v].ssl_seg);
+ } else {
+ int next_index = (ssl_index + 1) % 2;
+ DPRINTF("SSL%c\n", 'A' + next_index);
+ d->vp.ssl[v].ssl_index = next_index;
+ d->vp.ssl[v].ssl_seg = 0;
+ set_notify_status(d, v, MCPX_HW_NOTIFIER_SSLA_DONE + ssl_index,
+ NV1BA0_NOTIFICATION_STATUS_DONE_SUCCESS);
+ }
+ if (d->vp.ssl[v].count[d->vp.ssl[v].ssl_index] == 0) {
+ voice_off(d, v);
+ }
+ } else {
+ if (loop) {
+ cbo = lbo;
+ } else {
+ cbo = ebo;
+ voice_off(d, v);
+ DPRINTF("end of buffer!\n");
+ }
+ }
+ }
+
+ voice_set_mask(d, v, NV_PAVS_VOICE_PAR_OFFSET,
+ NV_PAVS_VOICE_PAR_OFFSET_CBO, cbo);
+ return sample_count;
+}
+
+static void se_frame(MCPXAPUState *d)
+{
+ mcpx_debug_begin_frame();
+ g_dbg.gp_realtime = d->gp.realtime;
+ g_dbg.ep_realtime = d->ep.realtime;
+
+ qemu_spin_lock(&d->vp.out_buf_lock);
+ int num_bytes_free = fifo8_num_free(&d->vp.out_buf);
+ qemu_spin_unlock(&d->vp.out_buf_lock);
+
+ /* A rudimentary calculation to determine approximately how taxed the APU
+ * thread is, by measuring how much time we spend waiting for FIFO to drain
+ * versus working on building frames.
+ * =1: thread is not sleeping and likely falling behind realtime
+ * <1: thread is able to complete work on time
+ */
+ if (num_bytes_free < sizeof(d->apu_fifo_output)) {
+ int64_t sleep_start = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
+ qemu_cond_wait(&d->cond, &d->lock);
+ int64_t sleep_end = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
+ d->sleep_acc += (sleep_end - sleep_start);
+ return;
+ }
+ int64_t now = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
+ if (now - d->frame_count_time >= 1000) {
+ g_dbg.frames_processed = d->frame_count;
+ float t = 1.0f - ((double)d->sleep_acc /
+ (double)((now - d->frame_count_time) * 1000));
+ g_dbg.utilization = t;
+
+ d->frame_count_time = now;
+ d->frame_count = 0;
+ d->sleep_acc = 0;
+ }
+ d->frame_count++;
+
+ /* Buffer for all mixbins for this frame */
+ float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME] = { 0 };
+
+ memset(d->vp.sample_buf, 0, sizeof(d->vp.sample_buf));
+
+ /* Process all voices, mixing each into the affected MIXBINs */
+ for (int list = 0; list < 3; list++) {
+ hwaddr top, current, next;
+ top = voice_list_regs[list].top;
+ current = voice_list_regs[list].current;
+ next = voice_list_regs[list].next;
+
+ d->regs[current] = d->regs[top];
+ DPRINTF("list %d current voice %d\n", list, d->regs[current]);
+
+ for (int i = 0; d->regs[current] != 0xFFFF; i++) {
+ /* Make sure not to get stuck... */
+ if (i >= MCPX_HW_MAX_VOICES) {
+ DPRINTF("Voice list contains invalid entry!\n");
+ break;
+ }
+
+ uint16_t v = d->regs[current];
+ d->regs[next] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_PITCH_LINK,
+ NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE);
+ if (!voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE,
+ NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE)) {
+ fe_method(d, SE2FE_IDLE_VOICE, v);
+ } else {
+ qemu_spin_lock(&d->vp.voice_spinlocks[v]);
+ while (is_voice_locked(d, v)) {
+ /* Stall until voice is available */
+ qemu_spin_unlock(&d->vp.voice_spinlocks[v]);
+ qemu_cond_wait(&d->cond, &d->lock);
+ qemu_spin_lock(&d->vp.voice_spinlocks[v]);
+ }
+ voice_process(d, mixbins, v);
+ qemu_spin_unlock(&d->vp.voice_spinlocks[v]);
+ }
+ d->regs[current] = d->regs[next];
+ }
+ }
+
+ if (d->mon == MCPX_APU_DEBUG_MON_VP) {
+ /* Mix all voices together to hear any audible voice */
+ int16_t isamp[NUM_SAMPLES_PER_FRAME * 2];
+ src_float_to_short_array((float *)d->vp.sample_buf, isamp,
+ NUM_SAMPLES_PER_FRAME * 2);
+ int off = (d->ep_frame_div % 8) * NUM_SAMPLES_PER_FRAME;
+ for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) {
+ d->apu_fifo_output[off + i][0] += isamp[2*i];
+ d->apu_fifo_output[off + i][1] += isamp[2*i+1];
+ }
+ memset(d->vp.sample_buf, 0, sizeof(d->vp.sample_buf));
+ memset(mixbins, 0, sizeof(mixbins));
+ }
+
+ /* Write VP results to the GP DSP MIXBUF */
+ for (int mixbin = 0; mixbin < NUM_MIXBINS; mixbin++) {
+ uint32_t base = GP_DSP_MIXBUF_BASE + mixbin * NUM_SAMPLES_PER_FRAME;
+ for (int sample = 0; sample < NUM_SAMPLES_PER_FRAME; sample++) {
+ dsp_write_memory(d->gp.dsp, 'X', base + sample,
+ float_to_24b(mixbins[mixbin][sample]));
+ }
+ }
+
+ bool ep_enabled = (d->ep.regs[NV_PAPU_EPRST] & NV_PAPU_GPRST_GPRST) &&
+ (d->ep.regs[NV_PAPU_EPRST] & NV_PAPU_GPRST_GPDSPRST);
+
+ /* Run GP */
+ if ((d->gp.regs[NV_PAPU_GPRST] & NV_PAPU_GPRST_GPRST) &&
+ (d->gp.regs[NV_PAPU_GPRST] & NV_PAPU_GPRST_GPDSPRST)) {
+ dsp_start_frame(d->gp.dsp);
+ d->gp.dsp->core.is_idle = false;
+ d->gp.dsp->core.cycle_count = 0;
+ do {
+ dsp_run(d->gp.dsp, 1000);
+ } while (!d->gp.dsp->core.is_idle && d->gp.realtime);
+ g_dbg.gp.cycles = d->gp.dsp->core.cycle_count;
+
+ if ((d->mon == MCPX_APU_DEBUG_MON_GP) ||
+ (d->mon == MCPX_APU_DEBUG_MON_GP_OR_EP && !ep_enabled)) {
+ int off = (d->ep_frame_div % 8) * NUM_SAMPLES_PER_FRAME;
+ for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) {
+ uint32_t l = dsp_read_memory(d->gp.dsp, 'X', 0x1400 + i);
+ d->apu_fifo_output[off + i][0] = l >> 8;
+ uint32_t r =
+ dsp_read_memory(d->gp.dsp, 'X', 0x1400 + 1 * 0x20 + i);
+ d->apu_fifo_output[off + i][1] = r >> 8;
+ }
+ }
+ }
+
+ /* Run EP */
+ if ((d->ep.regs[NV_PAPU_EPRST] & NV_PAPU_GPRST_GPRST) &&
+ (d->ep.regs[NV_PAPU_EPRST] & NV_PAPU_GPRST_GPDSPRST)) {
+ if (d->ep_frame_div % 8 == 0) {
+ dsp_start_frame(d->ep.dsp);
+ d->ep.dsp->core.is_idle = false;
+ d->ep.dsp->core.cycle_count = 0;
+ do {
+ dsp_run(d->ep.dsp, 1000);
+ } while (!d->ep.dsp->core.is_idle && d->ep.realtime);
+ g_dbg.ep.cycles = d->ep.dsp->core.cycle_count;
+ }
+ }
+
+ if ((d->ep_frame_div + 1) % 8 == 0) {
+#if 0
+ FILE *fd = fopen("ep.pcm", "a+");
+ assert(fd != NULL);
+ fwrite(d->apu_fifo_output, sizeof(d->apu_fifo_output), 1, fd);
+ fclose(fd);
+#endif
+ qemu_spin_lock(&d->vp.out_buf_lock);
+ int num_bytes_free = fifo8_num_free(&d->vp.out_buf);
+ assert(num_bytes_free >= sizeof(d->apu_fifo_output));
+ fifo8_push_all(&d->vp.out_buf, (uint8_t *)d->apu_fifo_output,
+ sizeof(d->apu_fifo_output));
+ qemu_spin_unlock(&d->vp.out_buf_lock);
+ memset(d->apu_fifo_output, 0, sizeof(d->apu_fifo_output));
+ }
+
+ d->ep_frame_div++;
+
+ mcpx_debug_end_frame();
+}
+
+/* Note: only supports millisecond resolution on Windows */
+static void sleep_ns(int64_t ns)
+{
+#ifndef _WIN32
+ struct timespec sleep_delay, rem_delay;
+ sleep_delay.tv_sec = ns / 1000000000LL;
+ sleep_delay.tv_nsec = ns % 1000000000LL;
+ nanosleep(&sleep_delay, &rem_delay);
+#else
+ Sleep(ns / SCALE_MS);
+#endif
+}
+
+static void mcpx_vp_out_cb(void *opaque, uint8_t *stream, int free_b)
+{
+ MCPXAPUState *s = MCPX_APU_DEVICE(opaque);
+
+ if (!runstate_is_running()) {
+ memset(stream, 0, free_b);
+ return;
+ }
+
+ int avail = 0;
+ while (avail < free_b) {
+ qemu_spin_lock(&s->vp.out_buf_lock);
+ avail = fifo8_num_used(&s->vp.out_buf);
+ qemu_spin_unlock(&s->vp.out_buf_lock);
+ if (avail < free_b) {
+ sleep_ns(1000000);
+ qemu_cond_broadcast(&s->cond);
+ }
+ }
+
+ int to_copy = MIN(free_b, avail);
+ while (to_copy > 0) {
+ uint32_t chunk_len = 0;
+ qemu_spin_lock(&s->vp.out_buf_lock);
+ const uint8_t *samples =
+ fifo8_pop_buf(&s->vp.out_buf, to_copy, &chunk_len);
+ assert(chunk_len <= to_copy);
+ memcpy(stream, samples, chunk_len);
+ qemu_spin_unlock(&s->vp.out_buf_lock);
+ stream += chunk_len;
+ to_copy -= chunk_len;
+ }
+
+ qemu_cond_broadcast(&s->cond);
+}
+
+static void mcpx_apu_realize(PCIDevice *dev, Error **errp)
+{
+ MCPXAPUState *d = MCPX_APU_DEVICE(dev);
+
+ dev->config[PCI_INTERRUPT_PIN] = 0x01;
+
+ memory_region_init_io(&d->mmio, OBJECT(dev), &mcpx_apu_mmio_ops, d,
+ "mcpx-apu-mmio", 0x80000);
+
+ memory_region_init_io(&d->vp.mmio, OBJECT(dev), &vp_ops, d,
+ "mcpx-apu-vp", 0x10000);
+ memory_region_add_subregion(&d->mmio, 0x20000, &d->vp.mmio);
+
+ memory_region_init_io(&d->gp.mmio, OBJECT(dev), &gp_ops, d,
+ "mcpx-apu-gp", 0x10000);
+ memory_region_add_subregion(&d->mmio, 0x30000, &d->gp.mmio);
+
+ memory_region_init_io(&d->ep.mmio, OBJECT(dev), &ep_ops, d,
+ "mcpx-apu-ep", 0x10000);
+ memory_region_add_subregion(&d->mmio, 0x50000, &d->ep.mmio);
+
+ pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
+}
+
+static void mcpx_apu_exitfn(PCIDevice *dev)
+{
+ MCPXAPUState *d = MCPX_APU_DEVICE(dev);
+ d->exiting = true;
+ qemu_cond_broadcast(&d->cond);
+ qemu_thread_join(&d->apu_thread);
+}
+
+static void mcpx_apu_reset(MCPXAPUState *d)
+{
+ qemu_mutex_lock(&d->lock); // FIXME: Can fail if thread is pegged, add flag
+ memset(d->regs, 0, sizeof(d->regs));
+
+ d->vp.ssl_base_page = 0;
+ d->vp.hrtf_headroom = 0;
+ memset(d->vp.ssl, 0, sizeof(d->vp.ssl));
+ memset(d->vp.hrtf_submix, 0, sizeof(d->vp.hrtf_submix));
+ memset(d->vp.submix_headroom, 0, sizeof(d->vp.submix_headroom));
+ memset(d->vp.voice_locked, 0, sizeof(d->vp.voice_locked));
+
+ // FIXME: Reset DSP state
+ memset(d->gp.dsp->core.pram_opcache, 0,
+ sizeof(d->gp.dsp->core.pram_opcache));
+ memset(d->ep.dsp->core.pram_opcache, 0,
+ sizeof(d->ep.dsp->core.pram_opcache));
+ d->set_irq = false;
+ qemu_cond_signal(&d->cond);
+ qemu_mutex_unlock(&d->lock);
+}
+
+// Note: This is handled as a VM state change and not as a `pre_save` callback
+// because we want to halt the FIFO before any VM state is saved/restored to
+// avoid corruption.
+static void mcpx_apu_vm_state_change(void *opaque, int running, RunState state)
+{
+ MCPXAPUState *d = opaque;
+
+ if (state == RUN_STATE_SAVE_VM) {
+ qemu_mutex_lock(&d->lock);
+ } else if (state == RUN_STATE_RESTORE_VM) {
+ mcpx_apu_reset(d);
+ }
+}
+
+static int mcpx_apu_post_save(void *opaque)
+{
+ MCPXAPUState *d = opaque;
+ qemu_cond_signal(&d->cond);
+ qemu_mutex_unlock(&d->lock);
+ return 0;
+}
+
+static int mcpx_apu_pre_load(void *opaque)
+{
+ MCPXAPUState *d = opaque;
+ qemu_mutex_lock(&d->lock);
+ return 0;
+}
+
+static int mcpx_apu_post_load(void *opaque, int version_id)
+{
+ MCPXAPUState *d = opaque;
+ qemu_cond_signal(&d->cond);
+ qemu_mutex_unlock(&d->lock);
+ return 0;
+}
+
+static void qdev_mcpx_apu_reset(DeviceState *dev)
+{
+ MCPXAPUState *d = MCPX_APU_DEVICE(dev);
+ mcpx_apu_reset(d);
+}
+
+const VMStateDescription vmstate_vp_dsp_dma_state = {
+ .name = "mcpx-apu/dsp-state/dma",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(configuration, DSPDMAState),
+ VMSTATE_UINT32(control, DSPDMAState),
+ VMSTATE_UINT32(start_block, DSPDMAState),
+ VMSTATE_UINT32(next_block, DSPDMAState),
+ VMSTATE_BOOL(error, DSPDMAState),
+ VMSTATE_BOOL(eol, DSPDMAState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_vp_dsp_core_state = {
+ .name = "mcpx-apu/dsp-state/core",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ // FIXME: Remove unnecessary fields
+ VMSTATE_UINT16(instr_cycle, dsp_core_t),
+ VMSTATE_UINT32(pc, dsp_core_t),
+ VMSTATE_UINT32_ARRAY(registers, dsp_core_t, DSP_REG_MAX),
+ VMSTATE_UINT32_2DARRAY(stack, dsp_core_t, 2, 16),
+ VMSTATE_UINT32_ARRAY(xram, dsp_core_t, DSP_XRAM_SIZE),
+ VMSTATE_UINT32_ARRAY(yram, dsp_core_t, DSP_YRAM_SIZE),
+ VMSTATE_UINT32_ARRAY(pram, dsp_core_t, DSP_PRAM_SIZE),
+ VMSTATE_UINT32_ARRAY(mixbuffer, dsp_core_t, DSP_MIXBUFFER_SIZE),
+ VMSTATE_UINT32_ARRAY(periph, dsp_core_t, DSP_PERIPH_SIZE),
+ VMSTATE_UINT32(loop_rep, dsp_core_t),
+ VMSTATE_UINT32(pc_on_rep, dsp_core_t),
+ VMSTATE_UINT16(interrupt_state, dsp_core_t),
+ VMSTATE_UINT16(interrupt_instr_fetch, dsp_core_t),
+ VMSTATE_UINT16(interrupt_save_pc, dsp_core_t),
+ VMSTATE_UINT16(interrupt_counter, dsp_core_t),
+ VMSTATE_UINT16(interrupt_ipl_to_raise, dsp_core_t),
+ VMSTATE_UINT16(interrupt_pipeline_count, dsp_core_t),
+ VMSTATE_INT16_ARRAY(interrupt_ipl, dsp_core_t, 12),
+ VMSTATE_UINT16_ARRAY(interrupt_is_pending, dsp_core_t, 12),
+ VMSTATE_UINT32(num_inst, dsp_core_t),
+ VMSTATE_UINT32(cur_inst_len, dsp_core_t),
+ VMSTATE_UINT32(cur_inst, dsp_core_t),
+ VMSTATE_BOOL(executing_for_disasm, dsp_core_t),
+ VMSTATE_UINT32(disasm_memory_ptr, dsp_core_t),
+ VMSTATE_BOOL(exception_debugging, dsp_core_t),
+ VMSTATE_UINT32(disasm_prev_inst_pc, dsp_core_t),
+ VMSTATE_BOOL(disasm_is_looping, dsp_core_t),
+ VMSTATE_UINT32(disasm_cur_inst, dsp_core_t),
+ VMSTATE_UINT16(disasm_cur_inst_len, dsp_core_t),
+ VMSTATE_UINT32_ARRAY(disasm_registers_save, dsp_core_t, 64),
+// #ifdef DSP_DISASM_REG_PC
+// VMSTATE_UINT32(pc_save, dsp_core_t),
+// #endif
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_vp_dsp_state = {
+ .name = "mcpx-apu/dsp-state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(core, DSPState, 1, vmstate_vp_dsp_core_state, dsp_core_t),
+ VMSTATE_STRUCT(dma, DSPState, 1, vmstate_vp_dsp_dma_state, DSPDMAState),
+ VMSTATE_INT32(save_cycles, DSPState),
+ VMSTATE_UINT32(interrupts, DSPState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
+const VMStateDescription vmstate_vp_ssl_data = {
+ .name = "mcpx_apu_voice_data",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(base, MCPXAPUVPSSLData, MCPX_HW_SSLS_PER_VOICE),
+ VMSTATE_UINT8_ARRAY(count, MCPXAPUVPSSLData, MCPX_HW_SSLS_PER_VOICE),
+ VMSTATE_INT32(ssl_index, MCPXAPUVPSSLData),
+ VMSTATE_INT32(ssl_seg, MCPXAPUVPSSLData),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_mcpx_apu = {
+ .name = "mcpx-apu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_save = mcpx_apu_post_save,
+ .pre_load = mcpx_apu_pre_load,
+ .post_load = mcpx_apu_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, MCPXAPUState),
+ VMSTATE_STRUCT_POINTER(gp.dsp, MCPXAPUState, vmstate_vp_dsp_state,
+ DSPState),
+ VMSTATE_UINT32_ARRAY(gp.regs, MCPXAPUState, 0x10000),
+ VMSTATE_STRUCT_POINTER(ep.dsp, MCPXAPUState, vmstate_vp_dsp_state,
+ DSPState),
+ VMSTATE_UINT32_ARRAY(ep.regs, MCPXAPUState, 0x10000),
+ VMSTATE_UINT32_ARRAY(regs, MCPXAPUState, 0x20000),
+ VMSTATE_UINT32(inbuf_sge_handle, MCPXAPUState),
+ VMSTATE_UINT32(outbuf_sge_handle, MCPXAPUState),
+ VMSTATE_STRUCT_ARRAY(vp.ssl, MCPXAPUState, MCPX_HW_MAX_VOICES, 1,
+ vmstate_vp_ssl_data, MCPXAPUVPSSLData),
+ VMSTATE_INT32(vp.ssl_base_page, MCPXAPUState),
+ VMSTATE_UINT8_ARRAY(vp.hrtf_submix, MCPXAPUState, 4),
+ VMSTATE_UINT8(vp.hrtf_headroom, MCPXAPUState),
+ VMSTATE_UINT8_ARRAY(vp.submix_headroom, MCPXAPUState, NUM_MIXBINS),
+ VMSTATE_UINT64_ARRAY(vp.voice_locked, MCPXAPUState, 4),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void mcpx_apu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->vendor_id = PCI_VENDOR_ID_NVIDIA;
+ k->device_id = PCI_DEVICE_ID_NVIDIA_MCPX_APU;
+ k->revision = 210;
+ k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
+ k->realize = mcpx_apu_realize;
+ k->exit = mcpx_apu_exitfn;
+
+ dc->desc = "MCPX Audio Processing Unit";
+ dc->reset = qdev_mcpx_apu_reset;
+ dc->vmsd = &vmstate_mcpx_apu;
+}
+
+static const TypeInfo mcpx_apu_info = {
+ .name = "mcpx-apu",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(MCPXAPUState),
+ .class_init = mcpx_apu_class_init,
+ .interfaces =
+ (InterfaceInfo[]){
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ {},
+ },
+};
+
+static void mcpx_apu_register(void)
+{
+ type_register_static(&mcpx_apu_info);
+}
+type_init(mcpx_apu_register);
+
+static void *mcpx_apu_frame_thread(void *arg)
+{
+ MCPXAPUState *d = MCPX_APU_DEVICE(arg);
+ qemu_mutex_lock(&d->lock);
+ while (!atomic_read(&d->exiting)) {
+ int xcntmode = GET_MASK(atomic_read(&d->regs[NV_PAPU_SECTL]),
+ NV_PAPU_SECTL_XCNTMODE);
+ uint32_t fectl = atomic_read(&d->regs[NV_PAPU_FECTL]);
+ if (xcntmode == NV_PAPU_SECTL_XCNTMODE_OFF ||
+ (fectl & NV_PAPU_FECTL_FEMETHMODE_TRAPPED) ||
+ (fectl & NV_PAPU_FECTL_FEMETHMODE_HALTED)) {
+ d->set_irq = true;
+ }
+
+ if (d->set_irq) {
+ qemu_mutex_unlock(&d->lock);
+ qemu_mutex_lock_iothread();
+ update_irq(d);
+ qemu_mutex_unlock_iothread();
+ qemu_mutex_lock(&d->lock);
+ d->set_irq = false;
+ }
+
+ xcntmode = GET_MASK(atomic_read(&d->regs[NV_PAPU_SECTL]),
+ NV_PAPU_SECTL_XCNTMODE);
+ fectl = atomic_read(&d->regs[NV_PAPU_FECTL]);
+ if (xcntmode == NV_PAPU_SECTL_XCNTMODE_OFF ||
+ (fectl & NV_PAPU_FECTL_FEMETHMODE_TRAPPED) ||
+ (fectl & NV_PAPU_FECTL_FEMETHMODE_HALTED)) {
+ qemu_cond_wait(&d->cond, &d->lock);
+ continue;
+ }
+ se_frame((void *)d);
+ }
+ qemu_mutex_unlock(&d->lock);
+ return NULL;
+}
+
+void mcpx_apu_init(PCIBus *bus, int devfn, MemoryRegion *ram)
+{
+ PCIDevice *dev = pci_create_simple(bus, devfn, "mcpx-apu");
+ MCPXAPUState *d = MCPX_APU_DEVICE(dev);
+
+ g_state = d;
+
+ d->ram = ram;
+ d->ram_ptr = memory_region_get_ram_ptr(d->ram);
+
+ d->gp.dsp = dsp_init(d, gp_scratch_rw, gp_fifo_rw);
+ for (int i = 0; i < DSP_PRAM_SIZE; i++) {
+ d->gp.dsp->core.pram[i] = 0xCACACACA;
+ }
+ memset(d->gp.dsp->core.pram_opcache, 0,
+ sizeof(d->gp.dsp->core.pram_opcache));
+ d->gp.dsp->is_gp = true;
+ d->gp.dsp->core.is_gp = true;
+ d->gp.dsp->core.is_idle = false;
+ d->gp.dsp->core.cycle_count = 0;
+
+ d->ep.dsp = dsp_init(d, ep_scratch_rw, ep_fifo_rw);
+ for (int i = 0; i < DSP_PRAM_SIZE; i++) {
+ d->ep.dsp->core.pram[i] = 0xCACACACA;
+ }
+ memset(d->ep.dsp->core.pram_opcache, 0,
+ sizeof(d->ep.dsp->core.pram_opcache));
+ for (int i = 0; i < DSP_XRAM_SIZE; i++) {
+ d->ep.dsp->core.xram[i] = 0xCACACACA;
+ }
+ for (int i = 0; i < DSP_YRAM_SIZE; i++) {
+ d->ep.dsp->core.yram[i] = 0xCACACACA;
+ }
+ d->ep.dsp->is_gp = false;
+ d->ep.dsp->core.is_gp = false;
+ d->ep.dsp->core.is_idle = false;
+ d->ep.dsp->core.cycle_count = 0;
+
+ d->set_irq = false;
+ d->exiting = false;
+
+ struct SDL_AudioSpec sdl_audio_spec = {
+ .freq = 48000,
+ .format = AUDIO_S16LSB,
+ .channels = 2,
+ .samples = 512,
+ .callback = mcpx_vp_out_cb,
+ .userdata = d,
+ };
+
+ assert(SDL_Init(SDL_INIT_AUDIO) >= 0);
+
+ SDL_AudioDeviceID sdl_audio_dev;
+ sdl_audio_dev = SDL_OpenAudioDevice(NULL, 0, &sdl_audio_spec, NULL, 0);
+ if (sdl_audio_dev == 0) {
+ fprintf(stderr, "SDL_OpenAudioDevice failed\n");
+ exit(1);
+ }
+ SDL_PauseAudioDevice(sdl_audio_dev, 0);
+
+ qemu_spin_init(&d->vp.out_buf_lock);
+ for (int i = 0; i < MCPX_HW_MAX_VOICES; i++) {
+ qemu_spin_init(&d->vp.voice_spinlocks[i]);
+ }
+ fifo8_create(&d->vp.out_buf, 3 * (256 * 2 * 2));
+
+ qemu_mutex_init(&d->lock);
+ qemu_cond_init(&d->cond);
+ qemu_add_vm_change_state_handler(mcpx_apu_vm_state_change, d);
+
+ /* Until DSP is more performant, a switch to decide whether or not we should
+ * use the full audio pipeline or not.
+ */
+ int use_dsp;
+ xemu_settings_get_bool(XEMU_SETTINGS_AUDIO_USE_DSP, &use_dsp);
+ if (use_dsp) {
+ d->mon = MCPX_APU_DEBUG_MON_GP_OR_EP;
+ d->gp.realtime = true;
+ d->ep.realtime = true;
+ } else {
+ d->mon = MCPX_APU_DEBUG_MON_VP;
+ d->gp.realtime = false;
+ d->ep.realtime = false;
+ }
+
+ qemu_thread_create(&d->apu_thread, "mcpx.apu_thread", mcpx_apu_frame_thread,
+ d, QEMU_THREAD_JOINABLE);
+}
diff --git a/hw/xbox/mcpx_apu.h b/hw/xbox/mcpx/apu.h
similarity index 83%
rename from hw/xbox/mcpx_apu.h
rename to hw/xbox/mcpx/apu.h
index 6c6fc484b3..853aa9adec 100644
--- a/hw/xbox/mcpx_apu.h
+++ b/hw/xbox/mcpx/apu.h
@@ -1,7 +1,9 @@
/*
- * QEMU Geforce NV2A implementation
+ * QEMU MCPX Audio Processing Unit implementation
*
- * Copyright (c) 2018 Jannik Vogel
+ * Copyright (c) 2012 espes
+ * Copyright (c) 2018-2019 Jannik Vogel
+ * Copyright (c) 2019-2021 Matt Borgerson
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
diff --git a/hw/xbox/mcpx/apu_debug.h b/hw/xbox/mcpx/apu_debug.h
new file mode 100644
index 0000000000..8c99718d62
--- /dev/null
+++ b/hw/xbox/mcpx/apu_debug.h
@@ -0,0 +1,90 @@
+/*
+ * QEMU MCPX Audio Processing Unit implementation
+ *
+ * Copyright (c) 2020-2021 Matt Borgerson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+
+#ifndef MCPX_APU_DEBUG_H
+#define MCPX_APU_DEBUG_H
+
+#include
+#include
+
+enum McpxApuDebugMon {
+ MCPX_APU_DEBUG_MON_AC97,
+ MCPX_APU_DEBUG_MON_VP,
+ MCPX_APU_DEBUG_MON_GP,
+ MCPX_APU_DEBUG_MON_EP,
+ MCPX_APU_DEBUG_MON_GP_OR_EP
+};
+
+struct McpxApuDebugVoice
+{
+ bool active;
+ bool paused;
+ bool stereo;
+ uint8_t bin[8];
+ uint16_t vol[8];
+
+ bool stream;
+ bool loop;
+ bool persist;
+ bool multipass;
+ bool linked;
+ int container_size, sample_size;
+ unsigned int samples_per_block;
+ uint32_t ebo, cbo, lbo, ba;
+ float rate;
+};
+
+struct McpxApuDebugVp
+{
+ struct McpxApuDebugVoice v[256];
+};
+
+struct McpxApuDebugDsp
+{
+ int cycles;
+};
+
+struct McpxApuDebug
+{
+ struct McpxApuDebugVp vp;
+ struct McpxApuDebugDsp gp, ep;
+ int frames_processed;
+ float utilization;
+ bool gp_realtime, ep_realtime;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const struct McpxApuDebug *mcpx_apu_get_debug_info(void);
+int mcpx_apu_debug_get_monitor(void);
+void mcpx_apu_debug_set_monitor(int mon);
+void mcpx_apu_debug_isolate_voice(uint16_t v);
+void mcpx_apu_debug_clear_isolations(void);
+void mcpx_apu_debug_toggle_mute(uint16_t v);
+bool mcpx_apu_debug_is_muted(uint16_t v);
+void mcpx_apu_debug_set_gp_realtime_enabled(bool enable);
+void mcpx_apu_debug_set_ep_realtime_enabled(bool enable);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/hw/xbox/mcpx/apu_regs.h b/hw/xbox/mcpx/apu_regs.h
new file mode 100644
index 0000000000..7488c2e267
--- /dev/null
+++ b/hw/xbox/mcpx/apu_regs.h
@@ -0,0 +1,346 @@
+/*
+ * QEMU MCPX Audio Processing Unit implementation
+ *
+ * Copyright (c) 2012 espes
+ * Copyright (c) 2018-2019 Jannik Vogel
+ * Copyright (c) 2019-2021 Matt Borgerson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+
+#ifndef MCPX_APU_REGS_H
+#define MCPX_APU_REGS_H
+
+#define NV_PAPU_ISTS 0x00001000
+# define NV_PAPU_ISTS_GINTSTS (1 << 0)
+# define NV_PAPU_ISTS_FETINTSTS (1 << 4)
+# define NV_PAPU_ISTS_FENINTSTS (1 << 5)
+# define NV_PAPU_ISTS_FEVINTSTS (1 << 6)
+#define NV_PAPU_IEN 0x00001004
+#define NV_PAPU_FECTL 0x00001100
+# define NV_PAPU_FECTL_FEMETHMODE 0x000000E0
+# define NV_PAPU_FECTL_FEMETHMODE_FREE_RUNNING 0x00000000
+# define NV_PAPU_FECTL_FEMETHMODE_HALTED 0x00000080
+# define NV_PAPU_FECTL_FEMETHMODE_TRAPPED 0x000000E0
+# define NV_PAPU_FECTL_FETRAPREASON 0x00000F00
+# define NV_PAPU_FECTL_FETRAPREASON_REQUESTED 0x00000F00
+#define NV_PAPU_FECV 0x00001110
+#define NV_PAPU_FEAV 0x00001118
+# define NV_PAPU_FEAV_VALUE 0x0000FFFF
+# define NV_PAPU_FEAV_LST 0x00030000
+
+#define NV_PAPU_FENADDR 0x0000115c
+
+#define NV_PAPU_FEDECMETH 0x00001300
+#define NV_PAPU_FEDECPARAM 0x00001304
+#define NV_PAPU_FEMEMADDR 0x00001324
+#define NV_PAPU_FEMEMDATA 0x00001334
+#define NV_PAPU_FETFORCE0 0x00001500
+#define NV_PAPU_FETFORCE1 0x00001504
+# define NV_PAPU_FETFORCE1_SE2FE_IDLE_VOICE (1 << 15)
+#define NV_PAPU_SECTL 0x00002000
+# define NV_PAPU_SECTL_XCNTMODE 0x00000018
+# define NV_PAPU_SECTL_XCNTMODE_OFF 0
+#define NV_PAPU_XGSCNT 0x0000200C
+#define NV_PAPU_VPVADDR 0x0000202C
+#define NV_PAPU_VPSGEADDR 0x00002030
+#define NV_PAPU_VPSSLADDR 0x00002034
+#define NV_PAPU_GPSADDR 0x00002040
+#define NV_PAPU_GPFADDR 0x00002044
+#define NV_PAPU_EPSADDR 0x00002048
+#define NV_PAPU_EPFADDR 0x0000204C
+#define NV_PAPU_TVL2D 0x00002054
+#define NV_PAPU_CVL2D 0x00002058
+#define NV_PAPU_NVL2D 0x0000205C
+#define NV_PAPU_TVL3D 0x00002060
+#define NV_PAPU_CVL3D 0x00002064
+#define NV_PAPU_NVL3D 0x00002068
+#define NV_PAPU_TVLMP 0x0000206C
+#define NV_PAPU_CVLMP 0x00002070
+#define NV_PAPU_NVLMP 0x00002074
+#define NV_PAPU_GPSMAXSGE 0x000020D4
+#define NV_PAPU_GPFMAXSGE 0x000020D8
+#define NV_PAPU_EPSMAXSGE 0x000020DC
+#define NV_PAPU_EPFMAXSGE 0x000020E0
+
+/* Each FIFO has the same fields */
+#define NV_PAPU_GPOFBASE0 0x00003024
+# define NV_PAPU_GPOFBASE0_VALUE 0x00FFFFFF // FIXME: Use ffff00 mask but shift appropriately
+#define NV_PAPU_GPOFEND0 0x00003028
+# define NV_PAPU_GPOFEND0_VALUE 0x00FFFFFF
+#define NV_PAPU_GPOFCUR0 0x0000302C
+# define NV_PAPU_GPOFCUR0_VALUE 0x00FFFFFF
+#define NV_PAPU_GPOFBASE1 0x00003034
+#define NV_PAPU_GPOFEND1 0x00003038
+#define NV_PAPU_GPOFCUR1 0x0000303C
+#define NV_PAPU_GPOFBASE2 0x00003044
+#define NV_PAPU_GPOFEND2 0x00003048
+#define NV_PAPU_GPOFCUR2 0x0000304C
+#define NV_PAPU_GPOFBASE3 0x00003054
+#define NV_PAPU_GPOFEND3 0x00003058
+#define NV_PAPU_GPOFCUR3 0x0000305C
+
+/* Fields are same as for the 4 output FIFOs, but only 2 input FIFOs */
+#define NV_PAPU_GPIFBASE0 0x00003064
+#define NV_PAPU_GPIFEND0 0x00003068
+#define NV_PAPU_GPIFCUR0 0x0000306C
+#define NV_PAPU_GPIFBASE1 0x00003074
+#define NV_PAPU_GPIFEND1 0x00003078
+#define NV_PAPU_GPIFCUR1 0x0000307C
+
+/* Fields, strides and count is same as for GP FIFOs */
+#define NV_PAPU_EPOFBASE0 0x00004024
+#define NV_PAPU_EPOFEND0 0x00004028
+#define NV_PAPU_EPOFCUR0 0x0000402C
+#define NV_PAPU_EPIFBASE0 0x00004064
+#define NV_PAPU_EPIFEND0 0x00004068
+#define NV_PAPU_EPIFCUR0 0x0000406C
+
+#define NV_PAPU_GPXMEM 0x00000000
+#define NV_PAPU_GPMIXBUF 0x00005000
+#define NV_PAPU_GPYMEM 0x00006000
+#define NV_PAPU_GPPMEM 0x0000A000
+#define NV_PAPU_GPRST 0x0000FFFC
+#define NV_PAPU_GPRST_GPRST (1 << 0)
+#define NV_PAPU_GPRST_GPDSPRST (1 << 1)
+#define NV_PAPU_GPRST_GPNMI (1 << 2)
+#define NV_PAPU_GPRST_GPABORT (1 << 3)
+
+#define NV_PAPU_EPXMEM 0x00000000
+#define NV_PAPU_EPYMEM 0x00006000
+#define NV_PAPU_EPPMEM 0x0000A000
+#define NV_PAPU_EPRST 0x0000FFFC
+
+static const struct {
+ hwaddr top, current, next;
+} voice_list_regs[] = {
+ {NV_PAPU_TVL2D, NV_PAPU_CVL2D, NV_PAPU_NVL2D}, //2D
+ {NV_PAPU_TVL3D, NV_PAPU_CVL3D, NV_PAPU_NVL3D}, //3D
+ {NV_PAPU_TVLMP, NV_PAPU_CVLMP, NV_PAPU_NVLMP}, //MP
+};
+
+
+/* audio processor object / front-end messages */
+#define NV1BA0_PIO_FREE 0x00000010
+#define NV1BA0_PIO_SET_ANTECEDENT_VOICE 0x00000120
+# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_HANDLE 0x0000FFFF
+# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST 0x00030000
+# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_INHERIT 0
+# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_2D_TOP 1
+# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_3D_TOP 2
+# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_MP_TOP 3
+#define NV1BA0_PIO_VOICE_ON 0x00000124
+# define NV1BA0_PIO_VOICE_ON_HANDLE 0x0000FFFF
+# define NV1BA0_PIO_VOICE_ON_ENVF 0x0F000000
+# define NV1BA0_PIO_VOICE_ON_ENVA 0xF0000000
+#define NV1BA0_PIO_VOICE_OFF 0x00000128
+# define NV1BA0_PIO_VOICE_OFF_HANDLE 0x0000FFFF
+#define NV1BA0_PIO_VOICE_RELEASE 0x0000012C
+#define NV1BA0_PIO_GET_VOICE_POSITION 0x00000130
+# define NV1BA0_PIO_VOICE_RELEASE_HANDLE 0x0000FFFF
+#define NV1BA0_PIO_VOICE_PAUSE 0x00000140
+# define NV1BA0_PIO_VOICE_PAUSE_HANDLE 0x0000FFFF
+# define NV1BA0_PIO_VOICE_PAUSE_ACTION (1 << 18)
+#define NV1BA0_PIO_SET_CONTEXT_DMA_NOTIFY 0x00000180
+#define NV1BA0_PIO_SET_CURRENT_SSL_CONTEXT_DMA 0x0000018C
+#define NV1BA0_PIO_SET_CURRENT_SSL 0x00000190
+# define NV1BA0_PIO_SET_CURRENT_SSL_BASE_PAGE 0x3FFFC0
+#define NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET 0x00000600
+#define NV1BA0_PIO_SET_SSL_SEGMENT_LENGTH 0x00000604
+#define NV1BA0_PIO_SET_SUBMIX_HEADROOM 0x00000200
+# define NV1BA0_PIO_SET_SUBMIX_HEADROOM_AMOUNT 0x7
+#define NV1BA0_PIO_SET_HRTF_HEADROOM 0x00000280
+# define NV1BA0_PIO_SET_HRTF_HEADROOM_AMOUNT 0x7
+#define NV1BA0_PIO_SET_HRTF_SUBMIXES 0x000002C0
+#define NV1BA0_PIO_SET_CURRENT_VOICE 0x000002F8
+#define NV1BA0_PIO_VOICE_LOCK 0x000002FC
+#define NV1BA0_PIO_SET_VOICE_CFG_VBIN 0x00000300
+#define NV1BA0_PIO_SET_VOICE_CFG_FMT 0x00000304
+#define NV1BA0_PIO_SET_VOICE_CFG_ENV0 0x00000308
+#define NV1BA0_PIO_SET_VOICE_CFG_ENVA 0x0000030C
+#define NV1BA0_PIO_SET_VOICE_CFG_ENV1 0x00000310
+#define NV1BA0_PIO_SET_VOICE_CFG_ENVF 0x00000314
+#define NV1BA0_PIO_SET_VOICE_CFG_MISC 0x00000318
+#define NV1BA0_PIO_SET_VOICE_SSL_A 0x00000320
+# define NV1BA0_PIO_SET_VOICE_SSL_A_COUNT 0x000000FF
+# define NV1BA0_PIO_SET_VOICE_SSL_A_BASE 0xFFFFFF00
+#define NV1BA0_PIO_SET_VOICE_SSL_B 0x0000035C
+#define NV1BA0_PIO_SET_VOICE_TAR_VOLA 0x00000360
+#define NV1BA0_PIO_SET_VOICE_TAR_VOLB 0x00000364
+#define NV1BA0_PIO_SET_VOICE_TAR_VOLC 0x00000368
+#define NV1BA0_PIO_SET_VOICE_LFO_ENV 0x0000036C
+#define NV1BA0_PIO_SET_VOICE_TAR_FCA 0x00000374
+#define NV1BA0_PIO_SET_VOICE_TAR_FCB 0x00000378
+#define NV1BA0_PIO_SET_VOICE_TAR_PITCH 0x0000037C
+# define NV1BA0_PIO_SET_VOICE_TAR_PITCH_STEP 0xFFFF0000
+#define NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE 0x000003A0
+# define NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE_OFFSET 0x00FFFFFF
+#define NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO 0x000003A4
+# define NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO_OFFSET 0x00FFFFFF
+#define NV1BA0_PIO_SET_VOICE_BUF_CBO 0x000003D8
+# define NV1BA0_PIO_SET_VOICE_BUF_CBO_OFFSET 0x00FFFFFF
+#define NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO 0x000003DC
+# define NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO_OFFSET 0x00FFFFFF
+#define NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET 0x00000600
+#define NV1BA0_PIO_SET_SSL_SEGMENT_LENGTH 0x00000604
+#define NV1BA0_PIO_SET_CURRENT_INBUF_SGE 0x00000804
+# define NV1BA0_PIO_SET_CURRENT_INBUF_SGE_HANDLE 0xFFFFFFFF
+#define NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET 0x00000808
+# define NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER 0xFFFFF000
+#define NV1BA0_PIO_SET_OUTBUF_BA 0x00001000 // 8 byte pitch, 4 entries
+# define NV1BA0_PIO_SET_OUTBUF_BA_ADDRESS 0x007FFF00
+#define NV1BA0_PIO_SET_OUTBUF_LEN 0x00001004 // 8 byte pitch, 4 entries
+# define NV1BA0_PIO_SET_OUTBUF_LEN_VALUE 0x007FFF00
+#define NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE 0x00001800
+# define NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_HANDLE 0xFFFFFFFF
+#define NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET 0x00001808
+# define NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER 0xFFFFF000
+
+#define SE2FE_IDLE_VOICE 0x00008000
+
+
+/* voice structure */
+#define NV_PAVS_SIZE 0x00000080
+#define NV_PAVS_VOICE_CFG_VBIN 0x00000000
+# define NV_PAVS_VOICE_CFG_VBIN_V0BIN (0x1F << 0)
+# define NV_PAVS_VOICE_CFG_VBIN_V1BIN (0x1F << 5)
+# define NV_PAVS_VOICE_CFG_VBIN_V2BIN (0x1F << 10)
+# define NV_PAVS_VOICE_CFG_VBIN_V3BIN (0x1F << 16)
+# define NV_PAVS_VOICE_CFG_VBIN_V4BIN (0x1F << 21)
+# define NV_PAVS_VOICE_CFG_VBIN_V5BIN (0x1F << 26)
+#define NV_PAVS_VOICE_CFG_FMT 0x00000004
+# define NV_PAVS_VOICE_CFG_FMT_V6BIN (0x1F << 0)
+# define NV_PAVS_VOICE_CFG_FMT_V7BIN (0x1F << 5)
+# define NV_PAVS_VOICE_CFG_FMT_HEADROOM (0x7 << 13)
+# define NV_PAVS_VOICE_CFG_FMT_SAMPLES_PER_BLOCK (0x1F << 16)
+# define NV_PAVS_VOICE_CFG_FMT_MULTIPASS_BIN (0x1F << 16)
+# define NV_PAVS_VOICE_CFG_FMT_MULTIPASS (1 << 21)
+# define NV_PAVS_VOICE_CFG_FMT_LINKED (1 << 22)
+# define NV_PAVS_VOICE_CFG_FMT_PERSIST (1 << 23)
+# define NV_PAVS_VOICE_CFG_FMT_DATA_TYPE (1 << 24)
+# define NV_PAVS_VOICE_CFG_FMT_LOOP (1 << 25)
+# define NV_PAVS_VOICE_CFG_FMT_CLEAR_MIX (1 << 26)
+# define NV_PAVS_VOICE_CFG_FMT_STEREO (1 << 27)
+# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE (0x3 << 28)
+# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_U8 0
+# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S16 1
+# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24 2
+# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S32 3
+# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE (0x3 << 30)
+# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_B8 0
+# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_B16 1
+# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_ADPCM 2
+# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_B32 3
+#define NV_PAVS_VOICE_CFG_ENV0 0x00000008
+# define NV_PAVS_VOICE_CFG_ENV0_EA_ATTACKRATE (0xFFF << 0)
+# define NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME (0xFFF << 12)
+# define NV_PAVS_VOICE_CFG_ENV0_EF_PITCHSCALE (0xFF << 24)
+#define NV_PAVS_VOICE_CFG_ENVA 0x0000000C
+# define NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE (0xFFF << 0)
+# define NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME (0xFFF << 12)
+# define NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL (0xFF << 24)
+#define NV_PAVS_VOICE_CFG_ENV1 0x00000010
+# define NV_PAVS_VOICE_CFG_ENV1_EF_FCSCALE (0xFF << 24)
+#define NV_PAVS_VOICE_CFG_ENVF 0x00000014
+#define NV_PAVS_VOICE_CFG_MISC 0x00000018
+# define NV_PAVS_VOICE_CFG_MISC_EF_RELEASERATE (0xFFF << 0)
+# define NV_PAVS_VOICE_CFG_MISC_FMODE (3 << 16)
+#define NV_PAVS_VOICE_CUR_PSL_START 0x00000020
+# define NV_PAVS_VOICE_CUR_PSL_START_BA 0x00FFFFFF
+#define NV_PAVS_VOICE_CUR_PSH_SAMPLE 0x00000024
+# define NV_PAVS_VOICE_CUR_PSH_SAMPLE_LBO 0x00FFFFFF
+
+#define NV_PAVS_VOICE_CUR_ECNT 0x00000034
+# define NV_PAVS_VOICE_CUR_ECNT_EACOUNT 0x0000FFFF
+# define NV_PAVS_VOICE_CUR_ECNT_EFCOUNT 0xFFFF0000
+
+#define NV_PAVS_VOICE_PAR_STATE 0x00000054
+# define NV_PAVS_VOICE_PAR_STATE_PAUSED (1 << 18)
+# define NV_PAVS_VOICE_PAR_STATE_NEW_VOICE (1 << 20)
+# define NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE (1 << 21)
+# define NV_PAVS_VOICE_PAR_STATE_EFCUR (0xF << 24)
+# define NV_PAVS_VOICE_PAR_STATE_EFCUR_OFF 0
+# define NV_PAVS_VOICE_PAR_STATE_EFCUR_DELAY 1
+# define NV_PAVS_VOICE_PAR_STATE_EFCUR_ATTACK 2
+# define NV_PAVS_VOICE_PAR_STATE_EFCUR_HOLD 3
+# define NV_PAVS_VOICE_PAR_STATE_EFCUR_DECAY 4
+# define NV_PAVS_VOICE_PAR_STATE_EFCUR_SUSTAIN 5
+# define NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE 6
+# define NV_PAVS_VOICE_PAR_STATE_EFCUR_FORCE_RELEASE 7
+# define NV_PAVS_VOICE_PAR_STATE_EACUR (0xF << 28)
+#define NV_PAVS_VOICE_PAR_OFFSET 0x00000058
+# define NV_PAVS_VOICE_PAR_OFFSET_CBO 0x00FFFFFF
+# define NV_PAVS_VOICE_PAR_OFFSET_EALVL 0xFF000000
+#define NV_PAVS_VOICE_PAR_NEXT 0x0000005C
+# define NV_PAVS_VOICE_PAR_NEXT_EBO 0x00FFFFFF
+# define NV_PAVS_VOICE_PAR_NEXT_EFLVL 0xFF000000
+#define NV_PAVS_VOICE_TAR_VOLA 0x00000060
+# define NV_PAVS_VOICE_TAR_VOLA_VOLUME6_B3_0 0x0000000F
+# define NV_PAVS_VOICE_TAR_VOLA_VOLUME0 0x0000FFF0
+# define NV_PAVS_VOICE_TAR_VOLA_VOLUME7_B3_0 0x000F0000
+# define NV_PAVS_VOICE_TAR_VOLA_VOLUME1 0xFFF00000
+#define NV_PAVS_VOICE_TAR_VOLB 0x00000064
+# define NV_PAVS_VOICE_TAR_VOLB_VOLUME6_B7_4 0x0000000F
+# define NV_PAVS_VOICE_TAR_VOLB_VOLUME2 0x0000FFF0
+# define NV_PAVS_VOICE_TAR_VOLB_VOLUME7_B7_4 0x000F0000
+# define NV_PAVS_VOICE_TAR_VOLB_VOLUME3 0xFFF00000
+#define NV_PAVS_VOICE_TAR_VOLC 0x00000068
+# define NV_PAVS_VOICE_TAR_VOLC_VOLUME6_B11_8 0x0000000F
+# define NV_PAVS_VOICE_TAR_VOLC_VOLUME4 0x0000FFF0
+# define NV_PAVS_VOICE_TAR_VOLC_VOLUME7_B11_8 0x000F0000
+# define NV_PAVS_VOICE_TAR_VOLC_VOLUME5 0xFFF00000
+#define NV_PAVS_VOICE_TAR_LFO_ENV 0x0000006C
+# define NV_PAVS_VOICE_TAR_LFO_ENV_EA_RELEASERATE (0xFFF << 0)
+#define NV_PAVS_VOICE_TAR_FCA 0x00000074
+# define NV_PAVS_VOICE_TAR_FCA_FC0 0x0000FFFF
+# define NV_PAVS_VOICE_TAR_FCA_FC1 0xFFFF0000
+#define NV_PAVS_VOICE_TAR_FCB 0x00000078
+#define NV_PAVS_VOICE_TAR_PITCH_LINK 0x0000007C
+# define NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE 0x0000FFFF
+# define NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH 0xFFFF0000
+
+
+#define GP_DSP_MIXBUF_BASE 0x001400
+
+#define GP_OUTPUT_FIFO_COUNT 4
+#define GP_INPUT_FIFO_COUNT 2
+
+#define EP_OUTPUT_FIFO_COUNT 4
+#define EP_INPUT_FIFO_COUNT 2
+
+#define MCPX_HW_MAX_VOICES 256
+
+#define NUM_SAMPLES_PER_FRAME 32
+#define NUM_MIXBINS 32
+
+#define ADPCM_SAMPLES_PER_BLOCK 64 // FIXME: Should be 65? Check interpolation
+
+#define MCPX_HW_MAX_PRD_ENTRIES_PER_SSL 16
+#define MCPX_HW_SSLS_PER_VOICE 2
+#define MCPX_HW_MAX_PRD_ENTRIES_PER_VOICE (MCPX_HW_MAX_PRD_ENTRIES_PER_SSL * MCPX_HW_SSLS_PER_VOICE)
+#define MCPX_HW_MAX_SSL_PRDS (MCPX_HW_MAX_VOICES * MCPX_HW_MAX_PRD_ENTRIES_PER_VOICE)
+
+#define NV_PSGE_SIZE 0x00000008
+
+#define MCPX_HW_NOTIFIER_BASE_OFFSET 2
+enum MCPX_HW_NOTIFIER {
+ MCPX_HW_NOTIFIER_SSLA_DONE = 0,
+ MCPX_HW_NOTIFIER_SSLB_DONE,
+ // ...
+ MCPX_HW_NOTIFIER_COUNT = 4,
+};
+#define NV1BA0_NOTIFICATION_STATUS_DONE_SUCCESS 0x01
+#define NV1BA0_NOTIFICATION_STATUS_IN_PROGRESS 0x80
+
+#endif
diff --git a/hw/xbox/dsp/Makefile.objs b/hw/xbox/mcpx/dsp/Makefile.objs
similarity index 100%
rename from hw/xbox/dsp/Makefile.objs
rename to hw/xbox/mcpx/dsp/Makefile.objs
diff --git a/hw/xbox/dsp/dsp.c b/hw/xbox/mcpx/dsp/dsp.c
similarity index 89%
rename from hw/xbox/dsp/dsp.c
rename to hw/xbox/mcpx/dsp/dsp.c
index 999ab80efd..20f01a85a2 100644
--- a/hw/xbox/dsp/dsp.c
+++ b/hw/xbox/mcpx/dsp/dsp.c
@@ -44,7 +44,15 @@
#define INTERRUPT_START_FRAME (1 << 1)
#define INTERRUPT_DMA_EOL (1 << 7)
-#define DPRINTF(s, ...) printf(s, ## __VA_ARGS__)
+// #define DEBUG_DSP
+
+#ifdef DEBUG_DSP
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+ do { } while (0)
+#endif
static uint32_t read_peripheral(dsp_core_t* core, uint32_t address);
static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value);
@@ -85,10 +93,13 @@ void dsp_destroy(DSPState* dsp)
static uint32_t read_peripheral(dsp_core_t* core, uint32_t address) {
DSPState* dsp = container_of(core, DSPState, core);
- // printf("read_peripheral 0x%06x\n", address);
+ DPRINTF("read_peripheral 0x%06x", address);
uint32_t v = 0xababa;
switch(address) {
+ case 0xFFFFB3:
+ v = 0; // core->num_inst; // ??
+ break;
case 0xFFFFC5:
v = dsp->interrupts;
if (dsp->dma.eol) {
@@ -109,16 +120,22 @@ static uint32_t read_peripheral(dsp_core_t* core, uint32_t address) {
break;
}
- // printf(" -> 0x%06x\n", v);
+ DPRINTF(" -> 0x%06x\n", v);
return v;
}
static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value) {
DSPState* dsp = container_of(core, DSPState, core);
- // printf("write_peripheral [0x%06x] = 0x%06x\n", address, value);
+ DPRINTF("write_peripheral [0x%06x] = 0x%06x\n", address, value);
switch(address) {
+ case 0xFFFFC4:
+ if (value & 1) {
+ core->is_idle = true;
+ break;
+ }
+ break;
case 0xFFFFC5:
dsp->interrupts &= ~value;
if (value & INTERRUPT_DMA_EOL) {
@@ -137,6 +154,8 @@ static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value)
case 0xFFFFD7:
dsp_dma_write(&dsp->dma, DMA_CONFIGURATION, value);
break;
+ default:
+ break;
}
}
@@ -152,28 +171,48 @@ void dsp_run(DSPState* dsp, int cycles)
if (dsp->save_cycles <= 0) return;
- // if (unlikely(bDspDebugging)) {
- // while (dsp->core.save_cycles > 0)
- // {
- // dsp56k_execute_instruction();
- // dsp->core.save_cycles -= dsp->core.instr_cycle;
- // DebugDsp_Check();
- // }
- // } else {
- // printf("--> %d\n", dsp->core.save_cycles);
+ int count = 0;
+ int dma_timer = 0;
+
while (dsp->save_cycles > 0)
{
dsp56k_execute_instruction(&dsp->core);
dsp->save_cycles -= dsp->core.instr_cycle;
+ dsp->core.cycle_count++;
+ count++;
+
+ if (dsp->dma.control & DMA_CONTROL_RUNNING) {
+ dma_timer++;
+ }
+
+ if (dma_timer > 2) {
+ dma_timer = 0;
+ dsp->dma.control &= ~DMA_CONTROL_RUNNING;
+ dsp->dma.control |= DMA_CONTROL_STOPPED;
+ }
+
+ if (dsp->core.is_idle) break;
}
-}
+ /* FIXME: DMA timing be done cleaner. Xbox enables running
+ * then polls to make sure its running. But we complete DMA instantaneously,
+ * so when is it supposed to be signaled that it stopped? Maybe just wait at
+ * least one cycle? How long does hardware wait?
+ */
+}
void dsp_bootstrap(DSPState* dsp)
{
// scratch memory is dma'd in to pram by the bootrom
dsp->dma.scratch_rw(dsp->dma.rw_opaque,
(uint8_t*)dsp->core.pram, 0, 0x800*4, false);
+ for (int i = 0; i < 0x800; i++) {
+ if (dsp->core.pram[i] & 0xff000000) {
+ DPRINTF(stderr, "Bootstrap %04x: %08x\n", i, dsp->core.pram[i]);
+ dsp->core.pram[i] &= 0x00ffffff;
+ }
+ }
+ memset(dsp->core.pram_opcache, 0, sizeof(dsp->core.pram_opcache));
}
void dsp_start_frame(DSPState* dsp)
@@ -294,12 +333,12 @@ void dsp_print_registers(DSPState* dsp)
dsp->core.registers[DSP_REG_A2], dsp->core.registers[DSP_REG_A1], dsp->core.registers[DSP_REG_A0]);
printf("B: B2: %02x B1: %06x B0: %06x\n",
dsp->core.registers[DSP_REG_B2], dsp->core.registers[DSP_REG_B1], dsp->core.registers[DSP_REG_B0]);
-
+
printf("X: X1: %06x X0: %06x\n", dsp->core.registers[DSP_REG_X1], dsp->core.registers[DSP_REG_X0]);
printf("Y: Y1: %06x Y0: %06x\n", dsp->core.registers[DSP_REG_Y1], dsp->core.registers[DSP_REG_Y0]);
for (i=0; i<8; i++) {
- printf("R%01x: %04x N%01x: %04x M%01x: %04x\n",
+ printf("R%01x: %04x N%01x: %04x M%01x: %04x\n",
i, dsp->core.registers[DSP_REG_R0+i],
i, dsp->core.registers[DSP_REG_N0+i],
i, dsp->core.registers[DSP_REG_M0+i]);
@@ -307,7 +346,7 @@ void dsp_print_registers(DSPState* dsp)
printf("LA: %04x LC: %04x PC: %04x\n", dsp->core.registers[DSP_REG_LA], dsp->core.registers[DSP_REG_LC], dsp->core.pc);
printf("SR: %04x OMR: %02x\n", dsp->core.registers[DSP_REG_SR], dsp->core.registers[DSP_REG_OMR]);
- printf("SP: %02x SSH: %04x SSL: %04x\n",
+ printf("SP: %02x SSH: %04x SSL: %04x\n",
dsp->core.registers[DSP_REG_SP], dsp->core.registers[DSP_REG_SSH], dsp->core.registers[DSP_REG_SSL]);
}
@@ -328,7 +367,7 @@ int dsp_get_register_address(DSPState* dsp, const char *regname, uint32_t **addr
size_t bits;
uint32_t mask;
} reg_addr_t;
-
+
/* sorted by name so that this can be bisected */
const reg_addr_t registers[] = {
@@ -409,7 +448,7 @@ int dsp_get_register_address(DSPState* dsp, const char *regname, uint32_t **addr
return 0;
}
len = i;
-
+
/* bisect */
l = 0;
r = ARRAYSIZE(registers) - 1;
@@ -449,7 +488,7 @@ bool dsp_disasm_set_register(DSPState* dsp, const char *arg, uint32_t value)
if (arg[0]=='S' || arg[0]=='s') {
if (arg[1]=='P' || arg[1]=='p') {
dsp->core.registers[DSP_REG_SP] = value & BITMASK(6);
- value &= BITMASK(4);
+ value &= BITMASK(4);
dsp->core.registers[DSP_REG_SSH] = dsp->core.stack[0][value];
dsp->core.registers[DSP_REG_SSL] = dsp->core.stack[1][value];
return true;
diff --git a/hw/xbox/dsp/dsp.h b/hw/xbox/mcpx/dsp/dsp.h
similarity index 100%
rename from hw/xbox/dsp/dsp.h
rename to hw/xbox/mcpx/dsp/dsp.h
diff --git a/hw/xbox/dsp/dsp_cpu.c b/hw/xbox/mcpx/dsp/dsp_cpu.c
similarity index 95%
rename from hw/xbox/dsp/dsp_cpu.c
rename to hw/xbox/mcpx/dsp/dsp_cpu.c
index cda605fd3c..b54ea82b90 100644
--- a/hw/xbox/dsp/dsp_cpu.c
+++ b/hw/xbox/mcpx/dsp/dsp_cpu.c
@@ -30,6 +30,7 @@
#include "qemu/bswap.h"
#include "dsp_cpu.h"
+
#define TRACE_DSP_DISASM 0
#define TRACE_DSP_DISASM_REG 0
#define TRACE_DSP_DISASM_MEM 0
@@ -62,7 +63,7 @@ static uint32_t read_memory_disasm(dsp_core_t* dsp, int space, uint32_t address)
static void write_memory_raw(dsp_core_t* dsp, int space, uint32_t address, uint32_t value);
static void write_memory_disasm(dsp_core_t* dsp, int space, uint32_t address, uint32_t value);
-static void dsp_write_reg(dsp_core_t* dsp, uint32_t numreg, uint32_t value);
+static void dsp_write_reg(dsp_core_t* dsp, uint32_t numreg, uint32_t value);
static void dsp_stack_push(dsp_core_t* dsp, uint32_t curpc, uint32_t cursr, uint16_t sshOnly);
static void dsp_stack_pop(dsp_core_t* dsp, uint32_t *curpc, uint32_t *cursr);
@@ -120,12 +121,12 @@ static const int registers_mask[64] = {
24, 24, 24, 24,
24, 24, 8, 8,
24, 24, 24, 24,
-
+
16, 16, 16, 16,
16, 16, 16, 16,
16, 16, 16, 16,
16, 16, 16, 16,
-
+
16, 16, 16, 16,
16, 16, 16, 16,
0, 0, 0, 0,
@@ -284,7 +285,7 @@ static const OpcodeEntry nonparallel_opcodes[] = {
{ "0000101111DDDDDD001bbbbb", "jsset #n, S, xxxx", dis_jsset_reg, emu_jsset_reg },
{ "0000010011000RRR000ddddd", "lra Rn, D", NULL, NULL },
{ "0000010001000000010ddddd", "lra xxxx, D", NULL, NULL },
- { "000011000001111010iiiiiD", "lsl #ii, D", NULL, NULL },
+ { "000011000001111010iiiiiD", "lsl #ii, D", dis_lsl_imm, emu_lsl_imm },
{ "00001100000111100001sssD", "lsl S, D", NULL, NULL },
{ "000011000001111011iiiiiD", "lsr #ii, D", NULL, NULL },
{ "00001100000111100011sssD", "lsr S, D", NULL, NULL },
@@ -386,7 +387,7 @@ void dsp56k_reset_cpu(dsp_core_t* dsp)
memset(dsp->periph, 0, sizeof(dsp->periph));
memset(dsp->stack, 0, sizeof(dsp->stack));
memset(dsp->registers, 0, sizeof(dsp->registers));
-
+
/* Registers */
dsp->pc = 0x0000;
dsp->registers[DSP_REG_OMR]=0x02;
@@ -422,21 +423,37 @@ void dsp56k_reset_cpu(dsp_core_t* dsp)
dsp->disasm_prev_inst_pc = 0xFFFFFFFF;
}
-static OpcodeEntry lookup_opcode(uint32_t op) {
- OpcodeEntry r = {0};
- int i;
- for (i=0; i> 24) & 0xff) ^
+ ((op >> 16) & 0xff) ^
+ ((op >> 8) & 0xff) ^
+ ((op >> 0) & 0xff);
+ if (opcache[tag].op != op || opcache[tag].entry == NULL) {
+ opcache[tag].op = op;
+ opcache[tag].entry = lookup_opcode_slow(op);
+ }
+
+ return opcache[tag].entry;
}
static uint16_t disasm_instruction(dsp_core_t* dsp, dsp_trace_disasm_t mode)
@@ -461,12 +478,12 @@ static uint16_t disasm_instruction(dsp_core_t* dsp, dsp_trace_disasm_t mode)
dsp->disasm_parallelmove_name[0] = 0;
if (dsp->disasm_cur_inst < 0x100000) {
- const OpcodeEntry op = lookup_opcode(dsp->disasm_cur_inst);
- if (op.template) {
- if (op.dis_func) {
- op.dis_func(dsp);
+ const OpcodeEntry *op = lookup_opcode(dsp->disasm_cur_inst);
+ if (op->template) {
+ if (op->dis_func) {
+ op->dis_func(dsp);
} else {
- sprintf(dsp->disasm_str_instr, "%s", op.name);
+ sprintf(dsp->disasm_str_instr, "%s", op->name);
}
} else {
dis_undefined(dsp);
@@ -492,7 +509,7 @@ static void disasm_reg_compare(dsp_core_t* dsp)
int i;
bool bRegA = false;
bool bRegB = false;
-
+
for (i=4; i<64; i++) {
if (dsp->disasm_registers_save[i] == dsp->registers[i]) {
continue;
@@ -625,7 +642,7 @@ uint16_t dsp56k_execute_one_disasm_instruction(dsp_core_t* dsp, FILE *out, uint3
/* Restore DSP context after executing instruction */
memcpy(dsp, &dsp_core_save, sizeof(dsp_core_t));
-
+
/* Unset DSP in disasm mode */
dsp->executing_for_disasm = false;
@@ -639,17 +656,17 @@ void dsp56k_execute_instruction(dsp_core_t* dsp)
/* Decode and execute current instruction */
dsp->cur_inst = read_memory_p(dsp, dsp->pc);
-
+
/* Initialize instruction size and cycle counter */
dsp->cur_inst_len = 1;
dsp->instr_cycle = 2;
/* Disasm current instruction ? (trace mode only) */
- if (TRACE_DSP_DISASM) {
+ if (TRACE_DSP_DISASM) {
/* Call disasm_instruction only when DSP is called in trace mode */
if (!dsp->executing_for_disasm) {
disasm_return = disasm_instruction(dsp, DSP_TRACE_MODE);
-
+
if (disasm_return) {
printf( "%s", disasm_get_instruction_text(dsp));
}
@@ -659,13 +676,17 @@ void dsp56k_execute_instruction(dsp_core_t* dsp)
}
}
}
-
+
if (dsp->cur_inst < 0x100000) {
- const OpcodeEntry op = lookup_opcode(dsp->cur_inst);
- if (op.emu_func) {
- op.emu_func(dsp);
+ const OpcodeEntry *op = dsp->pram_opcache[dsp->pc];
+ if (op == NULL) {
+ op = lookup_opcode(dsp->cur_inst);
+ dsp->pram_opcache[dsp->pc] = op;
+ }
+ if (op->emu_func) {
+ op->emu_func(dsp);
} else {
- printf("%x - %s\n", dsp->cur_inst, op.name);
+ printf("%x - %s\n", dsp->cur_inst, op->name);
emu_undefined(dsp);
}
} else {
@@ -704,6 +725,9 @@ void dsp56k_execute_instruction(dsp_core_t* dsp)
/* Process Interrupts */
dsp_postexecute_interrupts(dsp);
+
+ dsp->num_inst += dsp->instr_cycle;
+
#ifdef DSP_COUNT_IPS
++dsp->num_inst;
if ((dsp->num_inst & 63) == 0) {
@@ -726,7 +750,7 @@ static void dsp_postexecute_update_pc(dsp_core_t* dsp)
{
/* When running a REP, PC must stay on the current instruction */
if (dsp->loop_rep) {
- /* Is PC on the instruction to repeat ? */
+ /* Is PC on the instruction to repeat ? */
if (dsp->pc_on_rep==0) {
--dsp->registers[DSP_REG_LC];
dsp->registers[DSP_REG_LC] &= BITMASK(16);
@@ -754,7 +778,7 @@ static void dsp_postexecute_update_pc(dsp_core_t* dsp)
if (dsp->registers[DSP_REG_SR] & (1<pc == dsp->registers[DSP_REG_LA] + 1) {
+ if (dsp->pc == dsp->registers[DSP_REG_LA] + 1) {
--dsp->registers[DSP_REG_LC];
dsp->registers[DSP_REG_LC] &= BITMASK(16);
@@ -786,7 +810,7 @@ void dsp56k_add_interrupt(dsp_core_t* dsp, uint16_t inter)
return;
/* add this interrupt to the pending interrupts table */
- if (dsp->interrupt_is_pending[inter] == 0) {
+ if (dsp->interrupt_is_pending[inter] == 0) {
dsp->interrupt_is_pending[inter] = 1;
dsp->interrupt_counter ++;
}
@@ -818,7 +842,7 @@ static void dsp_postexecute_interrupts(dsp_core_t* dsp)
instr = read_memory_p(dsp, dsp->interrupt_instr_fetch);
if ( ((instr & 0xfff000) == 0x0d0000) || ((instr & 0xffc0ff) == 0x0bc080) ) {
dsp->interrupt_state = DSP_INTERRUPT_LONG;
- dsp_stack_push(dsp, dsp->interrupt_save_pc, dsp->registers[DSP_REG_SR], 0);
+ dsp_stack_push(dsp, dsp->interrupt_save_pc, dsp->registers[DSP_REG_SR], 0);
dsp->registers[DSP_REG_SR] &= BITMASK(16)-((1<pc);
if ( ((instr & 0xfff000) == 0x0d0000) || ((instr & 0xffc0ff) == 0x0bc080) ) {
dsp->interrupt_state = DSP_INTERRUPT_LONG;
- dsp_stack_push(dsp, dsp->interrupt_save_pc, dsp->registers[DSP_REG_SR], 0);
+ dsp_stack_push(dsp, dsp->interrupt_save_pc, dsp->registers[DSP_REG_SR], 0);
dsp->registers[DSP_REG_SR] &= BITMASK(16)-((1<interrupt_instr_fetch == 0xff) {
/* Clear HC and HCP interrupt */
// dsp->periph[DSP_SPACE_X][DSP_HOST_HSR] &= 0xff - (1<hostport[CPU_HOST_CVR] &= 0xff - (1<hostport[CPU_HOST_CVR] &= 0xff - (1<interrupt_instr_fetch = dsp->hostport[CPU_HOST_CVR] & BITMASK(5);
- // dsp->interrupt_instr_fetch *= 2;
+ // dsp->interrupt_instr_fetch *= 2;
assert(false);
}
}
@@ -974,9 +998,15 @@ uint32_t dsp56k_read_memory(dsp_core_t* dsp, int space, uint32_t address)
return dsp->read_peripheral(dsp, address);
} else if (address >= DSP_MIXBUFFER_BASE && address < DSP_MIXBUFFER_BASE+DSP_MIXBUFFER_SIZE) {
return dsp->mixbuffer[address-DSP_MIXBUFFER_BASE];
+ } else if (address >= 0xc00 && address < 0xc00+DSP_MIXBUFFER_SIZE) {
+ return dsp->mixbuffer[address-0xc00];
} else {
- assert(address < DSP_XRAM_SIZE);
- return dsp->xram[address];
+ if (address < DSP_XRAM_SIZE) {
+ return dsp->xram[address];
+ } else {
+ fprintf(stderr, "Out of bounds read at %x!\n", address);
+ return 0x00FFFFFF; // FIXME: What does the DSP actually do in this case?
+ }
}
} else if (space == DSP_SPACE_Y) {
assert(address < DSP_YRAM_SIZE);
@@ -996,7 +1026,7 @@ void dsp56k_write_memory(dsp_core_t* dsp, int space, uint32_t address, uint32_t
if (TRACE_DSP_DISASM_MEM)
write_memory_disasm(dsp, space, address, value);
- else
+ else
write_memory_raw(dsp, space, address, value);
}
@@ -1012,6 +1042,8 @@ static void write_memory_raw(dsp_core_t* dsp, int space, uint32_t address, uint3
return;
} else if (address >= DSP_MIXBUFFER_BASE && address < DSP_MIXBUFFER_BASE+DSP_MIXBUFFER_SIZE) {
dsp->mixbuffer[address-DSP_MIXBUFFER_BASE] = value;
+ } else if (address >= 0xc00 && address < 0xc00+DSP_MIXBUFFER_SIZE) {
+ dsp->mixbuffer[address-0xc00] = value;
} else {
assert(address < DSP_XRAM_SIZE);
dsp->xram[address] = value;
@@ -1022,6 +1054,7 @@ static void write_memory_raw(dsp_core_t* dsp, int space, uint32_t address, uint3
} else if (space == DSP_SPACE_P) {
assert(address < DSP_PRAM_SIZE);
stl_le_p(&dsp->pram[address], value);
+ dsp->pram_opcache[address] = NULL;
} else {
assert(false);
}
@@ -1097,7 +1130,7 @@ static void dsp_write_reg(dsp_core_t* dsp, uint32_t numreg, uint32_t value)
}
} else {
dsp->registers[DSP_REG_SP] = value & BITMASK(6);
- }
+ }
dsp_compute_ssh_ssl(dsp);
break;
case DSP_REG_SSH:
@@ -1112,7 +1145,7 @@ static void dsp_write_reg(dsp_core_t* dsp, uint32_t numreg, uint32_t value)
dsp->registers[DSP_REG_SSL] = value & BITMASK(16);
break;
default:
- dsp->registers[numreg] = value;
+ dsp->registers[numreg] = value;
dsp->registers[numreg] &= BITMASK(registers_mask[numreg]);
break;
}
@@ -1139,7 +1172,7 @@ static void dsp_stack_push(dsp_core_t* dsp, uint32_t curpc, uint32_t cursr, uint
if (dsp->exception_debugging)
assert(false);
}
-
+
dsp->registers[DSP_REG_SP] = (underflow | stack_error | stack) & BITMASK(6);
stack &= BITMASK(4);
@@ -1254,7 +1287,7 @@ static uint16_t dsp_asr56(uint32_t *dest, int n)
uint64_t dest_v = dest[2] | ((uint64_t)dest[1] << 24) | ((uint64_t)dest[0] << 48);
uint16_t carry = (dest_v >> (n-1)) & 1;
-
+
dest_v >>= n;
dest[2] = dest_v & BITMASK(24);
dest[1] = (dest_v >> 24) & BITMASK(24);
@@ -1401,7 +1434,7 @@ static void dsp_rnd56(dsp_core_t* dsp, uint32_t *dest)
rnd_const[1] = 0;
rnd_const[2] = (1<<22);
dsp_add56(rnd_const, dest);
-
+
if ((dest[2] & 0x7fffff) == 0){
dest[2] = 0;
}
diff --git a/hw/xbox/dsp/dsp_cpu.h b/hw/xbox/mcpx/dsp/dsp_cpu.h
similarity index 97%
rename from hw/xbox/dsp/dsp_cpu.h
rename to hw/xbox/mcpx/dsp/dsp_cpu.h
index 0f4fc9c308..6876b4d20a 100644
--- a/hw/xbox/dsp/dsp_cpu.h
+++ b/hw/xbox/mcpx/dsp/dsp_cpu.h
@@ -154,6 +154,10 @@ typedef struct dsp_interrupt_s {
typedef struct dsp_core_s dsp_core_t;
struct dsp_core_s {
+ bool is_gp;
+ bool is_idle;
+ uint32_t cycle_count;
+
/* DSP instruction Cycle counter */
uint16_t instr_cycle;
@@ -167,6 +171,7 @@ struct dsp_core_s {
uint32_t xram[DSP_XRAM_SIZE];
uint32_t yram[DSP_YRAM_SIZE];
uint32_t pram[DSP_PRAM_SIZE];
+ const void *pram_opcache[DSP_PRAM_SIZE];
uint32_t mixbuffer[DSP_MIXBUFFER_SIZE];
@@ -227,8 +232,8 @@ struct dsp_core_s {
uint16_t disasm_cur_inst_len;
/* Current instruction */
- char disasm_str_instr[128];
- char disasm_str_instr2[128];
+ char disasm_str_instr[256];
+ char disasm_str_instr2[523];
char disasm_parallelmove_name[64];
/**********************************
diff --git a/hw/xbox/dsp/dsp_dis.inl b/hw/xbox/mcpx/dsp/dsp_dis.inl
similarity index 99%
rename from hw/xbox/dsp/dsp_dis.inl
rename to hw/xbox/mcpx/dsp/dsp_dis.inl
index d8d3797ed2..ea9385f2bb 100644
--- a/hw/xbox/dsp/dsp_dis.inl
+++ b/hw/xbox/mcpx/dsp/dsp_dis.inl
@@ -301,6 +301,15 @@ static void dis_asl_imm(dsp_core_t* dsp)
registers_name[D ? DSP_REG_B : DSP_REG_A]);
}
+static void dis_lsl_imm(dsp_core_t* dsp)
+{
+ uint32_t D = dsp->disasm_cur_inst & 1;
+ uint32_t ii = (dsp->disasm_cur_inst >> 1) & BITMASK(5);
+ sprintf(dsp->disasm_str_instr, "lsl #$%02x, %s",
+ ii,
+ registers_name[D ? DSP_REG_B : DSP_REG_A]);
+}
+
static void dis_asr_imm(dsp_core_t* dsp)
{
uint32_t S = (dsp->disasm_cur_inst >> 7) & 1;
diff --git a/hw/xbox/dsp/dsp_dma.c b/hw/xbox/mcpx/dsp/dsp_dma.c
similarity index 50%
rename from hw/xbox/dsp/dsp_dma.c
rename to hw/xbox/mcpx/dsp/dsp_dma.c
index 0b43f3bef1..84ec4ffe99 100644
--- a/hw/xbox/dsp/dsp_dma.c
+++ b/hw/xbox/mcpx/dsp/dsp_dma.c
@@ -2,6 +2,7 @@
* MCPX DSP DMA
*
* Copyright (c) 2015 espes
+ * Copyright (c) 2020-2021 Matt Borgerson
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -22,7 +23,11 @@
#include
#include
+#include
+#include "qemu/compiler.h"
#include "dsp_dma.h"
+#include "dsp_state.h"
+
#define DMA_CONFIGURATION_AUTOSTART (1 << 0)
#define DMA_CONFIGURATION_AUTOREADY (1 << 1)
@@ -84,25 +89,91 @@ const char *format_names[] = {
"24 bit lsb", /* 0x6 */
"" /* 0x7 */
};
+
+const char *space_names[] = {
+ "x", /* DSP_SPACE_X, 0x0 */
+ "y", /* DSP_SPACE_Y, 0x1 */
+ "p" /* DSP_SPACE_P, 0x2 */
+};
+
#endif
+#define MIN(a,b) (((a)<(b))?(a):(b))
+
+static void scratch_circular_copy(
+ DSPDMAState *s,
+ uint32_t scratch_base,
+ uint32_t *scratch_offset,
+ uint32_t scratch_size,
+ uint32_t transfer_size,
+ uint8_t *scratch_buf,
+ int direction)
+{
+ if (*scratch_offset >= scratch_size) {
+ // fprintf(stderr, "Initial scratch offset exceeds scratch size! Wrapping\n");
+ *scratch_offset = 0;
+ }
+
+ uint32_t buf_offset = 0;
+
+ while (transfer_size > 0) {
+ size_t bytes_until_wrap = scratch_size - *scratch_offset;
+ size_t chunk_size = MIN(transfer_size, bytes_until_wrap);
+ uint32_t scratch_addr = scratch_base + *scratch_offset;
+
+ // R/W to scratch memory from chunk in buffer
+ s->scratch_rw(s->rw_opaque, &scratch_buf[buf_offset], scratch_addr, chunk_size, direction);
+
+ // Advance scratch pointer, wrap if we've reached the end
+ *scratch_offset += chunk_size;
+ if (*scratch_offset >= scratch_size) {
+ *scratch_offset = 0;
+ }
+
+ transfer_size -= chunk_size;
+ buf_offset += chunk_size;
+ }
+}
+
static void dsp_dma_run(DSPDMAState *s)
{
if (!(s->control & DMA_CONTROL_RUNNING)
|| (s->control & DMA_CONTROL_FROZEN)) {
return;
}
+
+ // DSPState *dsp = container_of(s, DSPState, dma);
+
while (!(s->next_block & NODE_POINTER_EOL)) {
uint32_t addr = s->next_block & NODE_POINTER_VAL;
- assert((addr+6) < sizeof(s->core->xram));
- uint32_t next_block = dsp56k_read_memory(s->core, DSP_SPACE_X, addr);
- uint32_t control = dsp56k_read_memory(s->core, DSP_SPACE_X, addr+1);
- uint32_t count = dsp56k_read_memory(s->core, DSP_SPACE_X, addr+2);
- uint32_t dsp_offset = dsp56k_read_memory(s->core, DSP_SPACE_X, addr+3);
- uint32_t scratch_offset = dsp56k_read_memory(s->core, DSP_SPACE_X, addr+4);
- uint32_t scratch_base = dsp56k_read_memory(s->core, DSP_SPACE_X, addr+5);
- uint32_t scratch_size = dsp56k_read_memory(s->core, DSP_SPACE_X, addr+6)+1;
+ // FIXME: Are these block addresses BYTE addresses or WORD addresses?
+ // Need to understand this DMA engine better.
+ uint32_t block_addr;
+ int block_space;
+ if (addr < 0x1800) {
+ assert(addr+6 < 0x1800);
+ block_space = DSP_SPACE_X;
+ block_addr = addr;
+ } else if (addr >= 0x1800 && addr < 0x2000) { //?
+ assert(addr+6 < 0x2000);
+ block_space = DSP_SPACE_Y;
+ block_addr = addr - 0x1800;
+ } else if (addr >= 0x2800 && addr < 0x3800) { //?
+ assert(addr+6 < 0x3800);
+ block_space = DSP_SPACE_P;
+ block_addr = addr - 0x2800;
+ } else {
+ assert(false);
+ }
+
+ uint32_t next_block = dsp56k_read_memory(s->core, block_space, block_addr);
+ uint32_t control = dsp56k_read_memory(s->core, block_space, block_addr+1);
+ uint32_t count = dsp56k_read_memory(s->core, block_space, block_addr+2);
+ uint32_t dsp_offset = dsp56k_read_memory(s->core, block_space, block_addr+3);
+ uint32_t scratch_offset = dsp56k_read_memory(s->core, block_space, block_addr+4);
+ uint32_t scratch_base = dsp56k_read_memory(s->core, block_space, block_addr+5);
+ uint32_t scratch_size = dsp56k_read_memory(s->core, block_space, block_addr+6)+1;
s->next_block = next_block;
if (s->next_block & NODE_POINTER_EOL) {
@@ -110,15 +181,15 @@ static void dsp_dma_run(DSPDMAState *s)
}
/* Decode control word */
- bool dsp_interleave = (control >> 0) & 1;
- bool direction = control & NODE_CONTROL_DIRECTION;
- uint32_t unk2 = (control >> 2) & 0x3;
- bool buffer_offset_writeback = (control >> 4) & 1;
- uint32_t buf_id = (control >> 5) & 0xf;
- bool unk9 = (control >> 9) & 1; /* FIXME: What does this do? */
- uint32_t format = (control >> 10) & 0x7;
- bool unk13 = (control >> 13) & 1;
- uint32_t dsp_step = (control >> 14) & 0x3FF;
+ bool dsp_interleave = (control >> 0) & 1;
+ bool direction = control & NODE_CONTROL_DIRECTION;
+ uint32_t unk2 = (control >> 2) & 0x3;
+ bool buffer_offset_writeback = (control >> 4) & 1;
+ uint32_t buf_id = (control >> 5) & 0xf;
+ // bool unk9 = (control >> 9) & 1; /* FIXME: What does this do? */
+ uint32_t format = (control >> 10) & 0x7;
+ bool unk13 = (control >> 13) & 1;
+ // uint32_t dsp_step = (control >> 14) & 0x3FF; // FIXME
/* Check for unhandled control settings */
assert(unk2 == 0x0);
@@ -130,11 +201,14 @@ static void dsp_dma_run(DSPDMAState *s)
unsigned int item_size;
uint32_t item_mask = 0xffffffff;
+ // bool lsb = (format == 6); // FIXME
+
switch(format) {
case 1:
item_size = 2;
+ item_mask = 0x0000FFFF;
break;
- case 2: //big-endian?
+ case 2:
case 6:
item_size = 4;
item_mask = 0x00FFFFFF;
@@ -145,21 +219,7 @@ static void dsp_dma_run(DSPDMAState *s)
break;
}
- size_t scratch_addr;
- if (buf_id == 0xe) { // 'circular'?
- // assert(scratch_offset == 0);
- // assert(scratch_offset + count * item_size < scratch_size);
- if (scratch_offset + count * item_size >= scratch_size) {
- // This happens during the startup sound effect.
- // I think it might actually be a bug in the code...
- DPRINTF("skipping bad dma...\n");
- continue;
- }
- scratch_addr = scratch_base + scratch_offset; //??
- } else {
- // assert(buf_id == 0xf) // 'offset'
- scratch_addr = scratch_offset;
- }
+ size_t scratch_addr = scratch_base + scratch_offset;
uint32_t mem_address;
int mem_space;
@@ -176,70 +236,61 @@ static void dsp_dma_run(DSPDMAState *s)
mem_space = DSP_SPACE_P;
mem_address = dsp_offset - 0x2800;
} else {
+ fprintf(stderr, "Attempt to access %08x\n", dsp_offset);
assert(false);
}
-#ifdef DEBUG
- char dsp_space_name = '?';
- if (mem_space == DSP_SPACE_X) {
- dsp_space_name = 'x';
- } else if (mem_space == DSP_SPACE_Y) {
- dsp_space_name = 'y';
- } else if (mem_space == DSP_SPACE_P) {
- dsp_space_name = 'p';
- }
-#endif
-
- DPRINTF("dsp dma block x:$%x (%s)\n"
- " next-block x:$%x%s\n"
- " control 0x%06x:\n"
- " dsp-interleave %d\n"
- " buffer-offset-writeback %d\n"
- " buffer 0x%x (%s)\n"
- " unk9 %d\n"
- " sample-format 0x%x (%s)\n"
- " dsp-step 0x%x\n"
- " sample-count 0x%x\n"
- " block-count 0x%x channel-count %d\n"
- " dsp-address 0x%x (%c:$%x)\n"
- " buffer-offset 0x%x (+ buffer-base 0x%x = 0x%zx)\n"
- " buffer-size 0x%x\n",
- addr, direction ? "dsp -> buffer" : "buffer -> dsp",
- next_block & NODE_POINTER_VAL, s->eol ? " (eol)" : "",
- control,
- dsp_interleave,
- buffer_offset_writeback,
- buf_id, buffer_names[buf_id],
- unk9,
- format, format_names[format],
- dsp_step,
- count,
- block_count,
- channel_count,
- dsp_offset, dsp_space_name, mem_address,
- scratch_offset, scratch_base, scratch_addr,
- scratch_size);
-
-
size_t transfer_size = count * item_size;
- uint8_t* scratch_buf = calloc(count, item_size);
+
+ // FIXME: Remove this intermediate buffer
+ static uint8_t *scratch_buf = NULL;
+ static ssize_t scratch_buf_size = -1;
+ if (count * item_size > scratch_buf_size) {
+ scratch_buf_size = count * item_size;
+ scratch_buf = malloc(scratch_buf_size);
+ }
if (direction) {
- int i;
- for (i=0; icore,
- mem_space, mem_address+i);
- switch(item_size) {
- case 2:
- *(uint16_t*)(scratch_buf + i*2) = v;
- break;
- case 4:
- *(uint32_t*)(scratch_buf + i*4) = v;
- break;
- default:
- assert(false);
- break;
+ if (dsp_interleave) {
+ // FIXME: Above xfer size calculation instead of
+ // overwriting here
+ transfer_size = block_count * item_size * channel_count;
+
+ // Interleave samples
+ for (int i = 0; i < block_count; i++) {
+ for (int ch = 0; ch < channel_count; ch++) {
+ uint32_t v = dsp56k_read_memory(s->core,
+ mem_space, mem_address+ch*block_count+i);
+ switch(item_size) {
+ case 2:
+ *(uint16_t*)(scratch_buf + i*2*channel_count + ch*2) = v >> 8;
+ break;
+ case 4:
+ *(uint32_t*)(scratch_buf + i*4*channel_count + ch*4) = v;
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
}
+
+ } else {
+ for (int i = 0; i < count; i++) {
+ uint32_t v = dsp56k_read_memory(s->core, mem_space, mem_address+i);
+ switch(item_size) {
+ case 2:
+ *(uint16_t*)(scratch_buf + i*2) = v >> 8;
+ break;
+ case 4:
+ *(uint32_t*)(scratch_buf + i*4) = v;
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+
}
/* FIXME: Move to function; then reuse for both directions */
@@ -247,16 +298,14 @@ static void dsp_dma_run(DSPDMAState *s)
case 0x0:
case 0x1:
case 0x2:
- case 0x3: {
- unsigned int fifo_index = buf_id;
- s->fifo_rw(s->rw_opaque,
- scratch_buf, fifo_index, transfer_size, 1);
+ case 0x3:
+ s->fifo_rw(s->rw_opaque, scratch_buf, buf_id, transfer_size, 1);
break;
- }
case 0xE:
+ scratch_circular_copy(s, scratch_base, &scratch_offset, scratch_size, transfer_size, scratch_buf, 1);
+ break;
case 0xF:
- s->scratch_rw(s->rw_opaque,
- scratch_buf, scratch_addr, transfer_size, 1);
+ s->scratch_rw(s->rw_opaque, scratch_buf, scratch_addr, transfer_size, 1);
break;
default:
fprintf(stderr, "Unknown DSP DMA buffer: 0x%x\n", buf_id);
@@ -264,20 +313,22 @@ static void dsp_dma_run(DSPDMAState *s)
break;
}
} else {
+ assert(!dsp_interleave);
- /* FIXME: Support FIFOs */
- assert(buf_id == 0xE || buf_id == 0xF);
+ if (buf_id == 0xe) {
+ scratch_circular_copy(s, scratch_base, &scratch_offset, scratch_size, transfer_size, scratch_buf, 0);
+ } else if (buf_id == 0xf) {
+ s->scratch_rw(s->rw_opaque, scratch_buf, scratch_addr, transfer_size, 0);
+ } else {
+ fprintf(stderr, "Unhandled DSP DMA buffer: 0x%x\n", buf_id);
+ assert(false);
+ }
- // read from scratch memory
- s->scratch_rw(s->rw_opaque,
- scratch_buf, scratch_addr, transfer_size, 0);
-
- int i;
- for (i=0; icore, mem_space, mem_address+i, v);
}
}
- free(scratch_buf);
+ if (buffer_offset_writeback) {
+ dsp56k_write_memory(s->core, block_space, block_addr+4, scratch_offset);
+ }
}
}
@@ -340,6 +393,9 @@ void dsp_dma_write(DSPDMAState *s, DSPDMARegister reg, uint32_t v)
break;
}
dsp_dma_run(s);
+
+ // IDK about this, but need to stop somehow?
+ // s->control |= DMA_CONTROL_STOPPED;
break;
case DMA_START_BLOCK:
s->start_block = v;
diff --git a/hw/xbox/dsp/dsp_dma.h b/hw/xbox/mcpx/dsp/dsp_dma.h
similarity index 94%
rename from hw/xbox/dsp/dsp_dma.h
rename to hw/xbox/mcpx/dsp/dsp_dma.h
index e06a3d13d8..7ec300d31d 100644
--- a/hw/xbox/dsp/dsp_dma.h
+++ b/hw/xbox/mcpx/dsp/dsp_dma.h
@@ -26,6 +26,9 @@
#include "dsp.h"
#include "dsp_cpu.h"
+#define DMA_CONTROL_RUNNING (1 << 4)
+#define DMA_CONTROL_STOPPED (1 << 5)
+
typedef enum DSPDMARegister {
DMA_CONFIGURATION,
DMA_CONTROL,
@@ -52,4 +55,4 @@ typedef struct DSPDMAState {
uint32_t dsp_dma_read(DSPDMAState *s, DSPDMARegister reg);
void dsp_dma_write(DSPDMAState *s, DSPDMARegister reg, uint32_t v);
-#endif
\ No newline at end of file
+#endif
diff --git a/hw/xbox/dsp/dsp_emu.inl b/hw/xbox/mcpx/dsp/dsp_emu.inl
similarity index 99%
rename from hw/xbox/dsp/dsp_emu.inl
rename to hw/xbox/mcpx/dsp/dsp_emu.inl
index b18259469d..aa67a20dad 100644
--- a/hw/xbox/dsp/dsp_emu.inl
+++ b/hw/xbox/mcpx/dsp/dsp_emu.inl
@@ -1689,6 +1689,17 @@ static void emu_lsl_b(dsp_core_t* dsp)
dsp->registers[DSP_REG_SR] |= (dsp->registers[DSP_REG_B1]==0)<cur_inst & 1;
+ uint32_t ii = (dsp->cur_inst >> 1) & BITMASK(5);
+ // FIXME
+ void (*func)(dsp_core_t *dsp) = D ? emu_lsl_b : emu_lsl_a;
+ while (ii--) {
+ func(dsp);
+ }
+}
+
static void emu_lsr_a(dsp_core_t* dsp)
{
uint32_t newcarry = dsp->registers[DSP_REG_A1] & 1;
diff --git a/hw/xbox/dsp/dsp_state.h b/hw/xbox/mcpx/dsp/dsp_state.h
similarity index 98%
rename from hw/xbox/dsp/dsp_state.h
rename to hw/xbox/mcpx/dsp/dsp_state.h
index a578094e36..899ac0740c 100644
--- a/hw/xbox/dsp/dsp_state.h
+++ b/hw/xbox/mcpx/dsp/dsp_state.h
@@ -34,6 +34,8 @@ struct DSPState {
int save_cycles;
uint32_t interrupts;
+
+ bool is_gp;
};
#endif /* DSP_STATE_H */
diff --git a/hw/xbox/mcpx/fpconv.h b/hw/xbox/mcpx/fpconv.h
new file mode 100644
index 0000000000..3995d150a7
--- /dev/null
+++ b/hw/xbox/mcpx/fpconv.h
@@ -0,0 +1,58 @@
+/*
+ * Helper FP conversions
+ *
+ * Copyright (c) 2020-2021 Matt Borgerson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+
+
+#ifndef FLOATCONV_H
+#define FLOATCONV_H
+
+static float uint8_to_float(uint8_t value)
+{
+ return ((int)value - 0x80) / (1.0 * 0x80);
+}
+
+static float int16_to_float(int16_t value)
+{
+ return value / (1.0 * 0x8000);
+}
+
+static float int32_to_float(int32_t value)
+{
+ return value / (1.0 * 0x80000000);
+}
+
+static float int24_to_float(int32_t value)
+{
+ return int32_to_float((uint32_t)value << 8);
+}
+
+static uint32_t float_to_24b(float value)
+{
+ double scaled_value = value * (8.0 * 0x100000);
+ int int24;
+ if (scaled_value >= (1.0 * 0x7fffff)) {
+ int24 = 0x7fffff;
+ } else if (scaled_value <= (-8.0 * 0x100000)) {
+ int24 = -1 - 0x7fffff;
+ } else {
+ int24 = lrint(scaled_value);
+ }
+ return int24 & 0xffffff;
+}
+
+#endif
diff --git a/hw/xbox/mcpx/svf.h b/hw/xbox/mcpx/svf.h
new file mode 100644
index 0000000000..a02b561f0f
--- /dev/null
+++ b/hw/xbox/mcpx/svf.h
@@ -0,0 +1,91 @@
+/*
+ * Adapted from SWH LADSPA Plugins package, modified for xemu
+ *
+ * Source: https://github.com/swh/ladspa/blob/master/svf_1214.xml
+ * Author: Steve Harris, andy@vellocet
+ * License: GPLv2
+ *
+ */
+
+#ifndef SVF_H
+#define SVF_H
+
+#include
+
+#define flush_to_zero(x) x
+
+// Constants to match filter types
+#define F_LP 1
+#define F_HP 2
+#define F_BP 3
+#define F_BR 4
+#define F_AP 5
+
+// Number of filter oversamples
+#define F_R 1
+
+/* Structure to hold parameters for SV filter */
+
+typedef struct {
+ float f; // 2.0*sin(PI*fs/(fc*r));
+ float q; // 2.0*cos(pow(q, 0.1)*PI*0.5);
+ float qnrm; // sqrt(m/2.0f+0.01f);
+ float h; // high pass output
+ float b; // band pass output
+ float l; // low pass output
+ float p; // peaking output (allpass with resonance)
+ float n; // notch output
+ float *op; // pointer to output value
+} sv_filter;
+
+/* Store data in SVF struct, takes the sampling frequency, cutoff frequency
+ and Q, and fills in the structure passed */
+/*
+static inline void setup_svf(sv_filter *sv, float fs, float fc, float q, int t) {
+ sv->f = 2.0f * sin(M_PI * fc / (float)(fs * F_R));
+ sv->q = 2.0f * cos(pow(q, 0.1f) * M_PI * 0.5f);
+*/
+static inline void setup_svf(sv_filter *sv, float fc, float q, int t) {
+ sv->f = fc;
+ sv->q = q;
+ sv->qnrm = sqrt(sv->q/2.0+0.01);
+ switch(t) {
+ case F_LP:
+ sv->op = &(sv->l);
+ break;
+ case F_HP:
+ sv->op = &(sv->h);
+ break;
+ case F_BP:
+ sv->op = &(sv->b);
+ break;
+ case F_BR:
+ sv->op = &(sv->n);
+ break;
+ default:
+ sv->op = &(sv->p);
+ }
+}
+
+/* Run one sample through the SV filter. Filter is by andy@vellocet */
+static inline float run_svf(sv_filter *sv, float in) {
+ float out;
+ int i;
+ in = sv->qnrm * in ;
+ for (i=0; i < F_R; i++) {
+ // very slight waveshape for extra stability
+ sv->b = flush_to_zero(sv->b - sv->b * sv->b * sv->b * 0.001f);
+ // regular state variable code here
+ // the notch and peaking outputs are optional
+ sv->h = flush_to_zero(in - sv->l - sv->q * sv->b);
+ sv->b = sv->b + sv->f * sv->h;
+ sv->l = flush_to_zero(sv->l + sv->f * sv->b);
+ sv->n = sv->l + sv->h;
+ sv->p = sv->l - sv->h;
+ out = *(sv->op);
+ in = out;
+ }
+ return out;
+}
+
+#endif
diff --git a/hw/xbox/mcpx_apu.c b/hw/xbox/mcpx_apu.c
deleted file mode 100644
index 3eb7ff5cf2..0000000000
--- a/hw/xbox/mcpx_apu.c
+++ /dev/null
@@ -1,1741 +0,0 @@
-/*
- * QEMU MCPX Audio Processing Unit implementation
- *
- * Copyright (c) 2012 espes
- * Copyright (c) 2018-2019 Jannik Vogel
- * Copyright (c) 2020 Matt Borgerson
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see .
- */
-
-#include "qemu/osdep.h"
-#include "hw/hw.h"
-#include "hw/i386/pc.h"
-#include "hw/pci/pci.h"
-#include "cpu.h"
-#include "hw/xbox/dsp/dsp.h"
-#include "hw/xbox/dsp/dsp_dma.h"
-#include "hw/xbox/dsp/dsp_cpu.h"
-#include "hw/xbox/dsp/dsp_state.h"
-#include "migration/vmstate.h"
-
-#include
-
-#define NUM_SAMPLES_PER_FRAME 32
-#define NUM_MIXBINS 32
-
-#include "hw/xbox/mcpx_apu.h"
-
-#define NV_PAPU_ISTS 0x00001000
-# define NV_PAPU_ISTS_GINTSTS (1 << 0)
-# define NV_PAPU_ISTS_FETINTSTS (1 << 4)
-#define NV_PAPU_IEN 0x00001004
-#define NV_PAPU_FECTL 0x00001100
-# define NV_PAPU_FECTL_FEMETHMODE 0x000000E0
-# define NV_PAPU_FECTL_FEMETHMODE_FREE_RUNNING 0x00000000
-# define NV_PAPU_FECTL_FEMETHMODE_HALTED 0x00000080
-# define NV_PAPU_FECTL_FEMETHMODE_TRAPPED 0x000000E0
-# define NV_PAPU_FECTL_FETRAPREASON 0x00000F00
-# define NV_PAPU_FECTL_FETRAPREASON_REQUESTED 0x00000F00
-#define NV_PAPU_FECV 0x00001110
-#define NV_PAPU_FEAV 0x00001118
-# define NV_PAPU_FEAV_VALUE 0x0000FFFF
-# define NV_PAPU_FEAV_LST 0x00030000
-#define NV_PAPU_FEDECMETH 0x00001300
-#define NV_PAPU_FEDECPARAM 0x00001304
-#define NV_PAPU_FEMEMADDR 0x00001324
-#define NV_PAPU_FEMEMDATA 0x00001334
-#define NV_PAPU_FETFORCE0 0x00001500
-#define NV_PAPU_FETFORCE1 0x00001504
-# define NV_PAPU_FETFORCE1_SE2FE_IDLE_VOICE (1 << 15)
-#define NV_PAPU_SECTL 0x00002000
-# define NV_PAPU_SECTL_XCNTMODE 0x00000018
-# define NV_PAPU_SECTL_XCNTMODE_OFF 0
-#define NV_PAPU_XGSCNT 0x0000200C
-#define NV_PAPU_VPVADDR 0x0000202C
-#define NV_PAPU_VPSGEADDR 0x00002030
-#define NV_PAPU_GPSADDR 0x00002040
-#define NV_PAPU_GPFADDR 0x00002044
-#define NV_PAPU_EPSADDR 0x00002048
-#define NV_PAPU_EPFADDR 0x0000204C
-#define NV_PAPU_TVL2D 0x00002054
-#define NV_PAPU_CVL2D 0x00002058
-#define NV_PAPU_NVL2D 0x0000205C
-#define NV_PAPU_TVL3D 0x00002060
-#define NV_PAPU_CVL3D 0x00002064
-#define NV_PAPU_NVL3D 0x00002068
-#define NV_PAPU_TVLMP 0x0000206C
-#define NV_PAPU_CVLMP 0x00002070
-#define NV_PAPU_NVLMP 0x00002074
-#define NV_PAPU_GPSMAXSGE 0x000020D4
-#define NV_PAPU_GPFMAXSGE 0x000020D8
-#define NV_PAPU_EPSMAXSGE 0x000020DC
-#define NV_PAPU_EPFMAXSGE 0x000020E0
-
-/* Each FIFO has the same fields */
-#define NV_PAPU_GPOFBASE0 0x00003024
-# define NV_PAPU_GPOFBASE0_VALUE 0x00FFFF00
-#define NV_PAPU_GPOFEND0 0x00003028
-# define NV_PAPU_GPOFEND0_VALUE 0x00FFFF00
-#define NV_PAPU_GPOFCUR0 0x0000302C
-# define NV_PAPU_GPOFCUR0_VALUE 0x00FFFFFC
-#define NV_PAPU_GPOFBASE1 0x00003034
-#define NV_PAPU_GPOFEND1 0x00003038
-#define NV_PAPU_GPOFCUR1 0x0000303C
-#define NV_PAPU_GPOFBASE2 0x00003044
-#define NV_PAPU_GPOFEND2 0x00003048
-#define NV_PAPU_GPOFCUR2 0x0000304C
-#define NV_PAPU_GPOFBASE3 0x00003054
-#define NV_PAPU_GPOFEND3 0x00003058
-#define NV_PAPU_GPOFCUR3 0x0000305C
-
-/* Fields are same as for the 4 output FIFOs, but only 2 input FIFOs */
-#define NV_PAPU_GPIFBASE0 0x00003064
-#define NV_PAPU_GPIFEND0 0x00003068
-#define NV_PAPU_GPIFCUR0 0x0000306C
-#define NV_PAPU_GPIFBASE1 0x00003074
-#define NV_PAPU_GPIFEND1 0x00003078
-#define NV_PAPU_GPIFCUR1 0x0000307C
-
-/* Fields, strides and count is same as for GP FIFOs */
-#define NV_PAPU_EPOFBASE0 0x00004024
-#define NV_PAPU_EPOFEND0 0x00004028
-#define NV_PAPU_EPOFCUR0 0x0000402C
-#define NV_PAPU_EPIFBASE0 0x00004064
-#define NV_PAPU_EPIFEND0 0x00004068
-#define NV_PAPU_EPIFCUR0 0x0000406C
-
-#define NV_PAPU_GPXMEM 0x00000000
-#define NV_PAPU_GPMIXBUF 0x00005000
-#define NV_PAPU_GPYMEM 0x00006000
-#define NV_PAPU_GPPMEM 0x0000A000
-#define NV_PAPU_GPRST 0x0000FFFC
-#define NV_PAPU_GPRST_GPRST (1 << 0)
-#define NV_PAPU_GPRST_GPDSPRST (1 << 1)
-#define NV_PAPU_GPRST_GPNMI (1 << 2)
-#define NV_PAPU_GPRST_GPABORT (1 << 3)
-
-#define NV_PAPU_EPXMEM 0x00000000
-#define NV_PAPU_EPYMEM 0x00006000
-#define NV_PAPU_EPPMEM 0x0000A000
-#define NV_PAPU_EPRST 0x0000FFFC
-
-static const struct {
- hwaddr top, current, next;
-} voice_list_regs[] = {
- {NV_PAPU_TVL2D, NV_PAPU_CVL2D, NV_PAPU_NVL2D}, //2D
- {NV_PAPU_TVL3D, NV_PAPU_CVL3D, NV_PAPU_NVL3D}, //3D
- {NV_PAPU_TVLMP, NV_PAPU_CVLMP, NV_PAPU_NVLMP}, //MP
-};
-
-
-/* audio processor object / front-end messages */
-#define NV1BA0_PIO_FREE 0x00000010
-#define NV1BA0_PIO_SET_ANTECEDENT_VOICE 0x00000120
-# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_HANDLE 0x0000FFFF
-# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST 0x00030000
-# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_INHERIT 0
-# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_2D_TOP 1
-# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_3D_TOP 2
-# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_MP_TOP 3
-#define NV1BA0_PIO_VOICE_ON 0x00000124
-# define NV1BA0_PIO_VOICE_ON_HANDLE 0x0000FFFF
-# define NV1BA0_PIO_VOICE_ON_ENVF 0x0F000000
-# define NV1BA0_PIO_VOICE_ON_ENVA 0xF0000000
-#define NV1BA0_PIO_VOICE_OFF 0x00000128
-# define NV1BA0_PIO_VOICE_OFF_HANDLE 0x0000FFFF
-#define NV1BA0_PIO_VOICE_RELEASE 0x0000012C
-# define NV1BA0_PIO_VOICE_RELEASE_HANDLE 0x0000FFFF
-#define NV1BA0_PIO_VOICE_PAUSE 0x00000140
-# define NV1BA0_PIO_VOICE_PAUSE_HANDLE 0x0000FFFF
-# define NV1BA0_PIO_VOICE_PAUSE_ACTION (1 << 18)
-#define NV1BA0_PIO_SET_CURRENT_VOICE 0x000002F8
-#define NV1BA0_PIO_SET_VOICE_CFG_VBIN 0x00000300
-#define NV1BA0_PIO_SET_VOICE_CFG_FMT 0x00000304
-#define NV1BA0_PIO_SET_VOICE_CFG_ENV0 0x00000308
-#define NV1BA0_PIO_SET_VOICE_CFG_ENVA 0x0000030C
-#define NV1BA0_PIO_SET_VOICE_CFG_ENV1 0x00000310
-#define NV1BA0_PIO_SET_VOICE_CFG_ENVF 0x00000314
-#define NV1BA0_PIO_SET_VOICE_CFG_MISC 0x00000318
-#define NV1BA0_PIO_SET_VOICE_TAR_VOLA 0x00000360
-#define NV1BA0_PIO_SET_VOICE_TAR_VOLB 0x00000364
-#define NV1BA0_PIO_SET_VOICE_TAR_VOLC 0x00000368
-#define NV1BA0_PIO_SET_VOICE_LFO_ENV 0x0000036C
-#define NV1BA0_PIO_SET_VOICE_TAR_PITCH 0x0000037C
-# define NV1BA0_PIO_SET_VOICE_TAR_PITCH_STEP 0xFFFF0000
-#define NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE 0x000003A0
-# define NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE_OFFSET 0x00FFFFFF
-#define NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO 0x000003A4
-# define NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO_OFFSET 0x00FFFFFF
-#define NV1BA0_PIO_SET_VOICE_BUF_CBO 0x000003D8
-# define NV1BA0_PIO_SET_VOICE_BUF_CBO_OFFSET 0x00FFFFFF
-#define NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO 0x000003DC
-# define NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO_OFFSET 0x00FFFFFF
-#define NV1BA0_PIO_SET_CURRENT_INBUF_SGE 0x00000804
-# define NV1BA0_PIO_SET_CURRENT_INBUF_SGE_HANDLE 0xFFFFFFFF
-#define NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET 0x00000808
-# define NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER 0xFFFFF000
-#define NV1BA0_PIO_SET_OUTBUF_BA 0x00001000 // 8 byte pitch, 4 entries
-# define NV1BA0_PIO_SET_OUTBUF_BA_ADDRESS 0x007FFF00
-#define NV1BA0_PIO_SET_OUTBUF_LEN 0x00001004 // 8 byte pitch, 4 entries
-# define NV1BA0_PIO_SET_OUTBUF_LEN_VALUE 0x007FFF00
-#define NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE 0x00001800
-# define NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_HANDLE 0xFFFFFFFF
-#define NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET 0x00001808
-# define NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER 0xFFFFF000
-
-#define SE2FE_IDLE_VOICE 0x00008000
-
-
-/* voice structure */
-#define NV_PAVS_SIZE 0x00000080
-#define NV_PAVS_VOICE_CFG_VBIN 0x00000000
-# define NV_PAVS_VOICE_CFG_VBIN_V0BIN (0x1F << 0)
-# define NV_PAVS_VOICE_CFG_VBIN_V1BIN (0x1F << 5)
-# define NV_PAVS_VOICE_CFG_VBIN_V2BIN (0x1F << 10)
-# define NV_PAVS_VOICE_CFG_VBIN_V3BIN (0x1F << 16)
-# define NV_PAVS_VOICE_CFG_VBIN_V4BIN (0x1F << 21)
-# define NV_PAVS_VOICE_CFG_VBIN_V5BIN (0x1F << 26)
-#define NV_PAVS_VOICE_CFG_FMT 0x00000004
-# define NV_PAVS_VOICE_CFG_FMT_V6BIN (0x1F << 0)
-# define NV_PAVS_VOICE_CFG_FMT_V7BIN (0x1F << 5)
-# define NV_PAVS_VOICE_CFG_FMT_SAMPLES_PER_BLOCK (0x1F << 16)
-# define NV_PAVS_VOICE_CFG_FMT_DATA_TYPE (1 << 24)
-# define NV_PAVS_VOICE_CFG_FMT_LOOP (1 << 25)
-# define NV_PAVS_VOICE_CFG_FMT_STEREO (1 << 27)
-# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE (0x3 << 28)
-# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_U8 0
-# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S16 1
-# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24 2
-# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S32 3
-# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE (0x3 << 30)
-# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_B8 0
-# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_B16 1
-# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_ADPCM 2
-# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_B32 3
-#define NV_PAVS_VOICE_CFG_ENV0 0x00000008
-# define NV_PAVS_VOICE_CFG_ENV0_EA_ATTACKRATE (0xFFF << 0)
-# define NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME (0xFFF << 12)
-# define NV_PAVS_VOICE_CFG_ENV0_EF_PITCHSCALE (0xFF << 24)
-#define NV_PAVS_VOICE_CFG_ENVA 0x0000000C
-# define NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE (0xFFF << 0)
-# define NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME (0xFFF << 12)
-# define NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL (0xFF << 24)
-#define NV_PAVS_VOICE_CFG_ENV1 0x00000010
-# define NV_PAVS_VOICE_CFG_ENV1_EF_FCSCALE (0xFF << 24)
-#define NV_PAVS_VOICE_CFG_ENVF 0x00000014
-#define NV_PAVS_VOICE_CFG_MISC 0x00000018
-# define NV_PAVS_VOICE_CFG_MISC_EF_RELEASERATE (0xFFF << 0)
-
-#define NV_PAVS_VOICE_CUR_PSL_START 0x00000020
-# define NV_PAVS_VOICE_CUR_PSL_START_BA 0x00FFFFFF
-#define NV_PAVS_VOICE_CUR_PSH_SAMPLE 0x00000024
-# define NV_PAVS_VOICE_CUR_PSH_SAMPLE_LBO 0x00FFFFFF
-
-#define NV_PAVS_VOICE_CUR_ECNT 0x00000034
-# define NV_PAVS_VOICE_CUR_ECNT_EACOUNT 0x0000FFFF
-# define NV_PAVS_VOICE_CUR_ECNT_EFCOUNT 0xFFFF0000
-
-#define NV_PAVS_VOICE_PAR_STATE 0x00000054
-# define NV_PAVS_VOICE_PAR_STATE_PAUSED (1 << 18)
-# define NV_PAVS_VOICE_PAR_STATE_NEW_VOICE (1 << 20)
-# define NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE (1 << 21)
-# define NV_PAVS_VOICE_PAR_STATE_EFCUR (0xF << 24)
-# define NV_PAVS_VOICE_PAR_STATE_EACUR (0xF << 28)
-#define NV_PAVS_VOICE_PAR_OFFSET 0x00000058
-# define NV_PAVS_VOICE_PAR_OFFSET_CBO 0x00FFFFFF
-# define NV_PAVS_VOICE_PAR_OFFSET_EALVL 0xFF000000
-#define NV_PAVS_VOICE_PAR_NEXT 0x0000005C
-# define NV_PAVS_VOICE_PAR_NEXT_EBO 0x00FFFFFF
-# define NV_PAVS_VOICE_PAR_NEXT_EFLVL 0xFF000000
-#define NV_PAVS_VOICE_TAR_VOLA 0x00000060
-# define NV_PAVS_VOICE_TAR_VOLA_VOLUME6_B3_0 0x0000000F
-# define NV_PAVS_VOICE_TAR_VOLA_VOLUME0 0x0000FFF0
-# define NV_PAVS_VOICE_TAR_VOLA_VOLUME7_B3_0 0x000F0000
-# define NV_PAVS_VOICE_TAR_VOLA_VOLUME1 0xFFF00000
-#define NV_PAVS_VOICE_TAR_VOLB 0x00000064
-# define NV_PAVS_VOICE_TAR_VOLB_VOLUME6_B7_4 0x0000000F
-# define NV_PAVS_VOICE_TAR_VOLB_VOLUME2 0x0000FFF0
-# define NV_PAVS_VOICE_TAR_VOLB_VOLUME7_B7_4 0x000F0000
-# define NV_PAVS_VOICE_TAR_VOLB_VOLUME3 0xFFF00000
-#define NV_PAVS_VOICE_TAR_VOLC 0x00000068
-# define NV_PAVS_VOICE_TAR_VOLC_VOLUME6_B11_8 0x0000000F
-# define NV_PAVS_VOICE_TAR_VOLC_VOLUME4 0x0000FFF0
-# define NV_PAVS_VOICE_TAR_VOLC_VOLUME7_B11_8 0x000F0000
-# define NV_PAVS_VOICE_TAR_VOLC_VOLUME5 0xFFF00000
-#define NV_PAVS_VOICE_TAR_LFO_ENV 0x0000006C
-# define NV_PAVS_VOICE_TAR_LFO_ENV_EA_RELEASERATE (0xFFF << 0)
-
-#define NV_PAVS_VOICE_TAR_PITCH_LINK 0x0000007C
-# define NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE 0x0000FFFF
-# define NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH 0xFFFF0000
-
-
-#define GP_DSP_MIXBUF_BASE 0x001400
-
-#define GP_OUTPUT_FIFO_COUNT 4
-#define GP_INPUT_FIFO_COUNT 2
-
-#define EP_OUTPUT_FIFO_COUNT 4
-#define EP_INPUT_FIFO_COUNT 2
-
-#define MCPX_HW_MAX_VOICES 256
-
-#define GET_MASK(v, mask) (((v) & (mask)) >> ctz32(mask))
-
-#define SET_MASK(v, mask, val) \
- do { \
- (v) &= ~(mask); \
- (v) |= ((val) << ctz32(mask)) & (mask); \
- } while (0)
-
-#define CASE_4(v, step) \
- case (v): \
- case (v)+(step): \
- case (v)+(step)*2: \
- case (v)+(step)*3
-
-
-// #define MCPX_DEBUG
-#ifdef MCPX_DEBUG
-# define MCPX_DPRINTF(format, ...) printf(format, ## __VA_ARGS__)
-#else
-# define MCPX_DPRINTF(format, ...) do { } while (0)
-#endif
-
-/* More debug functionality */
-#define GENERATE_MIXBIN_BEEP 0
-
-typedef struct MCPXAPUState {
- PCIDevice dev;
-
- MemoryRegion *ram;
- uint8_t *ram_ptr;
-
- MemoryRegion mmio;
-
- /* Setup Engine */
- struct {
- QEMUTimer *frame_timer;
- } se;
-
- /* Voice Processor */
- struct {
- MemoryRegion mmio;
- } vp;
-
- /* Global Processor */
- struct {
- MemoryRegion mmio;
- DSPState *dsp;
- uint32_t regs[0x10000];
- } gp;
-
- /* Encode Processor */
- struct {
- MemoryRegion mmio;
- DSPState *dsp;
- uint32_t regs[0x10000];
- } ep;
-
- uint32_t inbuf_sge_handle; //FIXME: Where is this stored?
- uint32_t outbuf_sge_handle; //FIXME: Where is this stored?
- uint32_t regs[0x20000];
-
-} MCPXAPUState;
-
-#define MCPX_APU_DEVICE(obj) \
- OBJECT_CHECK(MCPXAPUState, (obj), "mcpx-apu")
-
-static uint32_t voice_get_mask(MCPXAPUState *d,
- unsigned int voice_handle,
- hwaddr offset,
- uint32_t mask)
-{
- assert(voice_handle < 0xFFFF);
- hwaddr voice = d->regs[NV_PAPU_VPVADDR]
- + voice_handle * NV_PAVS_SIZE;
- return (ldl_le_phys(&address_space_memory, voice + offset) & mask)
- >> ctz32(mask);
-}
-static void voice_set_mask(MCPXAPUState *d,
- unsigned int voice_handle,
- hwaddr offset,
- uint32_t mask,
- uint32_t val)
-{
- assert(voice_handle < 0xFFFF);
- hwaddr voice = d->regs[NV_PAPU_VPVADDR]
- + voice_handle * NV_PAVS_SIZE;
- uint32_t v = ldl_le_phys(&address_space_memory, voice + offset) & ~mask;
- stl_le_phys(&address_space_memory, voice + offset,
- v | ((val << ctz32(mask)) & mask));
-}
-
-static void update_irq(MCPXAPUState *d)
-{
- if ((d->regs[NV_PAPU_IEN] & NV_PAPU_ISTS_GINTSTS)
- && ((d->regs[NV_PAPU_ISTS] & ~NV_PAPU_ISTS_GINTSTS)
- & d->regs[NV_PAPU_IEN])) {
-
- d->regs[NV_PAPU_ISTS] |= NV_PAPU_ISTS_GINTSTS;
- MCPX_DPRINTF("mcpx irq raise\n");
- pci_irq_assert(&d->dev);
- } else {
- d->regs[NV_PAPU_ISTS] &= ~NV_PAPU_ISTS_GINTSTS;
- MCPX_DPRINTF("mcpx irq lower\n");
- pci_irq_deassert(&d->dev);
- }
-}
-
-static uint64_t mcpx_apu_read(void *opaque,
- hwaddr addr, unsigned int size)
-{
- MCPXAPUState *d = opaque;
-
- uint64_t r = 0;
- switch (addr) {
- case NV_PAPU_XGSCNT:
- r = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 100; //???
- break;
- default:
- if (addr < 0x20000) {
- r = d->regs[addr];
- }
- break;
- }
-
- MCPX_DPRINTF("mcpx apu: read [0x%llx] -> 0x%llx\n", addr, r);
- return r;
-}
-
-static void mcpx_apu_write(void *opaque, hwaddr addr,
- uint64_t val, unsigned int size)
-{
- MCPXAPUState *d = opaque;
-
- MCPX_DPRINTF("mcpx apu: [0x%llx] = 0x%llx\n", addr, val);
-
- switch (addr) {
- case NV_PAPU_ISTS:
- /* the bits of the interrupts to clear are wrtten */
- d->regs[NV_PAPU_ISTS] &= ~val;
- update_irq(d);
- break;
- case NV_PAPU_SECTL:
- if (((val & NV_PAPU_SECTL_XCNTMODE) >> 3)
- == NV_PAPU_SECTL_XCNTMODE_OFF) {
- timer_del(d->se.frame_timer);
- } else {
- timer_mod(d->se.frame_timer,
- qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 10);
- }
- d->regs[addr] = val;
- break;
- case NV_PAPU_FEMEMDATA:
- /* 'magic write'
- * This value is expected to be written to FEMEMADDR on completion of
- * something to do with notifies. Just do it now :/ */
- stl_le_phys(&address_space_memory, d->regs[NV_PAPU_FEMEMADDR], val);
- d->regs[addr] = val;
- break;
- default:
- if (addr < 0x20000) {
- d->regs[addr] = val;
- }
- break;
- }
-}
-
-static const MemoryRegionOps mcpx_apu_mmio_ops = {
- .read = mcpx_apu_read,
- .write = mcpx_apu_write,
-};
-
-static void fe_method(MCPXAPUState *d,
- uint32_t method, uint32_t argument)
-{
-#ifdef MCPX_DEBUG
- unsigned int slot;
-#endif
-
- MCPX_DPRINTF("mcpx fe_method 0x%x 0x%x\n", method, argument);
-
- //assert((d->regs[NV_PAPU_FECTL] & NV_PAPU_FECTL_FEMETHMODE) == 0);
-
- d->regs[NV_PAPU_FEDECMETH] = method;
- d->regs[NV_PAPU_FEDECPARAM] = argument;
- unsigned int selected_handle, list;
- switch (method) {
- case NV1BA0_PIO_SET_ANTECEDENT_VOICE:
- d->regs[NV_PAPU_FEAV] = argument;
- break;
- case NV1BA0_PIO_VOICE_ON:
- selected_handle = argument & NV1BA0_PIO_VOICE_ON_HANDLE;
- list = GET_MASK(d->regs[NV_PAPU_FEAV], NV_PAPU_FEAV_LST);
- if (list != NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_INHERIT) {
- /* voice is added to the top of the selected list */
- unsigned int top_reg = voice_list_regs[list - 1].top;
- voice_set_mask(d, selected_handle,
- NV_PAVS_VOICE_TAR_PITCH_LINK,
- NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE,
- d->regs[top_reg]);
- d->regs[top_reg] = selected_handle;
- } else {
- unsigned int antecedent_voice =
- GET_MASK(d->regs[NV_PAPU_FEAV], NV_PAPU_FEAV_VALUE);
- /* voice is added after the antecedent voice */
- assert(antecedent_voice != 0xFFFF);
-
- uint32_t next_handle = voice_get_mask(d, antecedent_voice,
- NV_PAVS_VOICE_TAR_PITCH_LINK,
- NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE);
- voice_set_mask(d, selected_handle,
- NV_PAVS_VOICE_TAR_PITCH_LINK,
- NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE,
- next_handle);
- voice_set_mask(d, antecedent_voice,
- NV_PAVS_VOICE_TAR_PITCH_LINK,
- NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE,
- selected_handle);
- }
- unsigned int ea_start = GET_MASK(argument, NV1BA0_PIO_VOICE_ON_ENVA);
- voice_set_mask(d, selected_handle,
- NV_PAVS_VOICE_PAR_STATE,
- NV_PAVS_VOICE_PAR_STATE_EACUR,
- ea_start);
- if (ea_start == 1) { // Delay
- uint16_t delay_time = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENV0, NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME);
- voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, NV_PAVS_VOICE_CUR_ECNT_EACOUNT, delay_time * 16);
- } else if (ea_start == 2) { // Attack
- voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, NV_PAVS_VOICE_CUR_ECNT_EACOUNT, 0x0000);
- } else if (ea_start == 3) { // Hold
- uint16_t hold_time = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENVA, NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME);
- voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, NV_PAVS_VOICE_CUR_ECNT_EACOUNT, hold_time * 16);
- }
- //FIXME: Will count be overwritten in other cases too?
-
- unsigned int ef_start = GET_MASK(argument, NV1BA0_PIO_VOICE_ON_ENVF);
- voice_set_mask(d, selected_handle,
- NV_PAVS_VOICE_PAR_STATE,
- NV_PAVS_VOICE_PAR_STATE_EFCUR,
- ef_start);
- if (ef_start == 1) { // Delay
- uint16_t delay_time = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENV1, NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME);
- voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, delay_time * 16);
- } else if (ef_start == 2) { // Attack
- voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, 0x0000);
- } else if (ef_start == 3) { // Hold
- uint16_t hold_time = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENVF, NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME);
- voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, hold_time * 16);
- }
- //FIXME: Will count be overwritten in other cases too?
-
- voice_set_mask(d, selected_handle,
- NV_PAVS_VOICE_PAR_STATE,
- NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE,
- 1);
- break;
- case NV1BA0_PIO_VOICE_OFF:
- voice_set_mask(d, argument & NV1BA0_PIO_VOICE_OFF_HANDLE,
- NV_PAVS_VOICE_PAR_STATE,
- NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE,
- 0);
- break;
- case NV1BA0_PIO_VOICE_PAUSE:
- voice_set_mask(d, argument & NV1BA0_PIO_VOICE_PAUSE_HANDLE,
- NV_PAVS_VOICE_PAR_STATE,
- NV_PAVS_VOICE_PAR_STATE_PAUSED,
- (argument & NV1BA0_PIO_VOICE_PAUSE_ACTION) != 0);
- break;
- case NV1BA0_PIO_SET_CURRENT_VOICE:
- d->regs[NV_PAPU_FECV] = argument;
- break;
- case NV1BA0_PIO_SET_VOICE_CFG_VBIN:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_CFG_VBIN,
- 0xFFFFFFFF,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_CFG_FMT:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_CFG_FMT,
- 0xFFFFFFFF,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_CFG_ENV0:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_CFG_ENV0,
- 0xFFFFFFFF,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_CFG_ENVA:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_CFG_ENVA,
- 0xFFFFFFFF,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_CFG_ENV1:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_CFG_ENV1,
- 0xFFFFFFFF,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_CFG_ENVF:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_CFG_ENVF,
- 0xFFFFFFFF,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_CFG_MISC:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_CFG_MISC,
- 0xFFFFFFFF,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_TAR_VOLA:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_TAR_VOLA,
- 0xFFFFFFFF,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_TAR_VOLB:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_TAR_VOLB,
- 0xFFFFFFFF,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_TAR_VOLC:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_TAR_VOLC,
- 0xFFFFFFFF,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_LFO_ENV:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_TAR_LFO_ENV,
- 0xFFFFFFFF,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_TAR_PITCH:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_TAR_PITCH_LINK,
- NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH,
- (argument & NV1BA0_PIO_SET_VOICE_TAR_PITCH_STEP) >> 16);
- break;
- case NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_CUR_PSL_START,
- NV_PAVS_VOICE_CUR_PSL_START_BA,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_CUR_PSH_SAMPLE,
- NV_PAVS_VOICE_CUR_PSH_SAMPLE_LBO,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_BUF_CBO:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_PAR_OFFSET,
- NV_PAVS_VOICE_PAR_OFFSET_CBO,
- argument);
- break;
- case NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO:
- voice_set_mask(d, d->regs[NV_PAPU_FECV],
- NV_PAVS_VOICE_PAR_NEXT,
- NV_PAVS_VOICE_PAR_NEXT_EBO,
- argument);
- break;
- case NV1BA0_PIO_SET_CURRENT_INBUF_SGE:
- d->inbuf_sge_handle = argument & NV1BA0_PIO_SET_CURRENT_INBUF_SGE_HANDLE;
- break;
- case NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET: {
- //FIXME: Is there an upper limit for the SGE table size?
- //FIXME: NV_PAPU_VPSGEADDR is probably bad, as outbuf SGE use the same handle range (or that is also wrong)
- hwaddr sge_address = d->regs[NV_PAPU_VPSGEADDR] + d->inbuf_sge_handle * 8;
- stl_le_phys(&address_space_memory, sge_address, argument & NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER);
- MCPX_DPRINTF("Wrote inbuf SGE[0x%X] = 0x%08X\n", d->inbuf_sge_handle, argument & NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER);
- break;
- }
- CASE_4(NV1BA0_PIO_SET_OUTBUF_BA, 8): // 8 byte pitch, 4 entries
-#ifdef MCPX_DEBUG
- slot = (method - NV1BA0_PIO_SET_OUTBUF_BA) / 8;
- //FIXME: Use NV1BA0_PIO_SET_OUTBUF_BA_ADDRESS = 0x007FFF00 ?
- MCPX_DPRINTF("outbuf_ba[%d]: 0x%08X\n", slot, argument);
-#endif
- //assert(false); //FIXME: Enable assert! no idea what this reg does
- break;
- CASE_4(NV1BA0_PIO_SET_OUTBUF_LEN, 8): // 8 byte pitch, 4 entries
-#ifdef MCPX_DEBUG
- slot = (method - NV1BA0_PIO_SET_OUTBUF_LEN) / 8;
- //FIXME: Use NV1BA0_PIO_SET_OUTBUF_LEN_VALUE = 0x007FFF00 ?
- MCPX_DPRINTF("outbuf_len[%d]: 0x%08X\n", slot, argument);
-#endif
- //assert(false); //FIXME: Enable assert! no idea what this reg does
- break;
- case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE:
- d->outbuf_sge_handle = argument & NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_HANDLE;
- break;
- case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET: {
- //FIXME: Is there an upper limit for the SGE table size?
- //FIXME: NV_PAPU_VPSGEADDR is probably bad, as inbuf SGE use the same handle range (or that is also wrong)
- // NV_PAPU_EPFADDR EP outbufs
- // NV_PAPU_GPFADDR GP outbufs
- // But how does it know which outbuf is being written?!
- hwaddr sge_address = d->regs[NV_PAPU_VPSGEADDR] + d->outbuf_sge_handle * 8;
- stl_le_phys(&address_space_memory, sge_address, argument & NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER);
- MCPX_DPRINTF("Wrote outbuf SGE[0x%X] = 0x%08X\n", d->outbuf_sge_handle, argument & NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER);
- break;
- }
- case SE2FE_IDLE_VOICE:
- if (d->regs[NV_PAPU_FETFORCE1] & NV_PAPU_FETFORCE1_SE2FE_IDLE_VOICE) {
-
- d->regs[NV_PAPU_FECTL] &= ~NV_PAPU_FECTL_FEMETHMODE;
- d->regs[NV_PAPU_FECTL] |= NV_PAPU_FECTL_FEMETHMODE_TRAPPED;
-
- d->regs[NV_PAPU_FECTL] &= ~NV_PAPU_FECTL_FETRAPREASON;
- d->regs[NV_PAPU_FECTL] |= NV_PAPU_FECTL_FETRAPREASON_REQUESTED;
-
- d->regs[NV_PAPU_ISTS] |= NV_PAPU_ISTS_FETINTSTS;
- update_irq(d);
- } else {
- assert(false);
- }
- break;
- default:
- assert(false);
- break;
- }
-}
-
-static uint64_t vp_read(void *opaque,
- hwaddr addr, unsigned int size)
-{
- MCPX_DPRINTF("mcpx apu VP: read [0x%llx]\n", addr);
- switch (addr) {
- case NV1BA0_PIO_FREE:
- /* we don't simulate the queue for now,
- * pretend to always be empty */
- return 0x80;
- default:
- break;
- }
- return 0;
-}
-
-static void vp_write(void *opaque, hwaddr addr,
- uint64_t val, unsigned int size)
-{
- MCPXAPUState *d = opaque;
-
- MCPX_DPRINTF("mcpx apu VP: [0x%llx] = 0x%llx\n", addr, val);
-
- switch (addr) {
- case NV1BA0_PIO_SET_ANTECEDENT_VOICE:
- case NV1BA0_PIO_VOICE_ON:
- case NV1BA0_PIO_VOICE_OFF:
- case NV1BA0_PIO_VOICE_PAUSE:
- case NV1BA0_PIO_SET_CURRENT_VOICE:
- case NV1BA0_PIO_SET_VOICE_CFG_VBIN:
- case NV1BA0_PIO_SET_VOICE_CFG_FMT:
- case NV1BA0_PIO_SET_VOICE_CFG_ENV0:
- case NV1BA0_PIO_SET_VOICE_CFG_ENVA:
- case NV1BA0_PIO_SET_VOICE_CFG_ENV1:
- case NV1BA0_PIO_SET_VOICE_CFG_ENVF:
- case NV1BA0_PIO_SET_VOICE_CFG_MISC:
- case NV1BA0_PIO_SET_VOICE_TAR_VOLA:
- case NV1BA0_PIO_SET_VOICE_TAR_VOLB:
- case NV1BA0_PIO_SET_VOICE_TAR_VOLC:
- case NV1BA0_PIO_SET_VOICE_LFO_ENV:
- case NV1BA0_PIO_SET_VOICE_TAR_PITCH:
- case NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE:
- case NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO:
- case NV1BA0_PIO_SET_VOICE_BUF_CBO:
- case NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO:
- case NV1BA0_PIO_SET_CURRENT_INBUF_SGE:
- case NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET:
- CASE_4(NV1BA0_PIO_SET_OUTBUF_BA, 8): // 8 byte pitch, 4 entries
- CASE_4(NV1BA0_PIO_SET_OUTBUF_LEN, 8): // 8 byte pitch, 4 entries
- case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE:
- case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET:
- /* TODO: these should instead be queueing up fe commands */
- fe_method(d, addr, val);
- break;
- default:
- break;
- }
-}
-
-static const MemoryRegionOps vp_ops = {
- .read = vp_read,
- .write = vp_write,
-};
-
-static void scatter_gather_rw(MCPXAPUState *d,
- hwaddr sge_base, unsigned int max_sge,
- uint8_t *ptr, uint32_t addr, size_t len,
- bool dir)
-{
- unsigned int page_entry = addr / TARGET_PAGE_SIZE;
- unsigned int offset_in_page = addr % TARGET_PAGE_SIZE;
- unsigned int bytes_to_copy = TARGET_PAGE_SIZE - offset_in_page;
-
- while (len > 0) {
- assert(page_entry <= max_sge);
-
- uint32_t prd_address = ldl_le_phys(&address_space_memory,
- sge_base + page_entry * 8 + 0);
- /* uint32_t prd_control = ldl_le_phys(&address_space_memory,
- sge_base + page_entry * 8 + 4); */
-
- hwaddr paddr = prd_address + offset_in_page;
-
- if (bytes_to_copy > len) {
- bytes_to_copy = len;
- }
-
- assert(paddr + bytes_to_copy < memory_region_size(d->ram));
-
- if (dir) {
- memcpy(&d->ram_ptr[paddr], ptr, bytes_to_copy);
- memory_region_set_dirty(d->ram, paddr, bytes_to_copy);
- } else {
- memcpy(ptr, &d->ram_ptr[paddr], bytes_to_copy);
- }
-
- ptr += bytes_to_copy;
- len -= bytes_to_copy;
-
- /* After the first iteration, we are page aligned */
- page_entry += 1;
- bytes_to_copy = TARGET_PAGE_SIZE;
- offset_in_page = 0;
- }
-}
-
-static void gp_scratch_rw(void *opaque,
- uint8_t *ptr,
- uint32_t addr,
- size_t len,
- bool dir)
-{
- MCPXAPUState *d = opaque;
- scatter_gather_rw(d, d->regs[NV_PAPU_GPSADDR], d->regs[NV_PAPU_GPSMAXSGE],
- ptr, addr, len, dir);
-}
-
-static void ep_scratch_rw(void *opaque,
- uint8_t *ptr,
- uint32_t addr,
- size_t len,
- bool dir)
-{
- MCPXAPUState *d = opaque;
- scatter_gather_rw(d, d->regs[NV_PAPU_EPSADDR], d->regs[NV_PAPU_EPSMAXSGE],
- ptr, addr, len, dir);
-}
-
-static uint32_t circular_scatter_gather_rw(MCPXAPUState *d,
- hwaddr sge_base,
- unsigned int max_sge,
- uint8_t *ptr,
- uint32_t base, uint32_t end,
- uint32_t cur,
- size_t len,
- bool dir)
-{
- while (len > 0) {
- unsigned int bytes_to_copy = end - cur;
-
- if (bytes_to_copy > len) {
- bytes_to_copy = len;
- }
-
- MCPX_DPRINTF("circular scatter gather %s in range 0x%x - 0x%x at 0x%x of length 0x%x / 0x%lx bytes\n",
- dir ? "write" : "read", base, end, cur, bytes_to_copy, len);
-
- assert((cur >= base) && ((cur + bytes_to_copy) <= end));
- scatter_gather_rw(d, sge_base, max_sge, ptr, cur, bytes_to_copy, dir);
-
- ptr += bytes_to_copy;
- len -= bytes_to_copy;
-
- /* After the first iteration we might have to wrap */
- cur += bytes_to_copy;
- if (cur >= end) {
- assert(cur == end);
- cur = base;
- }
- }
-
- return cur;
-}
-
-static void gp_fifo_rw(void *opaque, uint8_t *ptr,
- unsigned int index, size_t len,
- bool dir)
-{
- MCPXAPUState *d = opaque;
- uint32_t base;
- uint32_t end;
- hwaddr cur_reg;
- if (dir) {
- assert(index < GP_OUTPUT_FIFO_COUNT);
- base = GET_MASK(d->regs[NV_PAPU_GPOFBASE0 + 0x10 * index],
- NV_PAPU_GPOFBASE0_VALUE);
- end = GET_MASK(d->regs[NV_PAPU_GPOFEND0 + 0x10 * index],
- NV_PAPU_GPOFEND0_VALUE);
- cur_reg = NV_PAPU_GPOFCUR0 + 0x10 * index;
- } else {
- assert(index < GP_INPUT_FIFO_COUNT);
- base = GET_MASK(d->regs[NV_PAPU_GPIFBASE0 + 0x10 * index],
- NV_PAPU_GPOFBASE0_VALUE);
- end = GET_MASK(d->regs[NV_PAPU_GPIFEND0 + 0x10 * index],
- NV_PAPU_GPOFEND0_VALUE);
- cur_reg = NV_PAPU_GPIFCUR0 + 0x10 * index;
- }
-
- uint32_t cur = GET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE);
-
- /* DSP hangs if current >= end; but forces current >= base */
- assert(cur < end);
- if (cur < base) {
- cur = base;
- }
-
- cur = circular_scatter_gather_rw(d,
- d->regs[NV_PAPU_GPFADDR], d->regs[NV_PAPU_GPFMAXSGE],
- ptr, base, end, cur, len, dir);
-
- SET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE, cur);
-}
-
-static void ep_fifo_rw(void *opaque, uint8_t *ptr,
- unsigned int index, size_t len,
- bool dir)
-{
- MCPXAPUState *d = opaque;
- uint32_t base;
- uint32_t end;
- hwaddr cur_reg;
- if (dir) {
- assert(index < EP_OUTPUT_FIFO_COUNT);
- base = GET_MASK(d->regs[NV_PAPU_EPOFBASE0 + 0x10 * index],
- NV_PAPU_GPOFBASE0_VALUE);
- end = GET_MASK(d->regs[NV_PAPU_EPOFEND0 + 0x10 * index],
- NV_PAPU_GPOFEND0_VALUE);
- cur_reg = NV_PAPU_EPOFCUR0 + 0x10 * index;
- } else {
- assert(index < EP_INPUT_FIFO_COUNT);
- base = GET_MASK(d->regs[NV_PAPU_EPIFBASE0 + 0x10 * index],
- NV_PAPU_GPOFBASE0_VALUE);
- end = GET_MASK(d->regs[NV_PAPU_EPIFEND0 + 0x10 * index],
- NV_PAPU_GPOFEND0_VALUE);
- cur_reg = NV_PAPU_EPIFCUR0 + 0x10 * index;
- }
-
- uint32_t cur = GET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE);
-
- /* DSP hangs if current >= end; but forces current >= base */
- assert(cur < end);
- if (cur < base) {
- cur = base;
- }
-
- cur = circular_scatter_gather_rw(d,
- d->regs[NV_PAPU_EPFADDR], d->regs[NV_PAPU_EPFMAXSGE],
- ptr, base, end, cur, len, dir);
-
- SET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE, cur);
-}
-
-static void proc_rst_write(DSPState *dsp, uint32_t oldval, uint32_t val)
-{
- if (!(val & NV_PAPU_GPRST_GPRST) || !(val & NV_PAPU_GPRST_GPDSPRST)) {
- dsp_reset(dsp);
- } else if (
- (!(oldval & NV_PAPU_GPRST_GPRST) || !(oldval & NV_PAPU_GPRST_GPDSPRST))
- && ((val & NV_PAPU_GPRST_GPRST) && (val & NV_PAPU_GPRST_GPDSPRST))) {
- dsp_bootstrap(dsp);
- }
-}
-
-/* Global Processor - programmable DSP */
-static uint64_t gp_read(void *opaque,
- hwaddr addr,
- unsigned int size)
-{
- MCPXAPUState *d = opaque;
-
- assert(size == 4);
- assert(addr % 4 == 0);
-
- uint64_t r = 0;
- switch (addr) {
- case NV_PAPU_GPXMEM ... NV_PAPU_GPXMEM + 0x1000 * 4 - 1: {
- uint32_t xaddr = (addr - NV_PAPU_GPXMEM) / 4;
- r = dsp_read_memory(d->gp.dsp, 'X', xaddr);
- break;
- }
- case NV_PAPU_GPMIXBUF ... NV_PAPU_GPMIXBUF + 0x400 * 4 - 1: {
- uint32_t xaddr = (addr - NV_PAPU_GPMIXBUF) / 4;
- r = dsp_read_memory(d->gp.dsp, 'X', GP_DSP_MIXBUF_BASE + xaddr);
- break;
- }
- case NV_PAPU_GPYMEM ... NV_PAPU_GPYMEM + 0x800 * 4 - 1: {
- uint32_t yaddr = (addr - NV_PAPU_GPYMEM) / 4;
- r = dsp_read_memory(d->gp.dsp, 'Y', yaddr);
- break;
- }
- case NV_PAPU_GPPMEM ... NV_PAPU_GPPMEM + 0x1000 * 4 - 1: {
- uint32_t paddr = (addr - NV_PAPU_GPPMEM) / 4;
- r = dsp_read_memory(d->gp.dsp, 'P', paddr);
- break;
- }
- default:
- r = d->gp.regs[addr];
- break;
- }
- MCPX_DPRINTF("mcpx apu GP: read [0x%llx] -> 0x%llx\n", addr, r);
- return r;
-}
-
-static void gp_write(void *opaque, hwaddr addr,
- uint64_t val, unsigned int size)
-{
- MCPXAPUState *d = opaque;
-
- assert(size == 4);
- assert(addr % 4 == 0);
-
- MCPX_DPRINTF("mcpx apu GP: [0x%llx] = 0x%llx\n", addr, val);
-
- switch (addr) {
- case NV_PAPU_GPXMEM ... NV_PAPU_GPXMEM + 0x1000 * 4 - 1: {
- uint32_t xaddr = (addr - NV_PAPU_GPXMEM) / 4;
- dsp_write_memory(d->gp.dsp, 'X', xaddr, val);
- break;
- }
- case NV_PAPU_GPMIXBUF ... NV_PAPU_GPMIXBUF + 0x400 * 4 - 1: {
- uint32_t xaddr = (addr - NV_PAPU_GPMIXBUF) / 4;
- dsp_write_memory(d->gp.dsp, 'X', GP_DSP_MIXBUF_BASE + xaddr, val);
- break;
- }
- case NV_PAPU_GPYMEM ... NV_PAPU_GPYMEM + 0x800 * 4 - 1: {
- uint32_t yaddr = (addr - NV_PAPU_GPYMEM) / 4;
- dsp_write_memory(d->gp.dsp, 'Y', yaddr, val);
- break;
- }
- case NV_PAPU_GPPMEM ... NV_PAPU_GPPMEM + 0x1000 * 4 - 1: {
- uint32_t paddr = (addr - NV_PAPU_GPPMEM) / 4;
- dsp_write_memory(d->gp.dsp, 'P', paddr, val);
- break;
- }
- case NV_PAPU_GPRST:
- proc_rst_write(d->gp.dsp, d->gp.regs[NV_PAPU_GPRST], val);
- d->gp.regs[NV_PAPU_GPRST] = val;
- break;
- default:
- d->gp.regs[addr] = val;
- break;
- }
-}
-
-static const MemoryRegionOps gp_ops = {
- .read = gp_read,
- .write = gp_write,
-};
-
-/* Encode Processor - encoding DSP */
-static uint64_t ep_read(void *opaque,
- hwaddr addr,
- unsigned int size)
-{
- MCPXAPUState *d = opaque;
-
- assert(size == 4);
- assert(addr % 4 == 0);
-
- uint64_t r = 0;
- switch (addr) {
- case NV_PAPU_EPXMEM ... NV_PAPU_EPXMEM + 0xC00 * 4 - 1: {
- uint32_t xaddr = (addr - NV_PAPU_EPXMEM) / 4;
- r = dsp_read_memory(d->ep.dsp, 'X', xaddr);
- break;
- }
- case NV_PAPU_EPYMEM ... NV_PAPU_EPYMEM + 0x100 * 4 - 1: {
- uint32_t yaddr = (addr - NV_PAPU_EPYMEM) / 4;
- r = dsp_read_memory(d->ep.dsp, 'Y', yaddr);
- break;
- }
- case NV_PAPU_EPPMEM ... NV_PAPU_EPPMEM + 0x1000 * 4 - 1: {
- uint32_t paddr = (addr - NV_PAPU_EPPMEM) / 4;
- r = dsp_read_memory(d->ep.dsp, 'P', paddr);
- break;
- }
- default:
- r = d->ep.regs[addr];
- break;
- }
- MCPX_DPRINTF("mcpx apu EP: read [0x%llx] -> 0x%llx\n", addr, r);
- return r;
-}
-
-static void ep_write(void *opaque, hwaddr addr,
- uint64_t val, unsigned int size)
-{
- MCPXAPUState *d = opaque;
-
- assert(size == 4);
- assert(addr % 4 == 0);
-
- MCPX_DPRINTF("mcpx apu EP: [0x%llx] = 0x%llx\n", addr, val);
-
- switch (addr) {
- case NV_PAPU_EPXMEM ... NV_PAPU_EPXMEM + 0xC00 * 4 - 1: {
- uint32_t xaddr = (addr - NV_PAPU_EPXMEM) / 4;
- dsp_write_memory(d->ep.dsp, 'X', xaddr, val);
- break;
- }
- case NV_PAPU_EPYMEM ... NV_PAPU_EPYMEM + 0x100 * 4 - 1: {
- uint32_t yaddr = (addr - NV_PAPU_EPYMEM) / 4;
- dsp_write_memory(d->ep.dsp, 'Y', yaddr, val);
- break;
- }
- case NV_PAPU_EPPMEM ... NV_PAPU_EPPMEM + 0x1000 * 4 - 1: {
- uint32_t paddr = (addr - NV_PAPU_EPPMEM) / 4;
- dsp_write_memory(d->ep.dsp, 'P', paddr, val);
- break;
- }
- case NV_PAPU_EPRST:
- proc_rst_write(d->ep.dsp, d->ep.regs[NV_PAPU_EPRST], val);
- d->ep.regs[NV_PAPU_EPRST] = val;
- break;
- default:
- d->ep.regs[addr] = val;
- break;
- }
-}
-
-static const MemoryRegionOps ep_ops = {
- .read = ep_read,
- .write = ep_write,
-};
-
-#if 0
-#include "adpcm_block.h"
-
-static hwaddr get_data_ptr(hwaddr sge_base, unsigned int max_sge, uint32_t addr) {
- unsigned int entry = addr / TARGET_PAGE_SIZE;
- assert(entry <= max_sge);
- uint32_t prd_address = ldl_le_phys(&address_space_memory, sge_base + entry*4*2);
- // uint32_t prd_control = ldl_le_phys(&address_space_memory, sge_base + entry*4*2 + 4);
- MCPX_DPRINTF("Addr: 0x%08X, control: 0x%08X\n", prd_address, prd_control);
-
- return prd_address + addr % TARGET_PAGE_SIZE;
-}
-
-static float step_envelope(MCPXAPUState *d, unsigned int v, uint32_t reg_0, uint32_t reg_a, uint32_t rr_reg, uint32_t rr_mask, uint32_t lvl_reg, uint32_t lvl_mask, uint32_t count_mask, uint32_t cur_mask) {
- uint8_t cur = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask);
- switch(cur) {
- case 0: // Off
- voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, 0);
- voice_set_mask(d, v, lvl_reg, lvl_mask, 0xFF);
- return 1.0f;
- case 1: { // Delay
- uint16_t count = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
- voice_set_mask(d, v, lvl_reg, lvl_mask, 0x00); // FIXME: Confirm this?
- if (count == 0) {
- cur++;
- voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
- count = 0;
- } else {
- count--;
- }
- voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
- break;
- }
- case 2: { // Attack
- uint16_t count = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
- uint16_t attack_rate = voice_get_mask(d, v, reg_0, NV_PAVS_VOICE_CFG_ENV0_EA_ATTACKRATE);
- float value;
- if (attack_rate == 0) {
- //FIXME: [division by zero]
- // Got crackling sound in hardware for amplitude env.
- value = 255.0f;
- } else {
- if (count <= attack_rate) {
- value = (count * 0xFF) / attack_rate;
- } else {
- //FIXME: Overflow in hardware
- // The actual value seems to overflow, but not sure how
- value = 255.0f;
- }
- }
- voice_set_mask(d, v, lvl_reg, lvl_mask, value);
- //FIXME: Comparison could also be the other way around?! Test please.
- if (count == (attack_rate * 16)) {
- cur++;
- voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
- uint16_t hold_time = voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME);
- count = hold_time * 16; //FIXME: Skip next phase if count is 0? [other instances too]
- } else {
- count++;
- }
- voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
- return value / 255.0f;
- }
- case 3: { // Hold
- uint16_t count = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
- voice_set_mask(d, v, lvl_reg, lvl_mask, 0xFF);
- if (count == 0) {
- cur++;
- voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
- uint16_t decay_rate = voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE);
- count = decay_rate * 16;
- } else {
- count--;
- }
- voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
- return 1.0f;
- }
- case 4: { // Decay
- uint16_t count = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
- uint16_t decay_rate = voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE);
- uint8_t sustain_level = voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL);
- float value;
- if (decay_rate == 0) {
- //FIXME: [division by zero]
- // Not tested on hardware
- value = 0.0f;
- } else {
- //FIXME: This formula and threshold is not accurate, but I can't get it any better for now
- value = 255.0f * powf(0.99988799f, (decay_rate * 16 - count) * 4096 / decay_rate);
- }
- if (value <= (sustain_level + 0.2f) || (value > 255.0f)) {
- //FIXME: Should we still update lvl?
- cur++;
- voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
- } else {
- count--;
- voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
- voice_set_mask(d, v, lvl_reg, lvl_mask, value);
- }
- return value / 255.0f;
- }
- case 5: { // Sustain
- uint8_t sustain_level = voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL);
- voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, 0x00); // FIXME: is this only set to 0 once or forced to zero?
- voice_set_mask(d, v, lvl_reg, lvl_mask, sustain_level);
- return sustain_level / 255.0f;
- }
- case 6: { // Release
- uint16_t release_rate = voice_get_mask(d, v, rr_reg, rr_mask);
- uint16_t count = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
- count--;
- voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
- uint8_t lvl = voice_get_mask(d, v, lvl_reg, lvl_mask);
- float value = count * lvl / (release_rate * 16);
- if (count == 0) {
- //FIXME: What to do now?!
-#if 0 // Hack so we don't assert
- cur++; // FIXME: Does this happen?!
- voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
-#else
- voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, 0x0); // Is this correct? FIXME: Turn off voice?
-#endif
- }
- return value / 255.0f;
- }
- case 7: // Force release
- assert(false); //FIXME: This mode is not understood yet
- return 1.0f;
- default:
- fprintf(stderr, "Unknown envelope state 0x%x\n", cur);
- assert(false);
- }
-
- return 0;
-}
-#endif
-
-static void process_voice(MCPXAPUState *d,
- int32_t mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME],
- uint32_t voice)
-{
-#if 0
- uint32_t v = voice;
- int32_t samples[2][0x20] = {0};
-
- float ea_value = step_envelope(d, v, NV_PAVS_VOICE_CFG_ENV0, NV_PAVS_VOICE_CFG_ENVA, NV_PAVS_VOICE_TAR_LFO_ENV, NV_PAVS_VOICE_TAR_LFO_ENV_EA_RELEASERATE, NV_PAVS_VOICE_PAR_OFFSET, NV_PAVS_VOICE_PAR_OFFSET_EALVL, NV_PAVS_VOICE_CUR_ECNT_EACOUNT, NV_PAVS_VOICE_PAR_STATE_EACUR);
- float ef_value = step_envelope(d, v, NV_PAVS_VOICE_CFG_ENV1, NV_PAVS_VOICE_CFG_ENVF, NV_PAVS_VOICE_CFG_MISC, NV_PAVS_VOICE_CFG_MISC_EF_RELEASERATE, NV_PAVS_VOICE_PAR_NEXT, NV_PAVS_VOICE_PAR_NEXT_EFLVL, NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, NV_PAVS_VOICE_PAR_STATE_EFCUR);
- assert(ea_value >= 0.0f);
- assert(ea_value <= 1.0f);
- assert(ef_value >= 0.0f);
- assert(ef_value <= 1.0f);
-
- int16_t p = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_PITCH_LINK, NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH);
- int8_t pm = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_ENV0, NV_PAVS_VOICE_CFG_ENV0_EF_PITCHSCALE);
- float rate = powf(2.0f, (p + pm * 32 * ef_value) / 4096.0f);
- MCPX_DPRINTF("Got %f\n", rate * 48000.0f);
-
- float overdrive = 1.0f; //FIXME: This is just a hack because our APU runs too rarely
-
- //NV_PAVS_VOICE_PAR_OFFSET_CBO
- bool stereo = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_STEREO);
- unsigned int channels = stereo ? 2 : 1;
- unsigned int sample_size = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE);
-
- // B8, B16, ADPCM, B32
- unsigned int container_sizes[4] = { 1, 2, 0, 4 };
- unsigned int container_size_index = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE);
- //FIXME: Move down, but currently debug code depends on this
- unsigned int container_size = container_sizes[container_size_index];
-
- bool stream = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_DATA_TYPE);
- assert(!stream);
- bool paused = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, NV_PAVS_VOICE_PAR_STATE_PAUSED);
- bool loop = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_LOOP);
- uint32_t ebo = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_NEXT, NV_PAVS_VOICE_PAR_NEXT_EBO);
- uint32_t cbo = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_OFFSET, NV_PAVS_VOICE_PAR_OFFSET_CBO);
- uint32_t lbo = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_PSH_SAMPLE, NV_PAVS_VOICE_CUR_PSH_SAMPLE_LBO);
- uint32_t ba = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_PSL_START, NV_PAVS_VOICE_CUR_PSL_START_BA);
- unsigned int samples_per_block = 1 + voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_SAMPLES_PER_BLOCK);
-
- // This is probably cleared when the first sample is played
- //FIXME: How will this behave if CBO > EBO on first play?
- //FIXME: How will this behave if paused?
- voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, NV_PAVS_VOICE_PAR_STATE_NEW_VOICE, 0);
-
- for(unsigned int i = 0; i < 0x20; i++) {
-
- uint32_t sample_pos = (uint32_t)(i * rate * overdrive);
-
- if ((cbo + sample_pos) > ebo) {
- if (!loop) {
- // Set to safe state
- cbo = ebo; //FIXME: Will the hw do this?
- //FIXME: Not sure if this happens.. needs a hwtest.
- // Some RE also suggests that the voices will automaticly be removed from the list (!!!)
- voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE, 0);
- break;
- }
- // Now go to loop start
- //FIXME: Make sure this logic still works for very high sample_pos greater than ebo
- cbo += sample_pos;
- cbo %= ebo + 1;
- cbo += lbo;
- cbo -= sample_pos;
- }
-
- sample_pos += cbo;
- assert(sample_pos <= ebo);
-
- if (container_size_index == NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_ADPCM) {
- //FIXME: Not sure how this behaves otherwise
- assert(sample_size == NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24);
-
- unsigned int block_index = sample_pos / 65;
- unsigned int block_position = sample_pos % 65;
-
- MCPX_DPRINTF("ADPCM: %d + %d\n", block_index, block_position);
-
- //FIXME: Remove this from the loop which collects required samples
- // We can always just grab one or two blocks to get all required samples
- if (stereo) {
- assert(samples_per_block == 2);
- // There are 65 samples per 72 byte block
- uint32_t block[72/4];
- uint32_t linear_addr = ba + block_index * 72;
- // FIXME: Only load block data which will be used
- for(unsigned int word_index = 0; word_index < 18; word_index++) {
- hwaddr addr = get_data_ptr(d->regs[NV_PAPU_VPSGEADDR], 0xFFFFFFFF, linear_addr);
- block[word_index] = ldl_le_phys(&address_space_memory, addr);
- linear_addr += 4;
- }
- //FIXME: Used wrong sample format in my own decoder.. stupid me lol
- int16_t tmp[2];
- adpcm_decode_stereo_block(&tmp[0], &tmp[1], (uint8_t*)block, block_position, block_position);
- samples[0][i] = tmp[0];
- samples[1][i] = tmp[1];
- } else {
- assert(samples_per_block == 1);
- // There are 65 samples per 36 byte block
- uint32_t block[36/4];
- uint32_t linear_addr = ba + block_index * 36;
- for(unsigned int word_index = 0; word_index < 9; word_index++) {
- hwaddr addr = get_data_ptr(d->regs[NV_PAPU_VPSGEADDR], 0xFFFFFFFF, linear_addr);
- block[word_index] = ldl_le_phys(&address_space_memory, addr);
- linear_addr += 4;
- }
- //FIXME: Used wrong sample format in my own decoder.. stupid me lol
- int16_t tmp;
- adpcm_decode_mono_block(&tmp, (uint8_t*)block, block_position, block_position);
- samples[0][i] = tmp;
- }
-
- } else {
- unsigned int block_size = container_size;
- block_size *= samples_per_block;
-
- uint32_t linear_addr = ba + sample_pos * block_size;
- hwaddr addr = get_data_ptr(d->regs[NV_PAPU_VPSGEADDR], 0xFFFFFFFF, linear_addr);
- //FIXME: Handle reading accross pages?!
-
- MCPX_DPRINTF("Sampling from 0x%08X\n", addr);
-
- // Get samples for this voice
- for(unsigned int channel = 0; channel < channels; channel++) {
- switch(sample_size) {
- case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_U8:
- samples[channel][i] = ldub_phys(&address_space_memory, addr);
- break;
- case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S16:
- samples[channel][i] = (int16_t)lduw_le_phys(&address_space_memory, addr);
- break;
- case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24:
- samples[channel][i] = (int32_t)(ldl_le_phys(&address_space_memory, addr) << 8) >> 8;
- break;
- case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S32:
- samples[channel][i] = (int32_t)ldl_le_phys(&address_space_memory, addr);
- break;
- }
-
- // Advance cursor to second channel for stereo
- addr += container_size;
- }
- }
- }
-
- //FIXME: Decode voice volume and bins
- int bin[8] = {
- voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, NV_PAVS_VOICE_CFG_VBIN_V0BIN),
- voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, NV_PAVS_VOICE_CFG_VBIN_V1BIN),
- voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, NV_PAVS_VOICE_CFG_VBIN_V2BIN),
- voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, NV_PAVS_VOICE_CFG_VBIN_V3BIN),
- voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, NV_PAVS_VOICE_CFG_VBIN_V4BIN),
- voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, NV_PAVS_VOICE_CFG_VBIN_V5BIN),
- voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_V6BIN),
- voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_V7BIN)
- };
- uint16_t vol[8] = {
- voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, NV_PAVS_VOICE_TAR_VOLA_VOLUME0),
- voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, NV_PAVS_VOICE_TAR_VOLA_VOLUME1),
- voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, NV_PAVS_VOICE_TAR_VOLB_VOLUME2),
- voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, NV_PAVS_VOICE_TAR_VOLB_VOLUME3),
- voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, NV_PAVS_VOICE_TAR_VOLC_VOLUME4),
- voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, NV_PAVS_VOICE_TAR_VOLC_VOLUME5),
- (voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, NV_PAVS_VOICE_TAR_VOLC_VOLUME6_B11_8) << 8) |
- (voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, NV_PAVS_VOICE_TAR_VOLB_VOLUME6_B7_4) << 4) |
- voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, NV_PAVS_VOICE_TAR_VOLA_VOLUME6_B3_0),
- (voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, NV_PAVS_VOICE_TAR_VOLC_VOLUME7_B11_8) << 8) |
- (voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, NV_PAVS_VOICE_TAR_VOLB_VOLUME7_B7_4) << 4) |
- voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, NV_PAVS_VOICE_TAR_VOLA_VOLUME7_B3_0),
- };
-
- if (paused) {
- assert(sizeof(samples) == 0x20 * 4 * 2);
- memset(samples, 0x00, sizeof(samples));
- } else {
- cbo += 0x20 * rate * overdrive;
- voice_set_mask(d, v, NV_PAVS_VOICE_PAR_OFFSET, NV_PAVS_VOICE_PAR_OFFSET_CBO, cbo);
- }
-
- // Apply the amplitude envelope
- //FIXME: Figure out when exactly and how exactly this is actually done
- for(unsigned int j = 0; j < channels; j++) {
- for(unsigned int i = 0; i < 0x20; i++) {
- samples[j][i] *= ea_value;
- }
- }
-
- //FIXME: If phase negations means to flip the signal upside down
- // we should modify volume of bin6 and bin7 here.
-
- // Mix samples into voice bins
- for(unsigned int j = 0; j < 8; j++) {
- MCPX_DPRINTF("Adding voice 0x%04X to bin %d [Rate %.2f, Volume 0x%03X] sample %d at %d [%.2fs]\n", v, bin[j], rate, vol[j], samples[0], cbo, cbo / (rate * 48000.0f));
- for(unsigned int i = 0; i < 0x20; i++) {
- //FIXME: how is the volume added?
- //FIXME: What happens to the other channel? Is this behaviour correct?
- mixbins[bin[j]][i] += (0xFFF - vol[j]) * samples[j % channels][i] / 0xFFF;
- }
- }
-#endif
-}
-
-/* This routine must run at 1500 Hz */
-/* TODO: this should be on a thread so it waits on the voice lock */
-static void se_frame(void *opaque)
-{
- MCPXAPUState *d = opaque;
- int mixbin;
- int sample;
-
- timer_mod(d->se.frame_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 10);
- MCPX_DPRINTF("mcpx frame ping\n");
-
- /* Buffer for all mixbins for this frame */
- int32_t mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME] = { 0 };
-
- /* Process all voices, mixing each into the affected MIXBINs */
- int list;
- for (list = 0; list < 3; list++) {
- hwaddr top, current, next;
- top = voice_list_regs[list].top;
- current = voice_list_regs[list].current;
- next = voice_list_regs[list].next;
-
- d->regs[current] = d->regs[top];
- MCPX_DPRINTF("list %d current voice %d\n", list, d->regs[current]);
- while (d->regs[current] != 0xFFFF) {
- d->regs[next] = voice_get_mask(d, d->regs[current],
- NV_PAVS_VOICE_TAR_PITCH_LINK,
- NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE);
- if (!voice_get_mask(d, d->regs[current],
- NV_PAVS_VOICE_PAR_STATE,
- NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE)) {
- MCPX_DPRINTF("voice %d not active...!\n", d->regs[current]);
- fe_method(d, SE2FE_IDLE_VOICE, d->regs[current]);
- } else {
- process_voice(d, mixbins, d->regs[current]);
- }
- MCPX_DPRINTF("next voice %d\n", d->regs[next]);
- d->regs[current] = d->regs[next];
- }
- }
-
-#if GENERATE_MIXBIN_BEEP
- /* Inject some audio to the mixbin for debugging.
- * Signal is 1500 Hz sine wave, phase shifted by mixbin number. */
- for (mixbin = 0; mixbin < NUM_MIXBINS; mixbin++) {
- for (sample = 0; sample < NUM_SAMPLES_PER_FRAME; sample++) {
- /* Avoid multiple of 1.0 / NUM_SAMPLES_PER_FRAME for phase shift,
- * or waves cancel out */
- float offset = sample / (float)NUM_SAMPLES_PER_FRAME -
- mixbin / (float)(NUM_SAMPLES_PER_FRAME + 1);
- float wave = sinf(offset * M_PI * 2.0f);
- mixbins[mixbin][sample] += wave * 0x3FFFFF;
- }
- }
-#endif
-
- /* Write VP results to the GP DSP MIXBUF */
- for (mixbin = 0; mixbin < NUM_MIXBINS; mixbin++) {
- for (sample = 0; sample < NUM_SAMPLES_PER_FRAME; sample++) {
- dsp_write_memory(d->gp.dsp,
- 'X', GP_DSP_MIXBUF_BASE + mixbin * 0x20 + sample,
- mixbins[mixbin][sample] & 0xFFFFFF);
- }
- }
-
- /* Kickoff DSP processing */
- if ((d->gp.regs[NV_PAPU_GPRST] & NV_PAPU_GPRST_GPRST)
- && (d->gp.regs[NV_PAPU_GPRST] & NV_PAPU_GPRST_GPDSPRST)) {
- dsp_start_frame(d->gp.dsp);
-
- // hax
- dsp_run(d->gp.dsp, 1000);
- }
- if ((d->ep.regs[NV_PAPU_EPRST] & NV_PAPU_GPRST_GPRST)
- && (d->ep.regs[NV_PAPU_EPRST] & NV_PAPU_GPRST_GPDSPRST)) {
- dsp_start_frame(d->ep.dsp);
-
- // hax
- // dsp_run(d->ep.dsp, 1000);
- }
-}
-
-static void mcpx_apu_realize(PCIDevice *dev, Error **errp)
-{
- MCPXAPUState *d = MCPX_APU_DEVICE(dev);
-
- dev->config[PCI_INTERRUPT_PIN] = 0x01;
-
- memory_region_init_io(&d->mmio, OBJECT(dev), &mcpx_apu_mmio_ops, d,
- "mcpx-apu-mmio", 0x80000);
-
- memory_region_init_io(&d->vp.mmio, OBJECT(dev), &vp_ops, d,
- "mcpx-apu-vp", 0x10000);
- memory_region_add_subregion(&d->mmio, 0x20000, &d->vp.mmio);
-
- memory_region_init_io(&d->gp.mmio, OBJECT(dev), &gp_ops, d,
- "mcpx-apu-gp", 0x10000);
- memory_region_add_subregion(&d->mmio, 0x30000, &d->gp.mmio);
-
- memory_region_init_io(&d->ep.mmio, OBJECT(dev), &ep_ops, d,
- "mcpx-apu-ep", 0x10000);
- memory_region_add_subregion(&d->mmio, 0x50000, &d->ep.mmio);
-
- pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
-
-
- d->se.frame_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, se_frame, d);
- d->gp.dsp = dsp_init(d, gp_scratch_rw, gp_fifo_rw);
- d->ep.dsp = dsp_init(d, ep_scratch_rw, ep_fifo_rw);
-}
-
-static int mcpx_apu_pre_load(void *opaque)
-{
- MCPXAPUState *d = opaque;
-
- timer_del(d->se.frame_timer);
-
- return 0;
-}
-
-static int mcpx_apu_post_load(void *opaque, int version_id)
-{
- MCPXAPUState *d = opaque;
-
- if (((d->regs[NV_PAPU_SECTL] & NV_PAPU_SECTL_XCNTMODE) >> 3) != NV_PAPU_SECTL_XCNTMODE_OFF) {
- timer_mod(d->se.frame_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 10);
- }
-
- return 0;
-}
-
-const VMStateDescription vmstate_vp_dsp_dma_state = {
- .name = "mcpx-apu/dsp-state/dma",
- .version_id = 1,
- .minimum_version_id = 1,
- .minimum_version_id_old = 1,
- .fields = (VMStateField[]) {
- VMSTATE_UINT32(configuration, DSPDMAState),
- VMSTATE_UINT32(control, DSPDMAState),
- VMSTATE_UINT32(start_block, DSPDMAState),
- VMSTATE_UINT32(next_block, DSPDMAState),
- VMSTATE_BOOL(error, DSPDMAState),
- VMSTATE_BOOL(eol, DSPDMAState),
- VMSTATE_END_OF_LIST()
- }
-};
-
-const VMStateDescription vmstate_vp_dsp_core_state = {
- .name = "mcpx-apu/dsp-state/core",
- .version_id = 1,
- .minimum_version_id = 1,
- .minimum_version_id_old = 1,
- .fields = (VMStateField[]) {
- // FIXME: Remove unnecessary fields
- VMSTATE_UINT16(instr_cycle, dsp_core_t),
- VMSTATE_UINT32(pc, dsp_core_t),
- VMSTATE_UINT32_ARRAY(registers, dsp_core_t, DSP_REG_MAX),
- VMSTATE_UINT32_2DARRAY(stack, dsp_core_t, 2, 16),
- VMSTATE_UINT32_ARRAY(xram, dsp_core_t, DSP_XRAM_SIZE),
- VMSTATE_UINT32_ARRAY(yram, dsp_core_t, DSP_YRAM_SIZE),
- VMSTATE_UINT32_ARRAY(pram, dsp_core_t, DSP_PRAM_SIZE),
- VMSTATE_UINT32_ARRAY(mixbuffer, dsp_core_t, DSP_MIXBUFFER_SIZE),
- VMSTATE_UINT32_ARRAY(periph, dsp_core_t, DSP_PERIPH_SIZE),
- VMSTATE_UINT32(loop_rep, dsp_core_t),
- VMSTATE_UINT32(pc_on_rep, dsp_core_t),
- VMSTATE_UINT16(interrupt_state, dsp_core_t),
- VMSTATE_UINT16(interrupt_instr_fetch, dsp_core_t),
- VMSTATE_UINT16(interrupt_save_pc, dsp_core_t),
- VMSTATE_UINT16(interrupt_counter, dsp_core_t),
- VMSTATE_UINT16(interrupt_ipl_to_raise, dsp_core_t),
- VMSTATE_UINT16(interrupt_pipeline_count, dsp_core_t),
- VMSTATE_INT16_ARRAY(interrupt_ipl, dsp_core_t, 12),
- VMSTATE_UINT16_ARRAY(interrupt_is_pending, dsp_core_t, 12),
- VMSTATE_UINT32(num_inst, dsp_core_t),
- VMSTATE_UINT32(cur_inst_len, dsp_core_t),
- VMSTATE_UINT32(cur_inst, dsp_core_t),
- VMSTATE_BOOL(executing_for_disasm, dsp_core_t),
- VMSTATE_UINT32(disasm_memory_ptr, dsp_core_t),
- VMSTATE_BOOL(exception_debugging, dsp_core_t),
- VMSTATE_UINT32(disasm_prev_inst_pc, dsp_core_t),
- VMSTATE_BOOL(disasm_is_looping, dsp_core_t),
- VMSTATE_UINT32(disasm_cur_inst, dsp_core_t),
- VMSTATE_UINT16(disasm_cur_inst_len, dsp_core_t),
- VMSTATE_UINT32_ARRAY(disasm_registers_save, dsp_core_t, 64),
-// #ifdef DSP_DISASM_REG_PC
-// VMSTATE_UINT32(pc_save, dsp_core_t),
-// #endif
- VMSTATE_END_OF_LIST()
- }
-};
-
-const VMStateDescription vmstate_vp_dsp_state = {
- .name = "mcpx-apu/dsp-state",
- .version_id = 1,
- .minimum_version_id = 1,
- .minimum_version_id_old = 1,
- .fields = (VMStateField[]) {
- VMSTATE_STRUCT(core, DSPState, 1, vmstate_vp_dsp_core_state, dsp_core_t),
- VMSTATE_STRUCT(dma, DSPState, 1, vmstate_vp_dsp_dma_state, DSPDMAState),
- VMSTATE_INT32(save_cycles, DSPState),
- VMSTATE_UINT32(interrupts, DSPState),
- VMSTATE_END_OF_LIST()
- }
-};
-
-static const VMStateDescription vmstate_mcpx_apu = {
- .name = "mcpx-apu",
- .version_id = 1,
- .minimum_version_id = 1,
- .pre_load = mcpx_apu_pre_load,
- .post_load = mcpx_apu_post_load,
- .fields = (VMStateField[]) {
- VMSTATE_PCI_DEVICE(dev, MCPXAPUState),
- VMSTATE_STRUCT_POINTER(gp.dsp, MCPXAPUState, vmstate_vp_dsp_state, DSPState),
- VMSTATE_UINT32_ARRAY(gp.regs, MCPXAPUState, 0x10000),
- VMSTATE_STRUCT_POINTER(ep.dsp, MCPXAPUState, vmstate_vp_dsp_state, DSPState),
- VMSTATE_UINT32_ARRAY(ep.regs, MCPXAPUState, 0x10000),
- VMSTATE_UINT32_ARRAY(regs, MCPXAPUState, 0x20000),
- VMSTATE_END_OF_LIST()
- },
-};
-
-static void mcpx_apu_class_init(ObjectClass *klass, void *data)
-{
- DeviceClass *dc = DEVICE_CLASS(klass);
- PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
-
- k->vendor_id = PCI_VENDOR_ID_NVIDIA;
- k->device_id = PCI_DEVICE_ID_NVIDIA_MCPX_APU;
- k->revision = 210;
- k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
- k->realize = mcpx_apu_realize;
-
- dc->desc = "MCPX Audio Processing Unit";
- dc->vmsd = &vmstate_mcpx_apu;
-}
-
-static const TypeInfo mcpx_apu_info = {
- .name = "mcpx-apu",
- .parent = TYPE_PCI_DEVICE,
- .instance_size = sizeof(MCPXAPUState),
- .class_init = mcpx_apu_class_init,
- .interfaces = (InterfaceInfo[]) {
- { INTERFACE_CONVENTIONAL_PCI_DEVICE },
- { },
- },
-};
-
-static void mcpx_apu_register(void)
-{
- type_register_static(&mcpx_apu_info);
-}
-type_init(mcpx_apu_register);
-
-void mcpx_apu_init(PCIBus *bus, int devfn, MemoryRegion *ram)
-{
- PCIDevice *dev = pci_create_simple(bus, devfn, "mcpx-apu");
- MCPXAPUState *d = MCPX_APU_DEVICE(dev);
-
- /* Keep pointers to system memory */
- d->ram = ram;
- d->ram_ptr = memory_region_get_ram_ptr(d->ram);
-}
diff --git a/hw/xbox/xbox.c b/hw/xbox/xbox.c
index 8042026a0f..aeafec3777 100644
--- a/hw/xbox/xbox.c
+++ b/hw/xbox/xbox.c
@@ -52,7 +52,7 @@
#include "hw/i2c/i2c.h"
#include "hw/i2c/smbus_eeprom.h"
#include "hw/xbox/nv2a/nv2a.h"
-#include "hw/xbox/mcpx_apu.h"
+#include "hw/xbox/mcpx/apu.h"
#include "hw/xbox/xbox.h"
#include "smbus.h"
diff --git a/ui/xemu-hud.cc b/ui/xemu-hud.cc
index c50d575e06..ccb291fbd7 100644
--- a/ui/xemu-hud.cc
+++ b/ui/xemu-hud.cc
@@ -48,6 +48,7 @@ extern "C" {
#include "qemu-common.h"
#include "sysemu/sysemu.h"
#include "sysemu/runstate.h"
+#include "hw/xbox/mcpx/apu_debug.h"
#undef typename
#undef atomic_fetch_add
@@ -130,7 +131,7 @@ private:
const float fade_in = 0.1;
const float fade_out = 0.9;
float fade = 0;
-
+
if (t < fade_in) {
// Linear fade in
fade = t/fade_in;
@@ -167,7 +168,7 @@ private:
ImGui::PopStyleColor();
ImGui::PopStyleVar();
ImGui::End();
- }
+ }
};
static void HelpMarker(const char* desc)
@@ -229,7 +230,7 @@ public:
const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text
ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText
-
+
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing
ImGui::PushFont(g_fixed_width_font);
ImGui::TextUnformatted(xemu_get_monitor_buffer());
@@ -531,6 +532,22 @@ public:
}
};
+static const char *paused_file_open(int flags,
+ const char *filters,
+ const char *default_path,
+ const char *default_name)
+{
+ bool is_running = runstate_is_running();
+ if (is_running) {
+ vm_stop(RUN_STATE_PAUSED);
+ }
+ const char *r = noc_file_dialog_open(flags, filters, default_path, default_name);
+ if (is_running) {
+ vm_start();
+ }
+
+ return r;
+}
#define MAX_STRING_LEN 2048 // FIXME: Completely arbitrary and only used here
// to give a buffer to ImGui for each field
@@ -557,7 +574,7 @@ public:
is_open = false;
dirty = false;
pending_restart = false;
-
+
flash_path[0] = '\0';
bootrom_path[0] = '\0';
hdd_path[0] = '\0';
@@ -565,7 +582,7 @@ public:
memory_idx = 0;
short_animation = false;
}
-
+
~SettingsWindow()
{
}
@@ -595,7 +612,7 @@ public:
len = strlen(tmp);
assert(len < MAX_STRING_LEN);
strncpy(eeprom_path, tmp, sizeof(eeprom_path));
-
+
xemu_settings_get_int(XEMU_SETTINGS_SYSTEM_MEMORY, &tmp_int);
memory_idx = (tmp_int-64)/64;
@@ -626,7 +643,7 @@ public:
}
ImGui::SameLine();
if (ImGui::Button("Browse...", ImVec2(100*g_ui_scale, 0))) {
- const char *selected = noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, filters, buf, NULL);
+ const char *selected = paused_file_open(NOC_FILE_DIALOG_OPEN, filters, buf, NULL);
if ((selected != NULL) && (strcmp(buf, selected) != 0)) {
strncpy(buf, selected, len-1);
dirty = true;
@@ -740,7 +757,7 @@ public:
// FIXME: Show driver
// FIXME: Show BIOS/BootROM hash
}
-
+
~AboutWindow()
{
}
@@ -786,7 +803,7 @@ public:
ImGui::SetCursorPosX(10*g_ui_scale);
ImGui::Dummy(ImVec2(0,20*g_ui_scale));
-
+
const char *msg = "Visit https://xemu.app for more information";
ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(msg).x)/2);
ImGui::Text("%s", msg);
@@ -910,7 +927,7 @@ public:
xemu_settings_set_bool(XEMU_SETTINGS_NETWORK_ENABLED, xemu_net_is_enabled());
xemu_settings_save();
}
-
+
ImGui::End();
}
};
@@ -1052,7 +1069,7 @@ public:
ImGui::Columns(2, "", false);
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth()*0.25);
-
+
ImGui::Text("User Token");
ImGui::SameLine();
HelpMarker("This is a unique access token used to authorize submission of the report. To request a token, click 'Get Token'.");
@@ -1082,9 +1099,9 @@ public:
ImGui::SameLine();
HelpMarker(playability_descriptions[playability]);
ImGui::NextColumn();
-
+
ImGui::Columns(1);
-
+
ImGui::Text("Description");
if (ImGui::InputTextMultiline("###desc", description, sizeof(description), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 6), 0)) {
report.compat_comments = description;
@@ -1117,10 +1134,10 @@ public:
"This information will be archived and used to analyze, resolve problems with, "
"and improve the application. This information may be made publicly visible, "
"for example: to anyone who wishes to see the playability status of a title, as "
- "indicated by your report.");
+ "indicated by your report.");
ImGui::TreePop();
}
-
+
ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
ImGui::Separator();
ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
@@ -1149,12 +1166,210 @@ public:
xemu_settings_save();
}
}
-
+
ImGui::End();
}
};
+#include
+
+float mix(float a, float b, float t)
+{
+ return a*(1.0-t) + (b-a)*t;
+}
+
+class DebugApuWindow
+{
+public:
+ bool is_open;
+
+ DebugApuWindow()
+ {
+ is_open = false;
+ }
+
+ ~DebugApuWindow()
+ {
+ }
+
+ void Draw()
+ {
+ if (!is_open) return;
+
+ ImGui::SetNextWindowContentSize(ImVec2(600.0f*g_ui_scale, 0.0f));
+ if (!ImGui::Begin("Audio Debug", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) {
+ ImGui::End();
+ return;
+ }
+
+ const struct McpxApuDebug *dbg = mcpx_apu_get_debug_info();
+
+
+ ImGui::Columns(2, "", false);
+ int now = SDL_GetTicks() % 1000;
+ float t = now/1000.0f;
+ float freq = 1;
+ float v = fabs(sin(M_PI*t*freq));
+ float c_active = mix(0.4, 0.97, v);
+ float c_inactive = 0.2f;
+
+ int voice_monitor = -1;
+ int voice_info = -1;
+ int voice_mute = -1;
+
+ // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style.
+ ImGui::PushFont(g_fixed_width_font);
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2));
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4));
+ for (int i = 0; i < 256; i++)
+ {
+ if (i % 16) {
+ ImGui::SameLine();
+ }
+
+ float c, s, h;
+ h = 0.6;
+ if (dbg->vp.v[i].active) {
+ if (dbg->vp.v[i].paused) {
+ c = c_inactive;
+ s = 0.4;
+ } else {
+ c = c_active;
+ s = 0.7;
+ }
+ if (mcpx_apu_debug_is_muted(i)) {
+ h = 1.0;
+ }
+ } else {
+ c = c_inactive;
+ s = 0;
+ }
+
+ ImGui::PushID(i);
+ ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(h, s, c));
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(h, s, 0.8));
+ ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(h, 0.8f, 1.0));
+ char buf[12];
+ snprintf(buf, sizeof(buf), "%02x", i);
+ ImGui::Button(buf);
+ if (/*dbg->vp.v[i].active &&*/ ImGui::IsItemHovered()) {
+ voice_monitor = i;
+ voice_info = i;
+ }
+ if (ImGui::IsItemClicked(1)) {
+ voice_mute = i;
+ }
+ ImGui::PopStyleColor(3);
+ ImGui::PopID();
+ }
+ ImGui::PopStyleVar(3);
+ ImGui::PopFont();
+
+ if (voice_info >= 0) {
+ const struct McpxApuDebugVoice *voice = &dbg->vp.v[voice_info];
+ ImGui::BeginTooltip();
+ bool is_paused = voice->paused;
+ ImGui::Text("Voice 0x%x/%d %s", voice_info, voice_info, is_paused ? "(Paused)" : "");
+ ImGui::SameLine();
+ ImGui::Text(voice->stereo ? "Stereo" : "Mono");
+
+ ImGui::Separator();
+ ImGui::PushFont(g_fixed_width_font);
+
+ const char *noyes[2] = { "NO", "YES" };
+ ImGui::Text("Stream: %-3s Loop: %-3s Persist: %-3s Multipass: %-3s "
+ "Linked: %-3s",
+ noyes[voice->stream], noyes[voice->loop],
+ noyes[voice->persist], noyes[voice->multipass],
+ noyes[voice->linked]);
+
+ const char *cs[4] = { "1 byte", "2 bytes", "ADPCM", "4 bytes" };
+ const char *ss[4] = {
+ "Unsigned 8b PCM",
+ "Signed 16b PCM",
+ "Signed 24b PCM",
+ "Signed 32b PCM"
+ };
+
+ assert(voice->container_size < 4);
+ assert(voice->sample_size < 4);
+ ImGui::Text("Container Size: %s, Sample Size: %s, Samples per Block: %d",
+ cs[voice->container_size], ss[voice->sample_size], voice->samples_per_block);
+ ImGui::Text("Rate: %f (%d Hz)", voice->rate, (int)(48000.0/voice->rate));
+ ImGui::Text("EBO=%d CBO=%d LBO=%d BA=%x",
+ voice->ebo, voice->cbo, voice->lbo, voice->ba);
+ ImGui::Text("Mix: ");
+ for (int i = 0; i < 8; i++) {
+ if (i == 4) ImGui::Text(" ");
+ ImGui::SameLine();
+ char buf[64];
+ if (voice->vol[i] == 0xFFF) {
+ snprintf(buf, sizeof(buf),
+ "Bin %2d (MUTE) ", voice->bin[i]);
+ } else {
+ snprintf(buf, sizeof(buf),
+ "Bin %2d (-%.3f) ", voice->bin[i],
+ (float)((voice->vol[i] >> 6) & 0x3f) +
+ (float)((voice->vol[i] >> 0) & 0x3f) / 64.0);
+ }
+ ImGui::Text("%-17s", buf);
+ }
+ ImGui::PopFont();
+ ImGui::EndTooltip();
+ }
+
+ if (voice_monitor >= 0) {
+ mcpx_apu_debug_isolate_voice(voice_monitor);
+ } else {
+ mcpx_apu_debug_clear_isolations();
+ }
+ if (voice_mute >= 0) {
+ mcpx_apu_debug_toggle_mute(voice_mute);
+ }
+
+ ImGui::SameLine();
+ ImGui::SetColumnWidth(0, ImGui::GetCursorPosX());
+ ImGui::NextColumn();
+
+ ImGui::PushFont(g_fixed_width_font);
+ ImGui::Text("Frames: %04d", dbg->frames_processed);
+ ImGui::Text("GP Cycles: %04d", dbg->gp.cycles);
+ ImGui::Text("EP Cycles: %04d", dbg->ep.cycles);
+ bool color = (dbg->utilization > 0.9);
+ if (color) ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
+ ImGui::Text("Utilization: %.2f%%", (dbg->utilization*100));
+ if (color) ImGui::PopStyleColor();
+ ImGui::PopFont();
+
+ ImGui::Separator();
+
+ static int mon = 0;
+ mon = mcpx_apu_debug_get_monitor();
+ if (ImGui::Combo("Monitor", &mon, "AC97\0VP Only\0GP Only\0EP Only\0GP/EP if enabled\0")) {
+ mcpx_apu_debug_set_monitor(mon);
+ }
+
+ static bool gp_realtime;
+ gp_realtime = dbg->gp_realtime;
+ if (ImGui::Checkbox("GP Realtime\n", &gp_realtime)) {
+ mcpx_apu_debug_set_gp_realtime_enabled(gp_realtime);
+ }
+
+ static bool ep_realtime;
+ ep_realtime = dbg->ep_realtime;
+ if (ImGui::Checkbox("EP Realtime\n", &ep_realtime)) {
+ mcpx_apu_debug_set_ep_realtime_enabled(ep_realtime);
+ }
+
+ ImGui::Columns(1);
+ ImGui::End();
+ }
+};
+
+
static MonitorWindow monitor_window;
+static DebugApuWindow apu_window;
static InputWindow input_window;
static NetworkWindow network_window;
static AboutWindow about_window;
@@ -1172,7 +1387,7 @@ public:
{
is_open = false;
}
-
+
~FirstBootWindow()
{
}
@@ -1240,7 +1455,7 @@ public:
if (ImGui::IsItemClicked()) {
xemu_open_web_browser("https://xemu.app");
}
-
+
ImGui::End();
}
};
@@ -1265,7 +1480,7 @@ static void action_load_disc(void)
const char *iso_file_filters = ".iso Files\0*.iso\0All Files\0*.*\0";
const char *current_disc_path;
xemu_settings_get_string(XEMU_SETTINGS_SYSTEM_DVD_PATH, ¤t_disc_path);
- const char *new_disc_path = noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, iso_file_filters, current_disc_path, NULL);
+ const char *new_disc_path = paused_file_open(NOC_FILE_DIALOG_OPEN, iso_file_filters, current_disc_path, NULL);
if (new_disc_path == NULL) {
/* Cancelled */
return;
@@ -1385,6 +1600,7 @@ static void ShowMainMenu()
if (ImGui::BeginMenu("Debug"))
{
ImGui::MenuItem("Monitor", NULL, &monitor_window.is_open);
+ ImGui::MenuItem("Audio", NULL, &apu_window.is_open);
ImGui::EndMenu();
}
@@ -1530,7 +1746,7 @@ void xemu_hud_should_capture_kbd_mouse(int *kbd, int *mouse)
{
ImGuiIO& io = ImGui::GetIO();
if (kbd) *kbd = io.WantCaptureKeyboard;
- if (mouse) *mouse = io.WantCaptureMouse;
+ if (mouse) *mouse = io.WantCaptureMouse;
}
void xemu_hud_render(void)
@@ -1670,6 +1886,7 @@ void xemu_hud_render(void)
input_window.Draw();
settings_window.Draw();
monitor_window.Draw();
+ apu_window.Draw();
about_window.Draw();
network_window.Draw();
compatibility_reporter_window.Draw();
diff --git a/ui/xemu-settings.c b/ui/xemu-settings.c
index a464f84e7c..41cbe4448f 100644
--- a/ui/xemu-settings.c
+++ b/ui/xemu-settings.c
@@ -44,6 +44,9 @@ struct xemu_settings {
int memory;
int short_animation; // Boolean
+ // [audio]
+ int use_dsp; // Boolean
+
// [display]
int scale;
int ui_scale;
@@ -102,6 +105,8 @@ struct config_offset_table {
[XEMU_SETTINGS_SYSTEM_EEPROM_PATH] = { CONFIG_TYPE_STRING, "system", "eeprom_path", offsetof(struct xemu_settings, eeprom_path), { .default_str = "" } },
[XEMU_SETTINGS_SYSTEM_MEMORY] = { CONFIG_TYPE_INT, "system", "memory", offsetof(struct xemu_settings, memory), { .default_int = 64 } },
[XEMU_SETTINGS_SYSTEM_SHORTANIM] = { CONFIG_TYPE_BOOL, "system", "shortanim", offsetof(struct xemu_settings, short_animation), { .default_bool = 0 } },
+
+ [XEMU_SETTINGS_AUDIO_USE_DSP] = { CONFIG_TYPE_BOOL, "audio", "use_dsp", offsetof(struct xemu_settings, use_dsp), { .default_bool = 0 } },
[XEMU_SETTINGS_DISPLAY_SCALE] = { CONFIG_TYPE_ENUM, "display", "scale", offsetof(struct xemu_settings, scale), { .default_int = DISPLAY_SCALE_SCALE }, display_scale_map },
[XEMU_SETTINGS_DISPLAY_UI_SCALE] = { CONFIG_TYPE_INT, "display", "ui_scale", offsetof(struct xemu_settings, ui_scale), { .default_int = 1 } },
diff --git a/ui/xemu-settings.h b/ui/xemu-settings.h
index 352224e0b0..d7e613922b 100644
--- a/ui/xemu-settings.h
+++ b/ui/xemu-settings.h
@@ -36,6 +36,7 @@ enum xemu_settings_keys {
XEMU_SETTINGS_SYSTEM_DVD_PATH,
XEMU_SETTINGS_SYSTEM_MEMORY,
XEMU_SETTINGS_SYSTEM_SHORTANIM,
+ XEMU_SETTINGS_AUDIO_USE_DSP,
XEMU_SETTINGS_DISPLAY_SCALE,
XEMU_SETTINGS_DISPLAY_UI_SCALE,
XEMU_SETTINGS_INPUT_CONTROLLER_1_GUID,