/* * QEMU MCPX Audio Processing Unit implementation * * Copyright (c) 2012 espes * Copyright (c) 2018-2019 Jannik Vogel * Copyright (c) 2019-2025 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 "hw/pci/pci_device.h" #include "cpu.h" #include "migration/vmstate.h" #include "qemu/main-loop.h" #include "sysemu/runstate.h" #include "audio/audio.h" #include "qemu/fifo8.h" #include "ui/xemu-settings.h" #include "trace.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 { /*< private >*/ PCIDevice parent_obj; /*< public >*/ 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 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 }; 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, int voice_list); 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, bool 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 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; g_dbg.vp.v[i].multipass_dst_voice = 0xFFFF; } } 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); if (m && g_dbg_cache.vp.v[g_dbg_voice_monitor].multipass) { uint8_t mp_bin = g_dbg_cache.vp.v[g_dbg_voice_monitor].multipass_bin; struct McpxApuDebugVoice *d = &g_dbg_cache.vp.v[v]; for (int i = 0; i < sizeof(d->bin) / sizeof(d->bin[0]); i++) { if (d->bin[i] == mp_bin) { m = false; break; } } } 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 void mcpx_apu_update_dsp_preference(MCPXAPUState *d) { static int last_known_preference = -1; if (last_known_preference == (int)g_config.audio.use_dsp) { return; } if (g_config.audio.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; } last_known_preference = g_config.audio.use_dsp; } 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) { qatomic_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])) { qatomic_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(PCI_DEVICE(d)); } else { qatomic_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(PCI_DEVICE(d)); } } 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 = qatomic_read(&d->regs[addr]); } break; } trace_mcpx_apu_reg_read(addr, size, r); return r; } static void mcpx_apu_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size) { MCPXAPUState *d = opaque; trace_mcpx_apu_reg_write(addr, size, val); switch (addr) { case NV_PAPU_ISTS: /* the bits of the interrupts to clear are written */ qatomic_and(&d->regs[NV_PAPU_ISTS], ~val); update_irq(d); qemu_cond_broadcast(&d->cond); break; case NV_PAPU_FECTL: case NV_PAPU_SECTL: qatomic_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"); qatomic_set(&d->regs[addr], val); break; default: if (addr < 0x20000) { qatomic_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 (qatomic_read(&d->vp.voice_locked[v / 64]) & mask) != 0; } static void fe_method(MCPXAPUState *d, uint32_t method, uint32_t argument) { unsigned int slot; trace_mcpx_apu_method(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); qatomic_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 int peek_ahead_multipass_bin(MCPXAPUState *d, uint16_t v, uint16_t *dst_voice) { bool first = true; while (v != 0xFFFF) { bool multipass = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_MULTIPASS); if (multipass) { if (first) { break; } *dst_voice = v; int mp_bin = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_MULTIPASS_BIN); return mp_bin; } v = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_PITCH_LINK, NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE); first = false; } *dst_voice = 0xFFFF; return -1; } static void dump_multipass_unused_debug_info(MCPXAPUState *d, uint16_t v) { unsigned int sample_size = voice_get_mask( d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE); unsigned int container_size_index = voice_get_mask( d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE); bool stream = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_DATA_TYPE); 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); bool persist = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_PERSIST); bool linked = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_LINKED); 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 = 0; // Value overloaded with multipass bin dbg->persist = persist; dbg->linked = linked; } static void get_multipass_samples(MCPXAPUState *d, float mixbins[][NUM_SAMPLES_PER_FRAME], uint16_t v, float samples[][2]) { struct McpxApuDebugVoice *dbg = &g_dbg.vp.v[v]; // DirectSound sets bin to 31, but hardware would allow other bins int mp_bin = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_MULTIPASS_BIN); dbg->multipass_bin = mp_bin; for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { samples[i][0] = mixbins[mp_bin][i]; samples[i][1] = mixbins[mp_bin][i]; } // DirectSound sets clear mix to true bool clear_mix = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_CLEAR_MIX); if (clear_mix) { memset(&mixbins[mp_bin][0], 0, sizeof(mixbins[0])); } // Dump irrelevant data for audio debug UI to avoid showing stale info dump_multipass_unused_debug_info(d, v); } static void voice_process(MCPXAPUState *d, float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME], uint16_t v, int voice_list) { 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 }; bool multipass = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_MULTIPASS); dbg->multipass = multipass; if (multipass) { get_multipass_samples(d, mixbins, v, samples); } else { 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. * * If the current voice belongs to a multipass sub-voice group we must * skip it here to avoid mixing it in twice because the sub-voices are * mixed into the multipass bin and that sub-mix will be mixed in here * later when the destination (i.e. second pass) voice is processed. * TODO: Are the 2D, 3D and MP voice lists merely a DirectSound * convention? Perhaps hardware doesn't care if e.g. a multipass * voice is in the 2D or 3D list. On the other hand, MON_VP is * not how the hardware works anyway so not much point worrying * about precise emulation here. DirectSound compatibility is * enough. */ int mp_bin = -1; uint16_t mp_dst_voice = 0xFFFF; if (voice_list == NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_MP_TOP - 1) { mp_bin = peek_ahead_multipass_bin(d, v, &mp_dst_voice); } dbg->multipass_dst_voice = mp_dst_voice; bool debug_isolation = g_dbg_voice_monitor >= 0 && g_dbg_voice_monitor == v; float g = 0.0f; for (int b = 0; b < 8; b++) { if (bin[b] == mp_bin && !debug_isolation) { continue; } 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? */ assert(!multipass); // Multipass is handled before this 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; if (!persist) { d->vp.ssl[v].ssl_index = 0; voice_off(d, v); } else { set_notify_status( d, v, MCPX_HW_NOTIFIER_SSLA_DONE + d->vp.ssl[v].ssl_index, NV1BA0_NOTIFICATION_STATUS_DONE_SUCCESS); } 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); 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); } } 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_apu_update_dsp_preference(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, list); 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 if (0 <= g_config.audio.volume_limit && g_config.audio.volume_limit < 1) { float f = pow(g_config.audio.volume_limit, M_E); for (int i = 0; i < 256; i++) { d->apu_fifo_output[i][0] *= f; d->apu_fifo_output[i][1] *= f; } } qemu_spin_lock(&d->vp.out_buf_lock); 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); chunk_len = fifo8_pop_buf(&s->vp.out_buf, stream, to_copy); assert(chunk_len <= to_copy); 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(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, bool running, RunState state) { MCPXAPUState *d = opaque; if (state == RUN_STATE_SAVE_VM) { qemu_mutex_lock(&d->lock); } } 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; mcpx_apu_reset(d); 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 mcpx_apu_reset_hold(Object *obj, ResetType type) { MCPXAPUState *d = MCPX_APU_DEVICE(obj); mcpx_apu_reset(d); } const VMStateDescription vmstate_vp_dsp_dma_state = { .name = "mcpx-apu/dsp-state/dma", .version_id = 1, .minimum_version_id = 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, .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_UNUSED(1), 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, .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, .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(parent_obj, 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); ResettableClass *rc = RESETTABLE_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 = 177; k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO; k->realize = mcpx_apu_realize; k->exit = mcpx_apu_exitfn; rc->phases.hold = mcpx_apu_reset_hold; 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); static void *mcpx_apu_frame_thread(void *arg) { MCPXAPUState *d = MCPX_APU_DEVICE(arg); qemu_mutex_lock(&d->lock); while (!qatomic_read(&d->exiting)) { int xcntmode = GET_MASK(qatomic_read(&d->regs[NV_PAPU_SECTL]), NV_PAPU_SECTL_XCNTMODE); uint32_t fectl = qatomic_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); bql_lock(); update_irq(d); bql_unlock(); qemu_mutex_lock(&d->lock); d->set_irq = false; } xcntmode = GET_MASK(qatomic_read(&d->regs[NV_PAPU_SECTL]), NV_PAPU_SECTL_XCNTMODE); fectl = qatomic_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, }; if (SDL_Init(SDL_INIT_AUDIO) < 0) { fprintf(stderr, "Failed to initialize SDL audio subsystem: %s\n", SDL_GetError()); exit(1); } 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: %s\n", SDL_GetError()); assert(!"SDL_OpenAudioDevice failed"); 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. */ mcpx_apu_update_dsp_preference(d); qemu_thread_create(&d->apu_thread, "mcpx.apu_thread", mcpx_apu_frame_thread, d, QEMU_THREAD_JOINABLE); }