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,