mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 02:56:36 -04:00
mmx6 framework: CD/DMA/MDEC + codegen fixes (Tomba builds+plays, MMX6 boots to intro)
Framework work on codex/mmx6-support that makes Tomba build and play (title screen, 0 dispatch misses) and MMX6 boot through BIOS to its intro. Touches recompiler codegen (code_generator, function_analysis) and runtime CD-ROM/DMA/MDEC/GPU/SIO timing (cdrom, dma, mdec, gpu, psx_cycles, traps, fntrace, debug_server) + headers + runtime.cmake. Milestone snapshot for collapsing onto master. Build dir excluded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+8
-3
@@ -36,16 +36,21 @@ Columns: **N** = native, **D** = DuckStation oracle.
|
||||
| `dump_ram` | ✓ | ✓ | `addr`, `len` | Chunked RAM read (up to 64 KB) |
|
||||
| `read_scratch` | | ✓ | `addr`, `len` | Read PS1 scratchpad (0x1F800000 region) |
|
||||
| `read_vram` / `vram_peek` | ✓¹ | ✓ | `x`, `y`, `w`, `h` | Read 16-bit VRAM pixels (max 128×128) |
|
||||
| `gpu_state` | ✓ | ✓ | — | Display area, draw offset, GPUSTAT, clip rect, xfer state |
|
||||
| `gpu_state` | ✓ | ✓ | — | Display area, display depth, draw offset, GPUSTAT, clip rect, xfer state |
|
||||
| `sio_state` | ✓ | ✓ | — | SIO registers + (native only) pad/memcard protocol + TX/RX history |
|
||||
| `irq_state` | ✓ | ✓ | — | `I_STAT`, `I_MASK` (both), plus chain state on native |
|
||||
| `dma_state` | | ✓ | — | DPCR, DICR, all 7 channel states (madr/bcr/chcr) |
|
||||
| `dma_state` | ✓ | ✓ | — | DPCR, DICR, all 7 channel states (madr/bcr/chcr) |
|
||||
| `event_state` | | ✓ | — | EvCB table summary (stub on DS — events are BIOS-level) |
|
||||
| `overlay_state` | | ✓ | — | Current overlay info |
|
||||
| `cdrom_sector_dump` | ✓ | | `offset`, `len` | Dump bytes from the last CD-ROM sector observed by the controller, including LBA/mode metadata |
|
||||
| `cdrom_sector_history` | ✓ | | `count`, optional `lba` | Dump newest CD-ROM sector history entries, including raw XA subheader fields, CPU/audio delivery flags, and the first 128 bytes |
|
||||
| `cdrom_sector_history_clear` | ✓ | | — | Reset the CD-ROM sector history ring |
|
||||
| `watch` | ✓ | ✓ | `addr` | Set byte-level memory watchpoint (fires per-frame on change) |
|
||||
| `unwatch` | ✓ | ✓ | `addr` | Remove memory watchpoint |
|
||||
| `set_input` | ✓ | ✓ | `buttons` | Override pad1 buttons (PS1 inverted bitmask, 0 = pressed) |
|
||||
| `clear_input` | ✓ | ✓ | — | Remove input override |
|
||||
| `turbo` | ✓ | | `enabled` | Enable/disable TCP-controlled frontend turbo for fast-forward validation |
|
||||
| `turbo_state` | ✓ | | — | Query TCP-controlled turbo state |
|
||||
| `pause` | ✓ | ✓ | — | Pause emulation |
|
||||
| `continue` (`c`) | ✓ | ✓ | — | Resume emulation |
|
||||
| `step` | ✓ | ✓ | `[count]` | Step N frames (default 1) |
|
||||
@@ -56,7 +61,7 @@ Columns: **N** = native, **D** = DuckStation oracle.
|
||||
| `frame_timeseries` | ✓ | ✓ | `start`, `end` | Compact timeseries, max 200 frames |
|
||||
| `set_snapshot` | ✓ | ✓ | `slot`, `addr`, `size` | Configure per-frame RAM snapshot region (slots 0-3) |
|
||||
| `get_snapshots` | ✓ | ✓ | — | Show snapshot config |
|
||||
| `screenshot` | ✓ | ✓ | `path` | Save display to PNG |
|
||||
| `screenshot` | ✓ | ✓ | `path` | Save display to PNG/file; native row dumps report `rgb555` or `rgb888` to match display depth |
|
||||
| `first_failure` | ✓ | | — | Find first divergence point between runs (native-side tracking) |
|
||||
| `read_frame_ram` | ✓ | | `addr`, `len`, `frame` | Read RAM **as of a specific frame** (from ring buffer) |
|
||||
| `wtrace_range` | ✓ | | `lo`, `hi` | Set RAM-write trace range (ring of 1024 writes with RA) |
|
||||
|
||||
@@ -24,6 +24,7 @@ struct FunctionAnalysisResult {
|
||||
int jr_ra_count;
|
||||
int prologue_count;
|
||||
int call_discovered_count = 0; // Functions found via JAL call-target following
|
||||
int strong_prologue_count = 0; // Functions found from prologues with saved $ra
|
||||
int state_continuation_count = 0; // Split entries after calls to SaveState-style helpers
|
||||
};
|
||||
|
||||
|
||||
@@ -1481,82 +1481,70 @@ std::string CodeGenerator::generate_file(
|
||||
// in CPUState, but these standalone functions provide correct reference behavior.
|
||||
ss << "/* --- Unaligned memory access helper implementations --- */\n";
|
||||
ss << "\n";
|
||||
ss << "/* LWL: Load Word Left - merges high bytes from aligned word into rt.\n";
|
||||
ss << " * addr is the effective (possibly unaligned) address.\n";
|
||||
ss << " * rt_value is the current value of the destination register.\n";
|
||||
ss << " * MIPS big-endian convention: byte 0 is the most-significant byte.\n";
|
||||
ss << " * The PSX (little-endian) maps addr&3==0 -> load entire word,\n";
|
||||
ss << " * addr&3==1 -> load 3 bytes into high 24 bits, etc.\n";
|
||||
ss << " * Formula:\n";
|
||||
ss << " * aligned_addr = addr & ~3;\n";
|
||||
ss << " * shift = (addr & 3) * 8;\n";
|
||||
ss << " * mask = 0xFFFFFFFF << shift;\n";
|
||||
ss << " * result = (rt_value & ~mask) | (word << shift);\n";
|
||||
ss << " * where word = read_word(aligned_addr).\n";
|
||||
ss << " */\n";
|
||||
ss << "static uint32_t psx_lwl(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
|
||||
ss << " uint32_t aligned_addr = addr & ~3u;\n";
|
||||
ss << " uint32_t word = cpu->read_word(aligned_addr);\n";
|
||||
ss << " uint32_t shift = (addr & 3u) * 8u;\n";
|
||||
ss << " uint32_t mask = 0xFFFFFFFFu << shift;\n";
|
||||
ss << " return (rt_value & ~mask) | (word << shift);\n";
|
||||
ss << "}\n";
|
||||
ss << "\n";
|
||||
ss << "/* LWR: Load Word Right - merges low bytes from aligned word into rt.\n";
|
||||
ss << " * addr is the effective (possibly unaligned) address.\n";
|
||||
ss << " * rt_value is the current value of the destination register.\n";
|
||||
ss << " * Formula:\n";
|
||||
ss << " * aligned_addr = addr & ~3;\n";
|
||||
ss << " * shift = (3 - (addr & 3)) * 8;\n";
|
||||
ss << " * mask = 0xFFFFFFFF >> shift;\n";
|
||||
ss << " * result = (rt_value & ~mask) | (word >> shift);\n";
|
||||
ss << " * where word = read_word(aligned_addr).\n";
|
||||
ss << " */\n";
|
||||
ss << "static uint32_t psx_lwr(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
|
||||
ss << " uint32_t aligned_addr = addr & ~3u;\n";
|
||||
ss << " uint32_t word = cpu->read_word(aligned_addr);\n";
|
||||
ss << " uint32_t shift = (3u - (addr & 3u)) * 8u;\n";
|
||||
ss << " uint32_t mask = 0xFFFFFFFFu >> shift;\n";
|
||||
ss << " return (rt_value & ~mask) | (word >> shift);\n";
|
||||
ss << "}\n";
|
||||
ss << "\n";
|
||||
ss << "/* SWL: Store Word Left - stores the high bytes of rt_value into memory.\n";
|
||||
ss << " * addr is the effective (possibly unaligned) address.\n";
|
||||
ss << " * rt_value is the value of the source register.\n";
|
||||
ss << " * Formula:\n";
|
||||
ss << " * aligned_addr = addr & ~3;\n";
|
||||
ss << " * shift = (addr & 3) * 8;\n";
|
||||
ss << " * mem_mask = 0xFFFFFFFF >> (32 - shift) [i.e. low 'shift' bits preserved]\n";
|
||||
ss << " * Actually:\n";
|
||||
ss << " * mask = 0xFFFFFFFF >> shift; [bits to take from rt]\n";
|
||||
ss << " * write_word(aligned_addr,\n";
|
||||
ss << " * (read_word(aligned_addr) & ~mask) | (rt_value >> shift));\n";
|
||||
ss << " */\n";
|
||||
ss << "static void psx_swl(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
|
||||
ss << " uint32_t aligned_addr = addr & ~3u;\n";
|
||||
ss << " uint32_t existing = cpu->read_word(aligned_addr);\n";
|
||||
ss << " uint32_t shift = (addr & 3u) * 8u;\n";
|
||||
ss << " uint32_t mask = 0xFFFFFFFFu >> shift;\n";
|
||||
ss << " cpu->write_word(aligned_addr, (existing & ~mask) | (rt_value >> shift));\n";
|
||||
ss << "}\n";
|
||||
ss << "\n";
|
||||
ss << "/* SWR: Store Word Right - stores the low bytes of rt_value into memory.\n";
|
||||
ss << " * addr is the effective (possibly unaligned) address.\n";
|
||||
ss << " * rt_value is the value of the source register.\n";
|
||||
ss << " * Formula:\n";
|
||||
ss << " * aligned_addr = addr & ~3;\n";
|
||||
ss << " * shift = (3 - (addr & 3)) * 8;\n";
|
||||
ss << " * mask = 0xFFFFFFFF << shift;\n";
|
||||
ss << " * write_word(aligned_addr,\n";
|
||||
ss << " * (read_word(aligned_addr) & ~mask) | (rt_value << shift));\n";
|
||||
ss << " */\n";
|
||||
ss << "static void psx_swr(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
|
||||
ss << " uint32_t aligned_addr = addr & ~3u;\n";
|
||||
ss << " uint32_t existing = cpu->read_word(aligned_addr);\n";
|
||||
ss << " uint32_t shift = (3u - (addr & 3u)) * 8u;\n";
|
||||
ss << " uint32_t mask = 0xFFFFFFFFu << shift;\n";
|
||||
ss << " cpu->write_word(aligned_addr, (existing & ~mask) | (rt_value << shift));\n";
|
||||
ss << "}\n";
|
||||
ss << "/* LWL/LWR/SWL/SWR implement little-endian R3000A unaligned word\n";
|
||||
ss << " * semantics. Keep these switch tables in sync with psx_interpreter.c\n";
|
||||
ss << " * and dirty_ram_interp.c.\n";
|
||||
ss << " */\n";
|
||||
ss << "\n";
|
||||
ss << "/* LWL: Load Word Left - merges high bytes from aligned word into rt.\n";
|
||||
ss << " * addr is the effective (possibly unaligned) address.\n";
|
||||
ss << " * rt_value is the current value of the destination register.\n";
|
||||
ss << " */\n";
|
||||
ss << "static uint32_t psx_lwl(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
|
||||
ss << " uint32_t word = cpu->read_word(addr & ~3u);\n";
|
||||
ss << " switch (addr & 3u) {\n";
|
||||
ss << " case 0: return (rt_value & 0x00FFFFFFu) | (word << 24);\n";
|
||||
ss << " case 1: return (rt_value & 0x0000FFFFu) | (word << 16);\n";
|
||||
ss << " case 2: return (rt_value & 0x000000FFu) | (word << 8);\n";
|
||||
ss << " default: return word;\n";
|
||||
ss << " }\n";
|
||||
ss << "}\n";
|
||||
ss << "\n";
|
||||
ss << "/* LWR: Load Word Right - merges low bytes from aligned word into rt.\n";
|
||||
ss << " * addr is the effective (possibly unaligned) address.\n";
|
||||
ss << " * rt_value is the current value of the destination register.\n";
|
||||
ss << " */\n";
|
||||
ss << "static uint32_t psx_lwr(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
|
||||
ss << " uint32_t word = cpu->read_word(addr & ~3u);\n";
|
||||
ss << " switch (addr & 3u) {\n";
|
||||
ss << " case 0: return word;\n";
|
||||
ss << " case 1: return (rt_value & 0xFF000000u) | (word >> 8);\n";
|
||||
ss << " case 2: return (rt_value & 0xFFFF0000u) | (word >> 16);\n";
|
||||
ss << " default: return (rt_value & 0xFFFFFF00u) | (word >> 24);\n";
|
||||
ss << " }\n";
|
||||
ss << "}\n";
|
||||
ss << "\n";
|
||||
ss << "/* SWL: Store Word Left - stores the high bytes of rt_value into memory.\n";
|
||||
ss << " * addr is the effective (possibly unaligned) address.\n";
|
||||
ss << " * rt_value is the value of the source register.\n";
|
||||
ss << " */\n";
|
||||
ss << "static void psx_swl(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
|
||||
ss << " uint32_t aligned_addr = addr & ~3u;\n";
|
||||
ss << " uint32_t word = cpu->read_word(aligned_addr);\n";
|
||||
ss << " switch (addr & 3u) {\n";
|
||||
ss << " case 0: word = (word & 0xFFFFFF00u) | (rt_value >> 24); break;\n";
|
||||
ss << " case 1: word = (word & 0xFFFF0000u) | (rt_value >> 16); break;\n";
|
||||
ss << " case 2: word = (word & 0xFF000000u) | (rt_value >> 8); break;\n";
|
||||
ss << " default: word = rt_value; break;\n";
|
||||
ss << " }\n";
|
||||
ss << " cpu->write_word(aligned_addr, word);\n";
|
||||
ss << "}\n";
|
||||
ss << "\n";
|
||||
ss << "/* SWR: Store Word Right - stores the low bytes of rt_value into memory.\n";
|
||||
ss << " * addr is the effective (possibly unaligned) address.\n";
|
||||
ss << " * rt_value is the value of the source register.\n";
|
||||
ss << " */\n";
|
||||
ss << "static void psx_swr(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
|
||||
ss << " uint32_t aligned_addr = addr & ~3u;\n";
|
||||
ss << " uint32_t word = cpu->read_word(aligned_addr);\n";
|
||||
ss << " switch (addr & 3u) {\n";
|
||||
ss << " case 0: word = rt_value; break;\n";
|
||||
ss << " case 1: word = (word & 0x000000FFu) | (rt_value << 8); break;\n";
|
||||
ss << " case 2: word = (word & 0x0000FFFFu) | (rt_value << 16); break;\n";
|
||||
ss << " default: word = (word & 0x00FFFFFFu) | (rt_value << 24); break;\n";
|
||||
ss << " }\n";
|
||||
ss << " cpu->write_word(aligned_addr, word);\n";
|
||||
ss << "}\n";
|
||||
ss << "\n";
|
||||
|
||||
// Make mutable copies for potential function splitting
|
||||
|
||||
@@ -77,6 +77,10 @@ static bool is_sw_reg_base(uint32_t instr, uint32_t base_reg, uint32_t value_reg
|
||||
return opcode == 0x2B && rs == base_reg && rt == value_reg;
|
||||
}
|
||||
|
||||
static bool is_sw_reg_sp(uint32_t instr, uint32_t value_reg) {
|
||||
return is_sw_reg_base(instr, 29, value_reg);
|
||||
}
|
||||
|
||||
static bool is_lui(uint32_t instr, uint32_t& rt_out) {
|
||||
uint32_t opcode = (instr >> 26) & 0x3F;
|
||||
if (opcode != 0x0F) return false;
|
||||
@@ -219,6 +223,7 @@ FunctionAnalysisResult FunctionAnalyzer::analyze() {
|
||||
result.jr_ra_count = 0;
|
||||
result.prologue_count = 0;
|
||||
result.call_discovered_count = 0;
|
||||
result.strong_prologue_count = 0;
|
||||
result.state_continuation_count = 0;
|
||||
|
||||
fmt::print("\n=== Function Boundary Detection ===\n\n");
|
||||
@@ -304,6 +309,72 @@ FunctionAnalysisResult FunctionAnalyzer::analyze() {
|
||||
fmt::print("✓ Identified {} unique functions ({} call-discovered)\n\n",
|
||||
function_starts.size(), result.call_discovered_count);
|
||||
|
||||
// Pass 2.55: promote strong standalone prologues.
|
||||
//
|
||||
// Return-scanning alone misses scheduler/task entry points that never
|
||||
// return normally, and direct JAL following misses callback entries whose
|
||||
// addresses are derived at runtime. A standard stack-frame prologue that
|
||||
// quickly saves $ra is a strong function-entry signal, provided the ADDIU
|
||||
// is not itself a branch delay slot.
|
||||
fmt::print("Finding strong standalone prologue entries...\n");
|
||||
int strong_prologues = 0;
|
||||
for (uint32_t addr = exe_start; addr < exe_end; addr += 4) {
|
||||
auto word_opt = exe_.read_word(addr);
|
||||
if (!word_opt.has_value()) break;
|
||||
|
||||
int32_t stack_size = 0;
|
||||
if (!is_prologue(*word_opt, stack_size)) continue;
|
||||
|
||||
if (addr >= exe_start + 4) {
|
||||
auto prev_opt = exe_.read_word(addr - 4);
|
||||
if (prev_opt.has_value() && is_branch_or_jump(*prev_opt)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (addr >= exe_start + 8) {
|
||||
auto prev2_opt = exe_.read_word(addr - 8);
|
||||
auto prev1_opt = exe_.read_word(addr - 4);
|
||||
if (prev2_opt.has_value() && prev1_opt.has_value()) {
|
||||
uint32_t lui_rt = 0;
|
||||
if (is_lui(*prev2_opt, lui_rt) &&
|
||||
is_lw_same_reg_base(*prev1_opt, lui_rt)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto existing_it = function_starts.upper_bound(addr);
|
||||
if (existing_it != function_starts.begin()) {
|
||||
--existing_it;
|
||||
auto return_it = function_last_return.find(*existing_it);
|
||||
if (return_it != function_last_return.end() &&
|
||||
return_it->second + 8u > addr) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
bool saves_ra = false;
|
||||
for (uint32_t look = addr + 4; look < addr + 64 && look < exe_end; look += 4) {
|
||||
auto next_opt = exe_.read_word(look);
|
||||
if (!next_opt.has_value()) break;
|
||||
if (is_sw_reg_sp(*next_opt, 31)) {
|
||||
saves_ra = true;
|
||||
break;
|
||||
}
|
||||
if (is_branch_or_jump(*next_opt)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (saves_ra && function_starts.insert(addr).second) {
|
||||
strong_prologues++;
|
||||
}
|
||||
}
|
||||
result.strong_prologue_count = strong_prologues;
|
||||
fmt::print("Identified {} strong prologue entries\n\n",
|
||||
result.strong_prologue_count);
|
||||
|
||||
// Pass 2.6: discover continuations saved by setjmp/SaveState-style helpers.
|
||||
//
|
||||
// Some games save the caller's $ra into an application context object and
|
||||
|
||||
@@ -35,6 +35,7 @@ typedef struct CDROMDebugState {
|
||||
uint64_t seq;
|
||||
uint8_t index_reg;
|
||||
uint8_t stat_reg;
|
||||
uint8_t request_reg;
|
||||
uint8_t irq_enable;
|
||||
uint8_t irq_flag;
|
||||
uint8_t mode_reg;
|
||||
@@ -62,8 +63,24 @@ typedef struct CDROMDebugState {
|
||||
int pending_delay;
|
||||
int pending_phase;
|
||||
uint32_t i_stat;
|
||||
int last_sector_lba;
|
||||
int last_sector_size;
|
||||
uint32_t last_sector_frame;
|
||||
uint8_t last_sector_mode;
|
||||
uint8_t last_sector_have_raw;
|
||||
} CDROMDebugState;
|
||||
|
||||
typedef struct CDROMSectorDebugState {
|
||||
int current_available;
|
||||
int current_read_pos;
|
||||
int current_size;
|
||||
int last_lba;
|
||||
int last_size;
|
||||
uint32_t last_frame;
|
||||
uint8_t last_mode;
|
||||
uint8_t last_have_raw;
|
||||
} CDROMSectorDebugState;
|
||||
|
||||
typedef struct CDROMTraceEntry {
|
||||
uint64_t seq;
|
||||
uint32_t addr;
|
||||
@@ -82,6 +99,7 @@ typedef struct CDROMTraceEntry {
|
||||
uint8_t response_read;
|
||||
uint8_t response_count;
|
||||
uint8_t sector_available;
|
||||
uint8_t request_reg;
|
||||
uint8_t mode_reg;
|
||||
uint8_t pending_cmd;
|
||||
uint8_t pending_pending;
|
||||
@@ -94,10 +112,36 @@ typedef struct CDROMTraceEntry {
|
||||
} CDROMTraceEntry;
|
||||
|
||||
#define CDROM_TRACE_CAP (1 << 16)
|
||||
#define CDROM_SECTOR_HISTORY_CAP (1 << 10)
|
||||
#define CDROM_SECTOR_HISTORY_BYTES 128
|
||||
|
||||
typedef struct CDROMSectorHistoryEntry {
|
||||
uint64_t seq;
|
||||
int lba;
|
||||
int size;
|
||||
uint32_t frame;
|
||||
uint8_t mode;
|
||||
uint8_t have_raw;
|
||||
uint8_t raw_mode;
|
||||
uint8_t xa_file;
|
||||
uint8_t xa_channel;
|
||||
uint8_t xa_submode;
|
||||
uint8_t xa_coding;
|
||||
uint8_t data_delivered;
|
||||
uint8_t xa_audio_delivered;
|
||||
uint8_t skip_reason;
|
||||
uint16_t bytes_len;
|
||||
uint8_t bytes[CDROM_SECTOR_HISTORY_BYTES];
|
||||
} CDROMSectorHistoryEntry;
|
||||
|
||||
void cdrom_debug_snapshot(CDROMDebugState* out);
|
||||
uint64_t cdrom_debug_get_trace(const CDROMTraceEntry** out_entries);
|
||||
void cdrom_debug_clear_trace(void);
|
||||
uint32_t cdrom_debug_copy_last_sector(uint32_t offset, uint32_t len,
|
||||
uint8_t* out,
|
||||
CDROMSectorDebugState* state);
|
||||
uint64_t cdrom_debug_get_sector_history(const CDROMSectorHistoryEntry** out_entries);
|
||||
void cdrom_debug_clear_sector_history(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -166,6 +166,10 @@ void debug_server_check_watchpoints(void);
|
||||
* -1 if no override is active. Value is PS1 16-bit button mask. */
|
||||
int debug_server_get_input_override(void);
|
||||
|
||||
/* TCP-controlled turbo mode. When enabled the frontend skips presentation and
|
||||
* wall-clock pacing at vblank, matching the keyboard TAB turbo path. */
|
||||
int debug_server_turbo_enabled(void);
|
||||
|
||||
/* ---- Send helpers (for extensions) ---- */
|
||||
|
||||
/* Set the CPU state pointer for register queries. Call after CPU init. */
|
||||
|
||||
@@ -27,9 +27,25 @@ void dma_init(void);
|
||||
uint32_t dma_read(uint32_t addr);
|
||||
void dma_write(uint32_t addr, uint32_t val);
|
||||
void dma_write_masked(uint32_t addr, uint32_t val, uint32_t mask);
|
||||
void dma_advance(uint32_t cycles);
|
||||
uint32_t dma_get_dicr(void);
|
||||
uint32_t dma_get_dpcr(void);
|
||||
|
||||
typedef struct DMAChannelDebugState {
|
||||
uint32_t madr;
|
||||
uint32_t bcr;
|
||||
uint32_t chcr;
|
||||
uint32_t active;
|
||||
uint32_t remaining_words;
|
||||
uint32_t cycles_accum;
|
||||
} DMAChannelDebugState;
|
||||
|
||||
typedef struct DMADebugState {
|
||||
uint32_t dpcr;
|
||||
uint32_t dicr;
|
||||
DMAChannelDebugState channels[7];
|
||||
} DMADebugState;
|
||||
|
||||
typedef struct DMATraceEntry {
|
||||
uint64_t seq;
|
||||
uint32_t frame;
|
||||
@@ -52,6 +68,7 @@ typedef struct DMATraceEntry {
|
||||
|
||||
uint64_t dma_debug_get_trace(const DMATraceEntry** out_entries);
|
||||
void dma_debug_clear_trace(void);
|
||||
void dma_debug_get_state(DMADebugState* out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ void fntrace_arm(uint32_t target); /* add to arm list, or 0 to clear */
|
||||
void fntrace_arm_clear(void);
|
||||
uint32_t fntrace_arm_count(void);
|
||||
uint32_t fntrace_arm_get(uint32_t i);
|
||||
void fntrace_arm_from_env(const char *env_name);
|
||||
|
||||
/* Reset ring (clears seq, leaves storage in place). */
|
||||
void fntrace_clear(void);
|
||||
|
||||
@@ -27,10 +27,14 @@ const uint16_t* gpu_get_vram(void); /* Pointer to 1024x512 16-bit VRAM */
|
||||
typedef struct {
|
||||
uint32_t display_x, display_y; /* VRAM start of display area (GP1(05h)) */
|
||||
uint32_t width, height; /* Derived from display mode + ranges */
|
||||
int depth24; /* GP1(08h) display depth flag: RGB888 scanout */
|
||||
int disabled; /* GP1(03h) display disable flag */
|
||||
} GpuDisplayInfo;
|
||||
|
||||
void gpu_get_display_info(GpuDisplayInfo* out);
|
||||
void gpu_display_pixel_rgb(const GpuDisplayInfo* di, uint32_t x, uint32_t y,
|
||||
uint8_t* r, uint8_t* g, uint8_t* b);
|
||||
uint32_t gpu_display_pixel_argb(const GpuDisplayInfo* di, uint32_t x, uint32_t y);
|
||||
uint64_t gpu_get_gp0_count(void); /* Total GP0 writes since init */
|
||||
void gpu_get_gp0_stats(uint64_t* nop, uint64_t* fill, uint64_t* draw, uint64_t* env, uint64_t* copy);
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@ extern "C" {
|
||||
#define TIMER_BASE 0x1F801100
|
||||
|
||||
void timers_init(void);
|
||||
void timers_get_snapshot(uint16_t counter[3], uint32_t mode[3],
|
||||
uint16_t target[3], int32_t irq_line[3],
|
||||
uint32_t frac[3]);
|
||||
void timers_set_snapshot(const uint16_t counter[3], const uint32_t mode[3],
|
||||
const uint16_t target[3], const int32_t irq_line[3],
|
||||
const uint32_t frac[3]);
|
||||
|
||||
/* MMIO read/write for 0x1F801100..0x1F80112F.
|
||||
* Accepts 16-bit or 32-bit access (caller zero-extends). */
|
||||
|
||||
@@ -233,3 +233,8 @@ function(psxrecomp_add_runtime_target target)
|
||||
$<$<CONFIG:Release>:/ENTRY:mainCRTStartup>)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Compatibility for early v4 game projects that used the longer helper name.
|
||||
function(psxrecomp_v4_add_runtime_target target)
|
||||
psxrecomp_add_runtime_target(${target} ${ARGN})
|
||||
endfunction()
|
||||
|
||||
+342
-30
@@ -30,6 +30,7 @@ extern uint64_t s_frame_count;
|
||||
/* CD-ROM state */
|
||||
static uint8_t index_reg;
|
||||
static uint8_t stat_reg;
|
||||
static uint8_t request_reg;
|
||||
static uint8_t irq_enable;
|
||||
static uint8_t irq_flag;
|
||||
|
||||
@@ -58,6 +59,19 @@ static uint8_t sector_buffer[SECTOR_BUFFER_SIZE];
|
||||
static int sector_read_pos;
|
||||
static int sector_available;
|
||||
static int sector_size;
|
||||
static uint8_t last_sector_buffer[SECTOR_BUFFER_SIZE];
|
||||
static int last_sector_lba;
|
||||
static int last_sector_size;
|
||||
static uint32_t last_sector_frame;
|
||||
static uint8_t last_sector_mode;
|
||||
static uint8_t last_sector_have_raw;
|
||||
static uint8_t last_sector_raw_mode;
|
||||
static uint8_t last_sector_xa_file;
|
||||
static uint8_t last_sector_xa_channel;
|
||||
static uint8_t last_sector_xa_submode;
|
||||
static uint8_t last_sector_xa_coding;
|
||||
static CDROMSectorHistoryEntry sector_history[CDROM_SECTOR_HISTORY_CAP];
|
||||
static uint64_t sector_history_seq;
|
||||
|
||||
/* Seek target */
|
||||
static uint8_t seek_min, seek_sec, seek_sect;
|
||||
@@ -75,15 +89,33 @@ static uint8_t cd_muted;
|
||||
#define XA_SUBHEADER_OFFSET 16
|
||||
#define XA_DATA_OFFSET 24
|
||||
#define XA_SOUND_GROUPS 18
|
||||
#define XA_NATIVE_FRAMES (XA_SOUND_GROUPS * 4 * 28)
|
||||
#define XA_MAX_44100_FRAMES 4704
|
||||
#define XA_NATIVE_FRAMES (XA_SOUND_GROUPS * 8 * 28)
|
||||
#define XA_MAX_44100_FRAMES 9408
|
||||
|
||||
#define XA_SUBMODE_AUDIO 0x04
|
||||
#define XA_SUBMODE_REALTIME 0x40
|
||||
#define CDROM_SECTOR_MODE2 0x02
|
||||
|
||||
#define CDROM_SKIP_NONE 0
|
||||
#define CDROM_SKIP_XA_AUDIO_REALTIME 1
|
||||
#define CDROM_REQUEST_BFRD 0x80u
|
||||
|
||||
typedef struct CDROMSectorDelivery {
|
||||
uint8_t raw_mode;
|
||||
uint8_t xa_file;
|
||||
uint8_t xa_channel;
|
||||
uint8_t xa_submode;
|
||||
uint8_t xa_coding;
|
||||
uint8_t data_delivered;
|
||||
uint8_t xa_audio_delivered;
|
||||
uint8_t skip_reason;
|
||||
} CDROMSectorDelivery;
|
||||
|
||||
static int32_t xa_hist_l[2];
|
||||
static int32_t xa_hist_r[2];
|
||||
static uint8_t xa_stream_file;
|
||||
static uint8_t xa_stream_channel;
|
||||
static uint8_t xa_stream_coding;
|
||||
static int xa_stream_active;
|
||||
|
||||
/* Operating divisor: 1x during BIOS boot, switches to g_game_divisor
|
||||
@@ -157,6 +189,7 @@ static void trace_cdrom(uint8_t kind, uint32_t addr, uint32_t val, uint8_t width
|
||||
e->i_stat = i_stat;
|
||||
e->index_reg = index_reg;
|
||||
e->stat_reg = stat_reg;
|
||||
e->request_reg = request_reg;
|
||||
e->irq_enable = irq_enable;
|
||||
e->irq_flag = irq_flag;
|
||||
e->mode_reg = mode_reg;
|
||||
@@ -174,6 +207,58 @@ static void trace_cdrom(uint8_t kind, uint32_t addr, uint32_t val, uint8_t width
|
||||
e->read_delay = read_delay;
|
||||
}
|
||||
|
||||
static int xa_is_audio_realtime(const CDROMSectorDelivery *d) {
|
||||
return d &&
|
||||
d->raw_mode == CDROM_SECTOR_MODE2 &&
|
||||
((d->xa_submode & (XA_SUBMODE_AUDIO | XA_SUBMODE_REALTIME)) ==
|
||||
(XA_SUBMODE_AUDIO | XA_SUBMODE_REALTIME));
|
||||
}
|
||||
|
||||
static CDROMSectorDelivery classify_raw_sector(const uint8_t *raw_data, int have_raw) {
|
||||
CDROMSectorDelivery d;
|
||||
memset(&d, 0, sizeof(d));
|
||||
if (!have_raw || !raw_data) return d;
|
||||
|
||||
d.raw_mode = raw_data[15];
|
||||
if (d.raw_mode == CDROM_SECTOR_MODE2) {
|
||||
d.xa_file = raw_data[XA_SUBHEADER_OFFSET + 0];
|
||||
d.xa_channel = raw_data[XA_SUBHEADER_OFFSET + 1];
|
||||
d.xa_submode = raw_data[XA_SUBHEADER_OFFSET + 2];
|
||||
d.xa_coding = raw_data[XA_SUBHEADER_OFFSET + 3];
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
static void record_sector_history(int lba, int size, uint8_t mode, int have_raw,
|
||||
const uint8_t *bytes,
|
||||
const CDROMSectorDelivery *delivery) {
|
||||
CDROMSectorHistoryEntry *e =
|
||||
§or_history[sector_history_seq % CDROM_SECTOR_HISTORY_CAP];
|
||||
e->seq = sector_history_seq++;
|
||||
e->lba = lba;
|
||||
e->size = size;
|
||||
e->frame = (uint32_t)s_frame_count;
|
||||
e->mode = mode;
|
||||
e->have_raw = (uint8_t)(have_raw ? 1 : 0);
|
||||
e->raw_mode = delivery ? delivery->raw_mode : 0;
|
||||
e->xa_file = delivery ? delivery->xa_file : 0;
|
||||
e->xa_channel = delivery ? delivery->xa_channel : 0;
|
||||
e->xa_submode = delivery ? delivery->xa_submode : 0;
|
||||
e->xa_coding = delivery ? delivery->xa_coding : 0;
|
||||
e->data_delivered = delivery ? delivery->data_delivered : 0;
|
||||
e->xa_audio_delivered = delivery ? delivery->xa_audio_delivered : 0;
|
||||
e->skip_reason = delivery ? delivery->skip_reason : CDROM_SKIP_NONE;
|
||||
e->bytes_len = (uint16_t)((size < CDROM_SECTOR_HISTORY_BYTES)
|
||||
? size : CDROM_SECTOR_HISTORY_BYTES);
|
||||
if (e->bytes_len && bytes) {
|
||||
memcpy(e->bytes, bytes, e->bytes_len);
|
||||
}
|
||||
if (e->bytes_len < CDROM_SECTOR_HISTORY_BYTES) {
|
||||
memset(e->bytes + e->bytes_len, 0,
|
||||
CDROM_SECTOR_HISTORY_BYTES - e->bytes_len);
|
||||
}
|
||||
}
|
||||
|
||||
static int has_disc(void) {
|
||||
return iso_handle != NULL;
|
||||
}
|
||||
@@ -233,6 +318,15 @@ static uint8_t bin_to_bcd(int val) {
|
||||
return (uint8_t)(((val / 10) << 4) | (val % 10));
|
||||
}
|
||||
|
||||
static void lba_to_msf(int lba, int pregap, int* m, int* s, int* f) {
|
||||
int frames = lba + pregap;
|
||||
if (frames < 0) frames = 0;
|
||||
*m = frames / (60 * 75);
|
||||
frames %= 60 * 75;
|
||||
*s = frames / 75;
|
||||
*f = frames % 75;
|
||||
}
|
||||
|
||||
static int16_t clamp16_cd(int32_t v) {
|
||||
if (v > 32767) return 32767;
|
||||
if (v < -32768) return -32768;
|
||||
@@ -244,6 +338,7 @@ static void xa_reset_decode(void) {
|
||||
xa_hist_r[0] = xa_hist_r[1] = 0;
|
||||
xa_stream_file = 0xFF;
|
||||
xa_stream_channel = 0xFF;
|
||||
xa_stream_coding = 0xFF;
|
||||
xa_stream_active = 0;
|
||||
}
|
||||
|
||||
@@ -292,6 +387,44 @@ static int xa_decode_sector_4bit_stereo(const uint8_t* data, int16_t* out) {
|
||||
return pair;
|
||||
}
|
||||
|
||||
static int xa_decode_sector_4bit_mono(const uint8_t* data, int16_t* out) {
|
||||
static const int k0[5] = { 0, 60, 115, 98, 122 };
|
||||
static const int k1[5] = { 0, 0, -52, -55, -60 };
|
||||
int pair = 0;
|
||||
|
||||
for (int g = 0; g < XA_SOUND_GROUPS; g++) {
|
||||
const uint8_t* grp = data + g * 128;
|
||||
for (int blk = 0; blk < 4; blk++) {
|
||||
for (int nibble = 0; nibble < 2; nibble++) {
|
||||
uint8_t hdr = grp[4 + blk * 2 + nibble];
|
||||
int shift = 12 - (int)(hdr & 0x0F);
|
||||
int filter = (hdr >> 4) & 0x03;
|
||||
if (shift < 0) shift = 0;
|
||||
|
||||
for (int i = 0; i < 28; i++) {
|
||||
uint8_t b = grp[16 + blk + i * 4];
|
||||
int32_t sample_nibble = nibble ? ((b >> 4) & 0x0F) : (b & 0x0F);
|
||||
if (sample_nibble >= 8) sample_nibble -= 16;
|
||||
|
||||
int32_t sample = (sample_nibble << shift)
|
||||
+ ((k0[filter] * xa_hist_l[0] + k1[filter] * xa_hist_l[1] + 32) >> 6);
|
||||
sample = clamp16_cd(sample);
|
||||
xa_hist_l[1] = xa_hist_l[0];
|
||||
xa_hist_l[0] = sample;
|
||||
|
||||
out[pair * 2 + 0] = (int16_t)sample;
|
||||
out[pair * 2 + 1] = (int16_t)sample;
|
||||
pair++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xa_hist_r[0] = xa_hist_l[0];
|
||||
xa_hist_r[1] = xa_hist_l[1];
|
||||
return pair;
|
||||
}
|
||||
|
||||
static int xa_resample_to_44100(const int16_t* in, int in_frames,
|
||||
int sample_rate, int16_t* out, int max_frames) {
|
||||
if (!in || !out || in_frames <= 0 || sample_rate <= 0 || max_frames <= 0) return 0;
|
||||
@@ -319,40 +452,46 @@ static int xa_resample_to_44100(const int16_t* in, int in_frames,
|
||||
return out_frames;
|
||||
}
|
||||
|
||||
static void maybe_deliver_xa_audio(const uint8_t* raw_data) {
|
||||
if (!(mode_reg & 0x40u) || !raw_data || cd_muted) return;
|
||||
static int maybe_deliver_xa_audio(const uint8_t* raw_data,
|
||||
const CDROMSectorDelivery *delivery) {
|
||||
if (!(mode_reg & 0x40u) || !raw_data || !delivery || cd_muted) return 0;
|
||||
if (!xa_is_audio_realtime(delivery)) return 0;
|
||||
|
||||
uint8_t file = raw_data[XA_SUBHEADER_OFFSET + 0];
|
||||
uint8_t channel = raw_data[XA_SUBHEADER_OFFSET + 1];
|
||||
uint8_t submode = raw_data[XA_SUBHEADER_OFFSET + 2];
|
||||
uint8_t coding = raw_data[XA_SUBHEADER_OFFSET + 3];
|
||||
if (!(submode & XA_SUBMODE_AUDIO)) return;
|
||||
uint8_t file = delivery->xa_file;
|
||||
uint8_t channel = delivery->xa_channel;
|
||||
uint8_t coding = delivery->xa_coding;
|
||||
|
||||
if ((mode_reg & 0x08u) &&
|
||||
(file != filter_file || channel != filter_channel)) {
|
||||
trace_cdrom('a', 0, ((uint32_t)file << 16) | ((uint32_t)channel << 8) | coding, 0);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stereo = (coding & 0x01u) != 0;
|
||||
int rate_code = (coding >> 2) & 0x03;
|
||||
int depth_code = (coding >> 4) & 0x03;
|
||||
int sample_rate = (rate_code == 0) ? 37800 : ((rate_code == 1) ? 18900 : 0);
|
||||
if (!stereo || depth_code != 0 || sample_rate == 0) {
|
||||
if (depth_code != 0 || sample_rate == 0) {
|
||||
trace_cdrom('X', 0, ((uint32_t)file << 16) | ((uint32_t)channel << 8) | coding, 0);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!xa_stream_active || xa_stream_file != file || xa_stream_channel != channel) {
|
||||
if (!xa_stream_active ||
|
||||
xa_stream_file != file ||
|
||||
xa_stream_channel != channel ||
|
||||
xa_stream_coding != coding) {
|
||||
xa_reset_decode();
|
||||
xa_stream_file = file;
|
||||
xa_stream_channel = channel;
|
||||
xa_stream_coding = coding;
|
||||
xa_stream_active = 1;
|
||||
}
|
||||
|
||||
int16_t native[XA_NATIVE_FRAMES * 2];
|
||||
int16_t pcm_44100[XA_MAX_44100_FRAMES * 2];
|
||||
int native_frames = xa_decode_sector_4bit_stereo(raw_data + XA_DATA_OFFSET, native);
|
||||
int native_frames = stereo
|
||||
? xa_decode_sector_4bit_stereo(raw_data + XA_DATA_OFFSET, native)
|
||||
: xa_decode_sector_4bit_mono(raw_data + XA_DATA_OFFSET, native);
|
||||
int out_frames = xa_resample_to_44100(native, native_frames, sample_rate,
|
||||
pcm_44100, XA_MAX_44100_FRAMES);
|
||||
spu_cd_audio_push(pcm_44100, out_frames);
|
||||
@@ -360,18 +499,21 @@ static void maybe_deliver_xa_audio(const uint8_t* raw_data) {
|
||||
((uint32_t)file << 24) | ((uint32_t)channel << 16) |
|
||||
((uint32_t)coding << 8) | ((uint32_t)(out_frames / 32) & 0xFFu),
|
||||
0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void read_sector_at(int min, int sec, int sect) {
|
||||
static int read_sector_at(int min, int sec, int sect) {
|
||||
int lba = msf_to_lba(min, sec, sect);
|
||||
uint8_t user_data[SECTOR_SIZE];
|
||||
uint8_t raw_data[RAW_SECTOR_SIZE];
|
||||
int have_raw = 0;
|
||||
CDROMSectorDelivery delivery;
|
||||
const uint8_t *history_bytes = user_data;
|
||||
int history_size = SECTOR_SIZE;
|
||||
|
||||
if (iso_handle) {
|
||||
have_raw = iso_read_raw_sector(iso_handle, lba, raw_data, RAW_SECTOR_SIZE);
|
||||
if (have_raw) {
|
||||
maybe_deliver_xa_audio(raw_data);
|
||||
memcpy(user_data, raw_data + RAW_USER_DATA_OFFSET, SECTOR_SIZE);
|
||||
} else if (!iso_read_sector(iso_handle, lba, user_data, SECTOR_SIZE)) {
|
||||
memset(user_data, 0, sizeof(user_data));
|
||||
@@ -380,11 +522,23 @@ static void read_sector_at(int min, int sec, int sect) {
|
||||
memset(user_data, 0, sizeof(user_data));
|
||||
}
|
||||
|
||||
delivery = classify_raw_sector(raw_data, have_raw);
|
||||
delivery.xa_audio_delivered =
|
||||
(uint8_t)maybe_deliver_xa_audio(raw_data, &delivery);
|
||||
delivery.data_delivered = 1;
|
||||
if (delivery.xa_audio_delivered ||
|
||||
((mode_reg & 0x08u) && xa_is_audio_realtime(&delivery))) {
|
||||
delivery.data_delivered = 0;
|
||||
delivery.skip_reason = CDROM_SKIP_XA_AUDIO_REALTIME;
|
||||
}
|
||||
|
||||
memset(sector_buffer, 0, sizeof(sector_buffer));
|
||||
if (mode_reg & 0x20) {
|
||||
if (delivery.data_delivered && (mode_reg & 0x20)) {
|
||||
if (have_raw) {
|
||||
memcpy(sector_buffer, raw_data + WHOLE_SECTOR_OFFSET, WHOLE_SECTOR_SIZE);
|
||||
sector_size = WHOLE_SECTOR_SIZE;
|
||||
history_bytes = sector_buffer;
|
||||
history_size = sector_size;
|
||||
} else {
|
||||
sector_buffer[0] = bin_to_bcd(min);
|
||||
sector_buffer[1] = bin_to_bcd(sec);
|
||||
@@ -392,15 +546,52 @@ static void read_sector_at(int min, int sec, int sect) {
|
||||
sector_buffer[3] = 0x02; /* Mode 2 sector. */
|
||||
memcpy(sector_buffer + FALLBACK_SECTOR_HEADER_SIZE, user_data, SECTOR_SIZE);
|
||||
sector_size = FALLBACK_WHOLE_SECTOR_SIZE;
|
||||
history_bytes = sector_buffer;
|
||||
history_size = sector_size;
|
||||
}
|
||||
} else {
|
||||
} else if (delivery.data_delivered) {
|
||||
memcpy(sector_buffer, user_data, SECTOR_SIZE);
|
||||
sector_size = SECTOR_SIZE;
|
||||
history_bytes = sector_buffer;
|
||||
history_size = sector_size;
|
||||
} else {
|
||||
sector_size = 0;
|
||||
}
|
||||
|
||||
sector_read_pos = 0;
|
||||
sector_available = 1;
|
||||
sector_available = delivery.data_delivered ? 1 : 0;
|
||||
if (delivery.data_delivered) {
|
||||
memcpy(last_sector_buffer, sector_buffer, (size_t)sector_size);
|
||||
} else {
|
||||
uint32_t copy_size = history_size < SECTOR_BUFFER_SIZE ? (uint32_t)history_size
|
||||
: SECTOR_BUFFER_SIZE;
|
||||
memcpy(last_sector_buffer, history_bytes, copy_size);
|
||||
if (copy_size < SECTOR_BUFFER_SIZE) {
|
||||
memset(last_sector_buffer + copy_size, 0, SECTOR_BUFFER_SIZE - copy_size);
|
||||
}
|
||||
}
|
||||
last_sector_lba = lba;
|
||||
last_sector_size = delivery.data_delivered ? sector_size : history_size;
|
||||
last_sector_frame = (uint32_t)s_frame_count;
|
||||
last_sector_mode = mode_reg;
|
||||
last_sector_have_raw = (uint8_t)(have_raw ? 1 : 0);
|
||||
last_sector_raw_mode = delivery.raw_mode;
|
||||
last_sector_xa_file = delivery.xa_file;
|
||||
last_sector_xa_channel = delivery.xa_channel;
|
||||
last_sector_xa_submode = delivery.xa_submode;
|
||||
last_sector_xa_coding = delivery.xa_coding;
|
||||
record_sector_history(lba, history_size, mode_reg, have_raw,
|
||||
history_bytes, &delivery);
|
||||
trace_cdrom('S', 0, (uint32_t)lba, 0);
|
||||
if (!delivery.data_delivered) {
|
||||
trace_cdrom('s', 0,
|
||||
((uint32_t)delivery.xa_file << 24) |
|
||||
((uint32_t)delivery.xa_channel << 16) |
|
||||
((uint32_t)delivery.xa_submode << 8) |
|
||||
delivery.xa_coding,
|
||||
0);
|
||||
}
|
||||
return delivery.data_delivered ? 1 : 0;
|
||||
}
|
||||
|
||||
static void advance_msf(int* m, int* s, int* f) {
|
||||
@@ -413,6 +604,7 @@ static void clear_sector_buffer(void) {
|
||||
sector_read_pos = 0;
|
||||
sector_size = 0;
|
||||
sector_available = 0;
|
||||
request_reg &= (uint8_t)~CDROM_REQUEST_BFRD;
|
||||
}
|
||||
|
||||
static void start_read_stream(uint8_t cmd) {
|
||||
@@ -436,13 +628,15 @@ static void stop_read_stream(void) {
|
||||
read_delay = 0;
|
||||
}
|
||||
|
||||
static void deliver_read_sector(void) {
|
||||
read_sector_at(read_min, read_sec, read_sect);
|
||||
static int deliver_read_sector(void) {
|
||||
int delivered = read_sector_at(read_min, read_sec, read_sect);
|
||||
advance_msf(&read_min, &read_sec, &read_sect);
|
||||
if (!delivered) return 0;
|
||||
response_clear();
|
||||
response_push(stat_reg);
|
||||
set_irq(CDIRQ_DATA_READY);
|
||||
fire_cdrom_irq();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void exec_command(uint8_t cmd) {
|
||||
@@ -541,6 +735,60 @@ static void exec_command(uint8_t cmd) {
|
||||
set_irq(CDIRQ_ACK);
|
||||
break;
|
||||
|
||||
case 0x0F: /* Getparam */
|
||||
response_push(stat_reg);
|
||||
response_push(mode_reg);
|
||||
response_push(0x00);
|
||||
response_push(filter_file);
|
||||
response_push(filter_channel);
|
||||
set_irq(CDIRQ_ACK);
|
||||
break;
|
||||
|
||||
case 0x10: { /* GetlocL */
|
||||
int lba = (last_sector_lba >= 0)
|
||||
? last_sector_lba
|
||||
: msf_to_lba(read_min, read_sec, read_sect);
|
||||
int m, s, f;
|
||||
lba_to_msf(lba, 150, &m, &s, &f);
|
||||
response_push(bin_to_bcd(m));
|
||||
response_push(bin_to_bcd(s));
|
||||
response_push(bin_to_bcd(f));
|
||||
response_push(last_sector_raw_mode ? last_sector_raw_mode : CDROM_SECTOR_MODE2);
|
||||
response_push(last_sector_xa_file);
|
||||
response_push(last_sector_xa_channel);
|
||||
response_push(last_sector_xa_submode);
|
||||
response_push(last_sector_xa_coding);
|
||||
set_irq(CDIRQ_ACK);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x11: { /* GetlocP */
|
||||
int lba = (last_sector_lba >= 0)
|
||||
? last_sector_lba
|
||||
: msf_to_lba(read_min, read_sec, read_sect);
|
||||
int rm, rs, rf;
|
||||
int am, as, af;
|
||||
lba_to_msf(lba, 0, &rm, &rs, &rf);
|
||||
lba_to_msf(lba, 150, &am, &as, &af);
|
||||
response_push(0x01); /* single data track */
|
||||
response_push(0x01);
|
||||
response_push(bin_to_bcd(rm));
|
||||
response_push(bin_to_bcd(rs));
|
||||
response_push(bin_to_bcd(rf));
|
||||
response_push(bin_to_bcd(am));
|
||||
response_push(bin_to_bcd(as));
|
||||
response_push(bin_to_bcd(af));
|
||||
set_irq(CDIRQ_ACK);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x13: /* GetTN */
|
||||
response_push(stat_reg);
|
||||
response_push(0x01);
|
||||
response_push(0x01);
|
||||
set_irq(CDIRQ_ACK);
|
||||
break;
|
||||
|
||||
case 0x15: /* SeekL */
|
||||
if (!has_disc()) {
|
||||
response_push(stat_reg | CDSTAT_ERROR | CDSTAT_SEEKERR);
|
||||
@@ -630,11 +878,13 @@ static void process_pending(uint32_t cycles) {
|
||||
case 0x06: /* ReadN data ready */
|
||||
case 0x1B: /* ReadS data ready */
|
||||
if (reading) {
|
||||
read_sector_at(read_min, read_sec, read_sect);
|
||||
int delivered = read_sector_at(read_min, read_sec, read_sect);
|
||||
advance_msf(&read_min, &read_sec, &read_sect);
|
||||
response_push(stat_reg);
|
||||
set_irq(CDIRQ_DATA_READY);
|
||||
fire_cdrom_irq();
|
||||
if (delivered) {
|
||||
response_push(stat_reg);
|
||||
set_irq(CDIRQ_DATA_READY);
|
||||
fire_cdrom_irq();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -713,8 +963,10 @@ void cdrom_init(const char* cue_path) {
|
||||
memset(param_fifo, 0, sizeof(param_fifo));
|
||||
memset(response_fifo, 0, sizeof(response_fifo));
|
||||
memset(sector_buffer, 0, sizeof(sector_buffer));
|
||||
memset(last_sector_buffer, 0, sizeof(last_sector_buffer));
|
||||
|
||||
index_reg = 0;
|
||||
request_reg = 0;
|
||||
irq_enable = 0x1F;
|
||||
irq_flag = 0;
|
||||
param_count = 0;
|
||||
@@ -723,6 +975,16 @@ void cdrom_init(const char* cue_path) {
|
||||
sector_read_pos = 0;
|
||||
sector_size = 0;
|
||||
sector_available = 0;
|
||||
last_sector_lba = -1;
|
||||
last_sector_size = 0;
|
||||
last_sector_frame = 0;
|
||||
last_sector_mode = 0;
|
||||
last_sector_have_raw = 0;
|
||||
last_sector_raw_mode = 0;
|
||||
last_sector_xa_file = 0;
|
||||
last_sector_xa_channel = 0;
|
||||
last_sector_xa_submode = 0;
|
||||
last_sector_xa_coding = 0;
|
||||
stop_read_stream();
|
||||
mode_reg = 0;
|
||||
filter_file = 0;
|
||||
@@ -732,6 +994,7 @@ void cdrom_init(const char* cue_path) {
|
||||
spu_cd_audio_reset();
|
||||
pending.pending = 0;
|
||||
seek_min = seek_sec = seek_sect = 0;
|
||||
cdrom_debug_clear_sector_history();
|
||||
|
||||
if (cue_path) {
|
||||
iso_handle = iso_open(cue_path);
|
||||
@@ -751,7 +1014,7 @@ uint32_t cdrom_read(uint32_t addr) {
|
||||
if (param_count == 0) s |= (1 << 3);
|
||||
if (param_count < PARAM_FIFO_SIZE) s |= (1 << 4);
|
||||
if (response_read < response_count) s |= (1 << 5);
|
||||
if (sector_available) s |= (1 << 6);
|
||||
if (request_reg & CDROM_REQUEST_BFRD) s |= (1 << 6);
|
||||
ret = s;
|
||||
break;
|
||||
}
|
||||
@@ -763,10 +1026,11 @@ uint32_t cdrom_read(uint32_t addr) {
|
||||
break;
|
||||
|
||||
case 0x1F801802:
|
||||
if (sector_available && sector_read_pos < sector_size) {
|
||||
if ((request_reg & CDROM_REQUEST_BFRD) && sector_available && sector_read_pos < sector_size) {
|
||||
ret = sector_buffer[sector_read_pos++];
|
||||
if (sector_read_pos >= sector_size) {
|
||||
sector_available = 0;
|
||||
request_reg &= (uint8_t)~CDROM_REQUEST_BFRD;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -814,7 +1078,12 @@ void cdrom_write(uint32_t addr, uint32_t value) {
|
||||
break;
|
||||
|
||||
case 0x1F801803:
|
||||
if (index_reg == 1) {
|
||||
if (index_reg == 0) {
|
||||
request_reg = val;
|
||||
if (!(request_reg & CDROM_REQUEST_BFRD)) {
|
||||
sector_read_pos = 0;
|
||||
}
|
||||
} else if (index_reg == 1) {
|
||||
irq_flag &= ~(val & 0x1F);
|
||||
if (val & 0x40) {
|
||||
param_count = 0;
|
||||
@@ -838,11 +1107,12 @@ void cdrom_tick(void) {
|
||||
|
||||
uint32_t cdrom_dma_read(void) {
|
||||
uint32_t val = 0;
|
||||
if (sector_available && sector_read_pos + 4 <= sector_size) {
|
||||
if ((request_reg & CDROM_REQUEST_BFRD) && sector_available && sector_read_pos + 4 <= sector_size) {
|
||||
memcpy(&val, sector_buffer + sector_read_pos, 4);
|
||||
sector_read_pos += 4;
|
||||
if (sector_read_pos >= sector_size) {
|
||||
sector_available = 0;
|
||||
request_reg &= (uint8_t)~CDROM_REQUEST_BFRD;
|
||||
}
|
||||
}
|
||||
trace_cdrom('D', 0, val, 4);
|
||||
@@ -850,7 +1120,9 @@ uint32_t cdrom_dma_read(void) {
|
||||
}
|
||||
|
||||
int cdrom_dma_ready(void) {
|
||||
return sector_available && (sector_read_pos < sector_size);
|
||||
return (request_reg & CDROM_REQUEST_BFRD) &&
|
||||
sector_available &&
|
||||
(sector_read_pos + 4 <= sector_size);
|
||||
}
|
||||
|
||||
void cdrom_debug_snapshot(CDROMDebugState* out) {
|
||||
@@ -859,6 +1131,7 @@ void cdrom_debug_snapshot(CDROMDebugState* out) {
|
||||
out->seq = cdrom_trace_seq;
|
||||
out->index_reg = index_reg;
|
||||
out->stat_reg = stat_reg;
|
||||
out->request_reg = request_reg;
|
||||
out->irq_enable = irq_enable;
|
||||
out->irq_flag = irq_flag;
|
||||
out->mode_reg = mode_reg;
|
||||
@@ -886,6 +1159,11 @@ void cdrom_debug_snapshot(CDROMDebugState* out) {
|
||||
out->pending_delay = pending.delay;
|
||||
out->pending_phase = pending.phase;
|
||||
out->i_stat = i_stat;
|
||||
out->last_sector_lba = last_sector_lba;
|
||||
out->last_sector_size = last_sector_size;
|
||||
out->last_sector_frame = last_sector_frame;
|
||||
out->last_sector_mode = last_sector_mode;
|
||||
out->last_sector_have_raw = last_sector_have_raw;
|
||||
}
|
||||
|
||||
uint64_t cdrom_debug_get_trace(const CDROMTraceEntry** out_entries) {
|
||||
@@ -897,3 +1175,37 @@ void cdrom_debug_clear_trace(void) {
|
||||
memset(cdrom_trace, 0, sizeof(cdrom_trace));
|
||||
cdrom_trace_seq = 0;
|
||||
}
|
||||
|
||||
uint64_t cdrom_debug_get_sector_history(const CDROMSectorHistoryEntry** out_entries) {
|
||||
if (out_entries) *out_entries = sector_history;
|
||||
return sector_history_seq;
|
||||
}
|
||||
|
||||
void cdrom_debug_clear_sector_history(void) {
|
||||
memset(sector_history, 0, sizeof(sector_history));
|
||||
sector_history_seq = 0;
|
||||
}
|
||||
|
||||
uint32_t cdrom_debug_copy_last_sector(uint32_t offset, uint32_t len,
|
||||
uint8_t* out,
|
||||
CDROMSectorDebugState* state) {
|
||||
if (state) {
|
||||
memset(state, 0, sizeof(*state));
|
||||
state->current_available = sector_available;
|
||||
state->current_read_pos = sector_read_pos;
|
||||
state->current_size = sector_size;
|
||||
state->last_lba = last_sector_lba;
|
||||
state->last_size = last_sector_size;
|
||||
state->last_frame = last_sector_frame;
|
||||
state->last_mode = last_sector_mode;
|
||||
state->last_have_raw = last_sector_have_raw;
|
||||
}
|
||||
|
||||
if (!out || last_sector_size <= 0 || offset >= (uint32_t)last_sector_size) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t avail = (uint32_t)last_sector_size - offset;
|
||||
if (len > avail) len = avail;
|
||||
memcpy(out, last_sector_buffer + offset, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
+307
-26
@@ -18,6 +18,7 @@
|
||||
#include "mdec.h"
|
||||
#include "interrupts.h"
|
||||
#include "psx_cycles.h"
|
||||
#include "timers.h"
|
||||
#include "dirty_ram_interp.h"
|
||||
#include "card_read_summary.h"
|
||||
#include "card_data_writes.h"
|
||||
@@ -105,6 +106,9 @@ static uint64_t s_dirty_break_hits = 0;
|
||||
static int s_input_override = -1;
|
||||
static int s_input_frames = 0;
|
||||
|
||||
/* ---- Frontend turbo override ---- */
|
||||
static volatile int s_turbo_enabled = 0;
|
||||
|
||||
/* ---- Ring buffer (heap-allocated) ---- */
|
||||
static PSXFrameRecord *s_frame_history = NULL;
|
||||
static uint64_t s_history_count = 0;
|
||||
@@ -1158,11 +1162,37 @@ static void evcb_snapshot_capture(EvCBTag tag, uint64_t fn_entry_seq) {
|
||||
static int s_fn_trace_active = 0;
|
||||
static uint32_t s_fn_trace_filter_lo = 0u;
|
||||
static uint32_t s_fn_trace_filter_hi = 0xFFFFFFFFu;
|
||||
static uint64_t s_fn_direct_seen = 0;
|
||||
static uint64_t s_fn_direct_no_cpu = 0;
|
||||
static uint64_t s_fn_direct_filtered = 0;
|
||||
|
||||
static int fn_trace_in_filter(uint32_t phys) {
|
||||
if (!s_fn_trace_active) return 0;
|
||||
return phys >= s_fn_trace_filter_lo && phys < s_fn_trace_filter_hi;
|
||||
}
|
||||
|
||||
static void fn_trace_filter_from_env(const char *env_name) {
|
||||
const char *spec = getenv(env_name);
|
||||
if (!spec || !*spec) return;
|
||||
|
||||
char *end = NULL;
|
||||
uint32_t lo = (uint32_t)strtoul(spec, &end, 0);
|
||||
if (end == spec) return;
|
||||
|
||||
while (*end == ' ' || *end == '\t') end++;
|
||||
if (*end != ':' && *end != '-' && *end != ',' && *end != ';') return;
|
||||
end++;
|
||||
|
||||
while (*end == ' ' || *end == '\t') end++;
|
||||
char *end2 = NULL;
|
||||
uint32_t hi = (uint32_t)strtoul(end, &end2, 0);
|
||||
if (end2 == end || hi <= lo) return;
|
||||
|
||||
s_fn_trace_filter_lo = lo;
|
||||
s_fn_trace_filter_hi = hi;
|
||||
s_fn_trace_active = 1;
|
||||
}
|
||||
|
||||
static int card_mgr_trace_target(uint32_t phys) {
|
||||
phys &= 0x1FFFFFFFu;
|
||||
if (phys >= 0x1FC0B600u && phys < 0x1FC0C240u) return 1;
|
||||
@@ -1571,12 +1601,17 @@ void debug_server_log_call_entry(uint32_t func_addr) {
|
||||
(void)func_addr;
|
||||
return;
|
||||
#endif
|
||||
if (!debug_cpu_ptr) return;
|
||||
s_fn_direct_seen++;
|
||||
if (!debug_cpu_ptr) {
|
||||
s_fn_direct_no_cpu++;
|
||||
return;
|
||||
}
|
||||
card_mgr_trace_record(func_addr, 0);
|
||||
sreg_trace_record(func_addr);
|
||||
call_focus_record(func_addr);
|
||||
if (!s_fn_entry) return;
|
||||
if (!fn_trace_in_filter(func_addr)) return;
|
||||
s_fn_direct_filtered++;
|
||||
FnEntryEntry *e = &s_fn_entry[s_fn_entry_seq % FN_TRACE_CAP];
|
||||
e->seq = s_fn_entry_seq;
|
||||
e->paired_exit_seq = 0;
|
||||
@@ -3293,6 +3328,7 @@ static void handle_gpu_state(int id, const char *json)
|
||||
send_fmt("{\"id\":%d,\"ok\":true,"
|
||||
"\"display_x\":%d,\"display_y\":%d,"
|
||||
"\"width\":%d,\"height\":%d,"
|
||||
"\"depth\":%d,\"depth24\":%d,"
|
||||
"\"disabled\":%d,"
|
||||
"\"gpustat\":\"0x%08X\","
|
||||
"\"gp0_writes\":%llu,"
|
||||
@@ -3301,6 +3337,7 @@ static void handle_gpu_state(int id, const char *json)
|
||||
"\"draw_offset\":[%d,%d]}",
|
||||
id, di.display_x, di.display_y,
|
||||
di.width, di.height,
|
||||
di.depth24 ? 24 : 15, di.depth24,
|
||||
di.disabled,
|
||||
gpustat,
|
||||
(unsigned long long)gpu_get_gp0_count(),
|
||||
@@ -3360,6 +3397,26 @@ static void handle_irq_state(int id, const char *json)
|
||||
dma_get_dpcr(), dma_get_dicr());
|
||||
}
|
||||
|
||||
static void handle_timers_state(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
uint16_t counter[3], target[3];
|
||||
uint32_t mode[3], frac[3];
|
||||
int32_t irq_line[3];
|
||||
timers_get_snapshot(counter, mode, target, irq_line, frac);
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"timers\":["
|
||||
"{\"ch\":0,\"counter\":%u,\"mode\":\"0x%04X\",\"target\":%u,"
|
||||
"\"irq_line\":%d,\"frac\":%u},"
|
||||
"{\"ch\":1,\"counter\":%u,\"mode\":\"0x%04X\",\"target\":%u,"
|
||||
"\"irq_line\":%d,\"frac\":%u},"
|
||||
"{\"ch\":2,\"counter\":%u,\"mode\":\"0x%04X\",\"target\":%u,"
|
||||
"\"irq_line\":%d,\"frac\":%u}]}",
|
||||
id,
|
||||
counter[0], mode[0], target[0], irq_line[0], frac[0],
|
||||
counter[1], mode[1], target[1], irq_line[1], frac[1],
|
||||
counter[2], mode[2], target[2], irq_line[2], frac[2]);
|
||||
}
|
||||
|
||||
/* GPU opcode counter — defined in gpu.c */
|
||||
static const char *cdrom_trace_kind_name(uint8_t kind)
|
||||
{
|
||||
@@ -3370,6 +3427,7 @@ static const char *cdrom_trace_kind_name(uint8_t kind)
|
||||
case 'F': return "fire_irq";
|
||||
case 'f': return "irq_masked";
|
||||
case 'S': return "sector";
|
||||
case 's': return "sector_skip";
|
||||
case 'A': return "xa_audio";
|
||||
case 'a': return "xa_skip";
|
||||
case 'X': return "xa_unsupported";
|
||||
@@ -3389,6 +3447,7 @@ static void handle_cdrom_state(int id, const char *json)
|
||||
send_fmt("{\"id\":%d,\"ok\":true,"
|
||||
"\"seq\":%llu,\"has_disc\":%d,"
|
||||
"\"index\":%u,\"stat\":\"0x%02X\","
|
||||
"\"request\":\"0x%02X\","
|
||||
"\"irq_enable\":\"0x%02X\",\"irq_flag\":\"0x%02X\","
|
||||
"\"mode\":\"0x%02X\","
|
||||
"\"param_count\":%d,\"response_read\":%d,\"response_count\":%d,"
|
||||
@@ -3398,9 +3457,11 @@ static void handle_cdrom_state(int id, const char *json)
|
||||
"\"filter_file\":%u,\"filter_channel\":%u,\"muted\":%u,"
|
||||
"\"seek_msf\":[%u,%u,%u],"
|
||||
"\"pending\":{\"cmd\":\"0x%02X\",\"active\":%d,\"delay\":%d,\"phase\":%d},"
|
||||
"\"last_sector\":{\"lba\":%d,\"size\":%d,\"frame\":%u,"
|
||||
"\"mode\":\"0x%02X\",\"have_raw\":%u},"
|
||||
"\"i_stat\":\"0x%08X\"}",
|
||||
id, (unsigned long long)s.seq, s.has_disc,
|
||||
s.index_reg, s.stat_reg, s.irq_enable, s.irq_flag,
|
||||
s.index_reg, s.stat_reg, s.request_reg, s.irq_enable, s.irq_flag,
|
||||
s.mode_reg,
|
||||
s.param_count, s.response_read, s.response_count,
|
||||
s.sector_available, s.sector_read_pos, s.sector_size,
|
||||
@@ -3409,7 +3470,131 @@ static void handle_cdrom_state(int id, const char *json)
|
||||
s.filter_file, s.filter_channel, s.muted,
|
||||
s.seek_min, s.seek_sec, s.seek_sect,
|
||||
s.pending_cmd, s.pending_pending, s.pending_delay,
|
||||
s.pending_phase, s.i_stat);
|
||||
s.pending_phase,
|
||||
s.last_sector_lba, s.last_sector_size, s.last_sector_frame,
|
||||
s.last_sector_mode, s.last_sector_have_raw,
|
||||
s.i_stat);
|
||||
}
|
||||
|
||||
static void handle_cdrom_sector_dump(int id, const char *json)
|
||||
{
|
||||
int offset = json_get_int(json, "offset", 0);
|
||||
int len = json_get_int(json, "len", 128);
|
||||
if (offset < 0) offset = 0;
|
||||
if (len < 1) len = 1;
|
||||
if (len > 2340) len = 2340;
|
||||
|
||||
uint8_t *bytes = (uint8_t *)malloc((size_t)len);
|
||||
if (!bytes) { send_err(id, "oom"); return; }
|
||||
|
||||
CDROMSectorDebugState s;
|
||||
uint32_t got = cdrom_debug_copy_last_sector((uint32_t)offset,
|
||||
(uint32_t)len,
|
||||
bytes, &s);
|
||||
|
||||
size_t bufsz = 512u + (size_t)got * 2u;
|
||||
char *buf = (char *)malloc(bufsz);
|
||||
if (!buf) {
|
||||
free(bytes);
|
||||
send_err(id, "oom");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
pos += snprintf(buf + pos, bufsz - pos,
|
||||
"{\"id\":%d,\"ok\":true,"
|
||||
"\"current\":{\"available\":%d,\"read_pos\":%d,\"size\":%d},"
|
||||
"\"last\":{\"lba\":%d,\"size\":%d,\"frame\":%u,"
|
||||
"\"mode\":\"0x%02X\",\"have_raw\":%u},"
|
||||
"\"offset\":%d,\"len\":%u,\"hex\":\"",
|
||||
id,
|
||||
s.current_available, s.current_read_pos, s.current_size,
|
||||
s.last_lba, s.last_size, s.last_frame,
|
||||
s.last_mode, s.last_have_raw,
|
||||
offset, got);
|
||||
for (uint32_t i = 0; i < got && pos + 3 < bufsz; i++) {
|
||||
pos += snprintf(buf + pos, bufsz - pos, "%02x", bytes[i]);
|
||||
}
|
||||
snprintf(buf + pos, bufsz - pos, "\"}");
|
||||
debug_server_send_line(buf);
|
||||
free(buf);
|
||||
free(bytes);
|
||||
}
|
||||
|
||||
static void append_hex_bytes(char *buf, size_t bufsz, size_t *pos,
|
||||
const uint8_t *bytes, uint32_t len)
|
||||
{
|
||||
for (uint32_t i = 0; i < len && *pos + 3 < bufsz; i++) {
|
||||
*pos += snprintf(buf + *pos, bufsz - *pos, "%02x", bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_cdrom_sector_history(int id, const char *json)
|
||||
{
|
||||
int count = json_get_int(json, "count", 64);
|
||||
if (count < 1) count = 1;
|
||||
if (count > CDROM_SECTOR_HISTORY_CAP) count = CDROM_SECTOR_HISTORY_CAP;
|
||||
|
||||
int filter_lba = -1;
|
||||
char lba_str[32];
|
||||
if (json_get_str(json, "lba", lba_str, sizeof(lba_str))) {
|
||||
filter_lba = (int)hex_to_u32(lba_str);
|
||||
}
|
||||
|
||||
const CDROMSectorHistoryEntry *entries = NULL;
|
||||
uint64_t total = cdrom_debug_get_sector_history(&entries);
|
||||
uint64_t oldest = (total > CDROM_SECTOR_HISTORY_CAP)
|
||||
? total - CDROM_SECTOR_HISTORY_CAP : 0;
|
||||
|
||||
size_t bufsz = 256u + (size_t)count * 760u;
|
||||
char *buf = (char *)malloc(bufsz);
|
||||
if (!buf) { send_err(id, "oom"); return; }
|
||||
|
||||
size_t pos = 0;
|
||||
int emitted = 0;
|
||||
pos += snprintf(buf + pos, bufsz - pos,
|
||||
"{\"id\":%d,\"ok\":true,\"total\":%llu,"
|
||||
"\"oldest\":%llu,\"entries\":[",
|
||||
id, (unsigned long long)total,
|
||||
(unsigned long long)oldest);
|
||||
|
||||
uint64_t seq = total;
|
||||
while (seq > oldest && emitted < count && pos < bufsz - 760) {
|
||||
seq--;
|
||||
const CDROMSectorHistoryEntry *e =
|
||||
&entries[seq % CDROM_SECTOR_HISTORY_CAP];
|
||||
if (e->seq != seq) continue;
|
||||
if (filter_lba >= 0 && e->lba != filter_lba) continue;
|
||||
|
||||
pos += snprintf(buf + pos, bufsz - pos,
|
||||
"%s{\"seq\":%llu,\"lba\":%d,\"size\":%d,"
|
||||
"\"frame\":%u,\"mode\":\"0x%02X\","
|
||||
"\"have_raw\":%u,\"raw_mode\":\"0x%02X\","
|
||||
"\"xa_file\":%u,\"xa_channel\":%u,"
|
||||
"\"xa_submode\":\"0x%02X\",\"xa_coding\":\"0x%02X\","
|
||||
"\"data_delivered\":%u,\"xa_audio_delivered\":%u,"
|
||||
"\"skip_reason\":%u,\"bytes_len\":%u,\"hex\":\"",
|
||||
emitted ? "," : "",
|
||||
(unsigned long long)e->seq, e->lba, e->size,
|
||||
e->frame, e->mode, e->have_raw, e->raw_mode,
|
||||
e->xa_file, e->xa_channel, e->xa_submode, e->xa_coding,
|
||||
e->data_delivered, e->xa_audio_delivered,
|
||||
e->skip_reason, e->bytes_len);
|
||||
append_hex_bytes(buf, bufsz, &pos, e->bytes, e->bytes_len);
|
||||
pos += snprintf(buf + pos, bufsz - pos, "\"}");
|
||||
emitted++;
|
||||
}
|
||||
|
||||
pos += snprintf(buf + pos, bufsz - pos, "],\"emitted\":%d}", emitted);
|
||||
debug_server_send_line(buf);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void handle_cdrom_sector_history_clear(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
cdrom_debug_clear_sector_history();
|
||||
send_ok(id);
|
||||
}
|
||||
|
||||
static void handle_cdrom_trace_clear(int id, const char *json)
|
||||
@@ -3448,6 +3633,7 @@ static void handle_cdrom_trace_dump(int id, const char *json)
|
||||
"\"val\":\"0x%08X\",\"w\":%u,\"func\":\"0x%08X\","
|
||||
"\"pc\":\"0x%08X\",\"frame\":%u,\"i_stat\":\"0x%08X\","
|
||||
"\"index\":%u,\"stat\":\"0x%02X\","
|
||||
"\"request\":\"0x%02X\","
|
||||
"\"irq_enable\":\"0x%02X\",\"irq_flag\":\"0x%02X\","
|
||||
"\"mode\":\"0x%02X\","
|
||||
"\"param\":%u,\"resp_read\":%u,\"resp_count\":%u,"
|
||||
@@ -3459,7 +3645,7 @@ static void handle_cdrom_trace_dump(int id, const char *json)
|
||||
(unsigned long long)e->seq, cdrom_trace_kind_name(e->kind),
|
||||
e->addr, e->val, (unsigned)e->width,
|
||||
e->func, e->pc, e->frame, e->i_stat,
|
||||
e->index_reg, e->stat_reg, e->irq_enable, e->irq_flag,
|
||||
e->index_reg, e->stat_reg, e->request_reg, e->irq_enable, e->irq_flag,
|
||||
e->mode_reg,
|
||||
e->param_count, e->response_read, e->response_count,
|
||||
e->sector_available, e->sector_read_pos, e->sector_size,
|
||||
@@ -3482,6 +3668,34 @@ static const char *dma_trace_kind_name(uint32_t kind)
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_dma_state(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
DMADebugState s;
|
||||
dma_debug_get_state(&s);
|
||||
|
||||
char buf[2048];
|
||||
size_t pos = 0;
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
||||
"{\"id\":%d,\"ok\":true,\"dpcr\":\"0x%08X\","
|
||||
"\"dicr\":\"0x%08X\",\"channels\":[",
|
||||
id, s.dpcr, s.dicr);
|
||||
for (int i = 0; i < 7 && pos < sizeof(buf) - 192; i++) {
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
||||
"%s{\"ch\":%d,\"madr\":\"0x%08X\","
|
||||
"\"bcr\":\"0x%08X\",\"chcr\":\"0x%08X\","
|
||||
"\"active\":%u,\"remaining_words\":%u,"
|
||||
"\"cycles_accum\":%u}",
|
||||
i ? "," : "",
|
||||
i, s.channels[i].madr, s.channels[i].bcr,
|
||||
s.channels[i].chcr, s.channels[i].active,
|
||||
s.channels[i].remaining_words,
|
||||
s.channels[i].cycles_accum);
|
||||
}
|
||||
snprintf(buf + pos, sizeof(buf) - pos, "]}");
|
||||
debug_server_send_line(buf);
|
||||
}
|
||||
|
||||
static void handle_dma_trace_clear(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
@@ -4725,6 +4939,26 @@ static void handle_clear_input(int id, const char *json)
|
||||
send_ok(id);
|
||||
}
|
||||
|
||||
static void handle_turbo(int id, const char *json)
|
||||
{
|
||||
int enabled = json_get_int(json, "enabled", -1);
|
||||
if (enabled < 0) {
|
||||
enabled = json_get_int(json, "on", -1);
|
||||
}
|
||||
if (enabled < 0) {
|
||||
send_err(id, "missing enabled");
|
||||
return;
|
||||
}
|
||||
s_turbo_enabled = enabled ? 1 : 0;
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"enabled\":%d}", id, s_turbo_enabled);
|
||||
}
|
||||
|
||||
static void handle_turbo_state(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"enabled\":%d}", id, s_turbo_enabled);
|
||||
}
|
||||
|
||||
/* pause / continue / step / run_to_frame: REMOVED.
|
||||
*
|
||||
* Per CLAUDE.md global rule #2 ("Never time/attach for observability —
|
||||
@@ -5089,7 +5323,6 @@ static void handle_screenshot_file(int id, const char *json)
|
||||
send_err(id, "display disabled"); return;
|
||||
}
|
||||
|
||||
const uint16_t *vram = gpu_get_vram();
|
||||
uint32_t w = di.width; if (w > 640) w = 640;
|
||||
uint32_t h = di.height; if (h > 512) h = 512;
|
||||
|
||||
@@ -5125,13 +5358,9 @@ static void handle_screenshot_file(int id, const char *json)
|
||||
uint8_t *row = (uint8_t *)malloc(row_stride);
|
||||
for (uint32_t y = 0; y < h; y++) {
|
||||
memset(row, 0, row_stride);
|
||||
uint32_t vy = (di.display_y + y) & 511;
|
||||
for (uint32_t x = 0; x < w; x++) {
|
||||
uint32_t vx = (di.display_x + x) & 1023;
|
||||
uint16_t c = vram[vy * 1024 + vx];
|
||||
uint8_t r = (c & 0x1F) << 3;
|
||||
uint8_t g = ((c >> 5) & 0x1F) << 3;
|
||||
uint8_t b = ((c >> 10) & 0x1F) << 3;
|
||||
uint8_t r, g, b;
|
||||
gpu_display_pixel_rgb(&di, x, y, &r, &g, &b);
|
||||
row[x * 3 + 0] = b;
|
||||
row[x * 3 + 1] = g;
|
||||
row[x * 3 + 2] = r;
|
||||
@@ -5148,7 +5377,7 @@ static void handle_screenshot_file(int id, const char *json)
|
||||
static void handle_screenshot(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
/* Read display area from GPU and encode as hex RGB555 */
|
||||
/* Read display area from GPU and encode as display-format hex rows. */
|
||||
GpuDisplayInfo di;
|
||||
gpu_get_display_info(&di);
|
||||
|
||||
@@ -5156,26 +5385,30 @@ static void handle_screenshot(int id, const char *json)
|
||||
send_err(id, "display disabled"); return;
|
||||
}
|
||||
|
||||
const uint16_t *vram = gpu_get_vram();
|
||||
uint32_t w = di.width;
|
||||
uint32_t h = di.height;
|
||||
if (w > 640) w = 640;
|
||||
if (h > 512) h = 512;
|
||||
|
||||
/* Send metadata first */
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"width\":%u,\"height\":%u,\"format\":\"rgb555\"}",
|
||||
id, w, h);
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"width\":%u,\"height\":%u,\"format\":\"%s\"}",
|
||||
id, w, h, di.depth24 ? "rgb888" : "rgb555");
|
||||
|
||||
/* Send rows as hex lines */
|
||||
char *hex = (char *)malloc(w * 4 + 32);
|
||||
uint32_t chars_per_pixel = di.depth24 ? 6u : 4u;
|
||||
char *hex = (char *)malloc((size_t)w * chars_per_pixel + 32u);
|
||||
if (!hex) return;
|
||||
|
||||
for (uint32_t y = 0; y < h; y++) {
|
||||
uint32_t vy = (di.display_y + y) & 511;
|
||||
for (uint32_t x = 0; x < w; x++) {
|
||||
uint32_t vx = (di.display_x + x) & 1023;
|
||||
uint16_t pixel = vram[vy * 1024 + vx];
|
||||
snprintf(hex + x * 4, 5, "%04x", pixel);
|
||||
if (di.depth24) {
|
||||
uint8_t r, g, b;
|
||||
gpu_display_pixel_rgb(&di, x, y, &r, &g, &b);
|
||||
snprintf(hex + x * 6, 7, "%02x%02x%02x", r, g, b);
|
||||
} else {
|
||||
uint16_t pixel = gpu_vram_peek((int)(di.display_x + x), (int)(di.display_y + y));
|
||||
snprintf(hex + x * 4, 5, "%04x", pixel);
|
||||
}
|
||||
}
|
||||
send_fmt("{\"row\":%u,\"hex\":\"%s\"}", y, hex);
|
||||
}
|
||||
@@ -6626,6 +6859,9 @@ static void handle_fn_clear(int id, const char *json) {
|
||||
s_fn_unmatched_returns = 0;
|
||||
s_fn_stack_overflows = 0;
|
||||
s_fn_tail_calls = 0;
|
||||
s_fn_direct_seen = 0;
|
||||
s_fn_direct_no_cpu = 0;
|
||||
s_fn_direct_filtered = 0;
|
||||
if (s_fn_entry) memset(s_fn_entry, 0, (size_t)FN_TRACE_CAP * sizeof(FnEntryEntry));
|
||||
if (s_fn_exit) memset(s_fn_exit, 0, (size_t)FN_EXIT_TRACE_CAP * sizeof(FnExitEntry));
|
||||
send_ok(id);
|
||||
@@ -6638,7 +6874,9 @@ static void handle_fn_stats(int id, const char *json) {
|
||||
"\"stack_top\":%d,\"unmatched_returns\":%llu,"
|
||||
"\"stack_overflows\":%llu,\"tail_calls\":%llu,"
|
||||
"\"entry_capacity\":%llu,\"exit_capacity\":%llu,"
|
||||
"\"filter_lo\":\"0x%08X\",\"filter_hi\":\"0x%08X\"}\n",
|
||||
"\"filter_lo\":\"0x%08X\",\"filter_hi\":\"0x%08X\","
|
||||
"\"active\":%d,\"direct_seen\":%llu,"
|
||||
"\"direct_no_cpu\":%llu,\"direct_filtered\":%llu}\n",
|
||||
id,
|
||||
(unsigned long long)s_fn_entry_seq,
|
||||
(unsigned long long)s_fn_exit_seq,
|
||||
@@ -6648,7 +6886,11 @@ static void handle_fn_stats(int id, const char *json) {
|
||||
(unsigned long long)s_fn_tail_calls,
|
||||
(unsigned long long)FN_TRACE_CAP,
|
||||
(unsigned long long)FN_EXIT_TRACE_CAP,
|
||||
s_fn_trace_filter_lo, s_fn_trace_filter_hi);
|
||||
s_fn_trace_filter_lo, s_fn_trace_filter_hi,
|
||||
s_fn_trace_active,
|
||||
(unsigned long long)s_fn_direct_seen,
|
||||
(unsigned long long)s_fn_direct_no_cpu,
|
||||
(unsigned long long)s_fn_direct_filtered);
|
||||
}
|
||||
|
||||
/* Helper: parse filter / range / count for fn_*_dump.
|
||||
@@ -7190,9 +7432,14 @@ static const CmdEntry s_commands[] = {
|
||||
{ "mem_words", handle_mem_words },
|
||||
{ "vram_peek", handle_vram_peek },
|
||||
{ "irq_state", handle_irq_state },
|
||||
{ "timers_state", handle_timers_state },
|
||||
{ "cdrom_state", handle_cdrom_state },
|
||||
{ "cdrom_sector_dump", handle_cdrom_sector_dump },
|
||||
{ "cdrom_sector_history", handle_cdrom_sector_history },
|
||||
{ "cdrom_sector_history_clear", handle_cdrom_sector_history_clear },
|
||||
{ "cdrom_trace_dump", handle_cdrom_trace_dump },
|
||||
{ "cdrom_trace_clear", handle_cdrom_trace_clear },
|
||||
{ "dma_state", handle_dma_state },
|
||||
{ "dma_trace_dump", handle_dma_trace_dump },
|
||||
{ "dma_trace_clear", handle_dma_trace_clear },
|
||||
{ "sio_state", handle_sio_state },
|
||||
@@ -7290,6 +7537,8 @@ static const CmdEntry s_commands[] = {
|
||||
{ "press", handle_press },
|
||||
{ "pad_status", handle_pad_status },
|
||||
{ "clear_input", handle_clear_input },
|
||||
{ "turbo", handle_turbo },
|
||||
{ "turbo_state", handle_turbo_state },
|
||||
{ "pause", handle_pause },
|
||||
{ "continue", handle_continue },
|
||||
{ "step", handle_step },
|
||||
@@ -7440,6 +7689,8 @@ void debug_server_init(int port)
|
||||
s_wtrace_trans_head = 0;
|
||||
s_wtrace_trans_range_count = 0;
|
||||
|
||||
fntrace_arm_from_env("PSX_FNTRACE_ARM");
|
||||
|
||||
/* Function entry/exit ring buffers (32 MB each, 64 MB total). */
|
||||
if (!s_fn_entry) s_fn_entry = (FnEntryEntry *)calloc(FN_TRACE_CAP, sizeof(FnEntryEntry));
|
||||
if (!s_fn_exit) s_fn_exit = (FnExitEntry *)calloc(FN_EXIT_TRACE_CAP, sizeof(FnExitEntry));
|
||||
@@ -7448,6 +7699,15 @@ void debug_server_init(int port)
|
||||
s_fn_stack_top = 0;
|
||||
s_fn_unmatched_returns = 0;
|
||||
s_fn_stack_overflows = 0;
|
||||
s_fn_tail_calls = 0;
|
||||
s_fn_prev_ra = 0;
|
||||
s_fn_direct_seen = 0;
|
||||
s_fn_direct_no_cpu = 0;
|
||||
s_fn_direct_filtered = 0;
|
||||
s_fn_trace_active = 0;
|
||||
s_fn_trace_filter_lo = 0u;
|
||||
s_fn_trace_filter_hi = 0xFFFFFFFFu;
|
||||
fn_trace_filter_from_env("PSX_FN_FILTER");
|
||||
|
||||
if (!s_call_focus) {
|
||||
s_call_focus = (CallFocusEntry *)calloc(CALL_FOCUS_CAP,
|
||||
@@ -7526,7 +7786,17 @@ void debug_server_init(int port)
|
||||
* writer to identify the BIOS function driving this sub-state. */
|
||||
s_wtrace_ranges[14].lo = 0x00066BC0u;
|
||||
s_wtrace_ranges[14].hi = 0x00066BD0u;
|
||||
s_wtrace_range_count = 15;
|
||||
s_wtrace_ranges[15].lo = 0x00097420u; /* movie/frame handoff state */
|
||||
s_wtrace_ranges[15].hi = 0x00097430u;
|
||||
s_wtrace_range_count = 16;
|
||||
|
||||
s_wtrace_boot_ranges[0].lo = 0x00097420u; /* movie/frame handoff state */
|
||||
s_wtrace_boot_ranges[0].hi = 0x00097430u;
|
||||
s_wtrace_boot_range_count = 1;
|
||||
|
||||
s_wtrace_trans_ranges[0].lo = 0x00097420u; /* movie/frame handoff state */
|
||||
s_wtrace_trans_ranges[0].hi = 0x00097430u;
|
||||
s_wtrace_trans_range_count = 1;
|
||||
|
||||
#if DEFAULT_DEBUG_PORT == 4470
|
||||
/* Tomba STR/FMVs: movie state, CD sector descriptor ring, and CD globals.
|
||||
@@ -7591,7 +7861,9 @@ void debug_server_init(int port)
|
||||
s_wtrace_ranges[29].hi = 0x000B3800u;
|
||||
s_wtrace_ranges[30].lo = 0x000EA000u; /* BIOS licensed-screen GPU packet buffer */
|
||||
s_wtrace_ranges[30].hi = 0x000ED000u;
|
||||
s_wtrace_range_count = 31;
|
||||
s_wtrace_ranges[31].lo = 0x00097420u; /* movie/frame handoff state */
|
||||
s_wtrace_ranges[31].hi = 0x00097430u;
|
||||
s_wtrace_range_count = 32;
|
||||
|
||||
s_wtrace_boot_ranges[0].lo = 0x0009B3B0u; /* slot0 callbacks */
|
||||
s_wtrace_boot_ranges[0].hi = 0x0009B3C4u;
|
||||
@@ -7615,7 +7887,9 @@ void debug_server_init(int port)
|
||||
s_wtrace_boot_ranges[9].hi = 0x000B3800u;
|
||||
s_wtrace_boot_ranges[10].lo = 0x000EA000u; /* BIOS licensed-screen GPU packet buffer */
|
||||
s_wtrace_boot_ranges[10].hi = 0x000ED000u;
|
||||
s_wtrace_boot_range_count = 11;
|
||||
s_wtrace_boot_ranges[11].lo = 0x00097420u; /* movie/frame handoff state */
|
||||
s_wtrace_boot_ranges[11].hi = 0x00097430u;
|
||||
s_wtrace_boot_range_count = 12;
|
||||
|
||||
s_wtrace_trans_ranges[0].lo = 0x0000E1F4u; /* BIOS TCB save areas */
|
||||
s_wtrace_trans_ranges[0].hi = 0x0000E400u;
|
||||
@@ -7635,7 +7909,9 @@ void debug_server_init(int port)
|
||||
s_wtrace_trans_ranges[7].hi = 0x0009C9E0u;
|
||||
s_wtrace_trans_ranges[8].lo = 0x0009C970u; /* title/menu state variables */
|
||||
s_wtrace_trans_ranges[8].hi = 0x0009C9A0u;
|
||||
s_wtrace_trans_range_count = 9;
|
||||
s_wtrace_trans_ranges[9].lo = 0x00097420u; /* movie/frame handoff state */
|
||||
s_wtrace_trans_ranges[9].hi = 0x00097430u;
|
||||
s_wtrace_trans_range_count = 10;
|
||||
#endif
|
||||
|
||||
/* Tier 1: heap-allocate MMIO trace ring buffer (2 MB). */
|
||||
@@ -7842,3 +8118,8 @@ int debug_server_get_input_override(void)
|
||||
}
|
||||
return s_input_override;
|
||||
}
|
||||
|
||||
int debug_server_turbo_enabled(void)
|
||||
{
|
||||
return s_turbo_enabled != 0;
|
||||
}
|
||||
|
||||
+290
-34
@@ -5,7 +5,10 @@
|
||||
* - Ch2 (GPU): block mode and linked-list mode
|
||||
* - Ch6 (OTC): ordering table clear
|
||||
*
|
||||
* Transfer is executed synchronously when CHCR start bit is written.
|
||||
* Most channels execute synchronously when CHCR start bit is written. MDEC
|
||||
* request-mode transfers are advanced from the guest cycle clock so games
|
||||
* which synchronize video decode through DMA busy/request state see realistic
|
||||
* backpressure.
|
||||
* Reference: nocash PSX specs, DuckStation src/core/dma.cpp
|
||||
*/
|
||||
|
||||
@@ -40,6 +43,30 @@ typedef struct {
|
||||
|
||||
static DMAChannel channels[7];
|
||||
|
||||
typedef struct {
|
||||
uint8_t active;
|
||||
uint8_t debug_started;
|
||||
uint32_t total_words;
|
||||
uint32_t remaining_words;
|
||||
uint32_t cycles_accum;
|
||||
} DMAAsyncChannel;
|
||||
|
||||
static DMAAsyncChannel mdec_async[2];
|
||||
static DMAAsyncChannel cdrom_async;
|
||||
|
||||
#define DMA_MDEC_IN_CYCLES_PER_WORD 1u
|
||||
#define DMA_MDEC_OUT_CYCLES_PER_WORD 14u
|
||||
#define DMA_GPU_CYCLES_PER_WORD 1u
|
||||
#define DMA_CDROM_CYCLES_PER_WORD 1u
|
||||
|
||||
typedef struct {
|
||||
uint8_t active;
|
||||
uint32_t total_words;
|
||||
uint32_t cycles_remaining;
|
||||
} DMADelayedComplete;
|
||||
|
||||
static DMADelayedComplete delayed_complete[7];
|
||||
|
||||
/* ---- Global registers ---- */
|
||||
|
||||
static uint32_t dpcr; /* 0x1F8010F0: DMA control (enable bits) */
|
||||
@@ -132,6 +159,167 @@ static void complete_transfer(int ch) {
|
||||
|
||||
/* ---- Transfer execution ---- */
|
||||
|
||||
static void cancel_async_transfer(int ch) {
|
||||
if (ch >= 0 && ch < 2) {
|
||||
mdec_async[ch].active = 0;
|
||||
mdec_async[ch].debug_started = 0;
|
||||
mdec_async[ch].total_words = 0;
|
||||
mdec_async[ch].remaining_words = 0;
|
||||
mdec_async[ch].cycles_accum = 0;
|
||||
}
|
||||
if (ch == 3) {
|
||||
cdrom_async.active = 0;
|
||||
cdrom_async.debug_started = 0;
|
||||
cdrom_async.total_words = 0;
|
||||
cdrom_async.remaining_words = 0;
|
||||
cdrom_async.cycles_accum = 0;
|
||||
}
|
||||
if (ch >= 0 && ch < 7) {
|
||||
delayed_complete[ch].active = 0;
|
||||
delayed_complete[ch].total_words = 0;
|
||||
delayed_complete[ch].cycles_remaining = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void schedule_delayed_complete(int ch, uint32_t total_words,
|
||||
uint32_t cycles_per_word) {
|
||||
if (total_words == 0 || cycles_per_word == 0) {
|
||||
complete_transfer(ch);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t cycles = (uint64_t)total_words * (uint64_t)cycles_per_word;
|
||||
if (cycles > UINT32_MAX) cycles = UINT32_MAX;
|
||||
|
||||
delayed_complete[ch].active = 1;
|
||||
delayed_complete[ch].total_words = total_words;
|
||||
delayed_complete[ch].cycles_remaining = (uint32_t)cycles;
|
||||
}
|
||||
|
||||
static void advance_delayed_complete(int ch, uint32_t cycles) {
|
||||
DMADelayedComplete *d = &delayed_complete[ch];
|
||||
if (!d->active) return;
|
||||
if (cycles < d->cycles_remaining) {
|
||||
d->cycles_remaining -= cycles;
|
||||
return;
|
||||
}
|
||||
|
||||
d->active = 0;
|
||||
d->total_words = 0;
|
||||
d->cycles_remaining = 0;
|
||||
complete_transfer(ch);
|
||||
}
|
||||
|
||||
static void start_async_mdec_transfer(int ch) {
|
||||
DMAAsyncChannel *a = &mdec_async[ch];
|
||||
if (a->active) return;
|
||||
|
||||
a->active = 1;
|
||||
a->debug_started = 0;
|
||||
a->total_words = transfer_word_count(ch);
|
||||
a->remaining_words = a->total_words;
|
||||
a->cycles_accum = 0;
|
||||
|
||||
if (ch == 0) {
|
||||
mdec_debug_dma_in_start(channels[0].madr & 0x1FFFFCu, a->remaining_words);
|
||||
} else {
|
||||
mdec_debug_dma_out_start(channels[1].madr & 0x1FFFFCu, a->remaining_words);
|
||||
}
|
||||
a->debug_started = 1;
|
||||
}
|
||||
|
||||
static void finish_async_mdec_transfer(int ch, uint32_t final_addr, uint32_t total_words) {
|
||||
if (ch == 0) {
|
||||
mdec_debug_dma_in_end(final_addr, total_words);
|
||||
} else {
|
||||
mdec_debug_dma_out_end(final_addr, total_words);
|
||||
}
|
||||
cancel_async_transfer(ch);
|
||||
complete_transfer(ch);
|
||||
}
|
||||
|
||||
static void start_async_cdrom_transfer(void) {
|
||||
DMAAsyncChannel *a = &cdrom_async;
|
||||
if (a->active) return;
|
||||
|
||||
a->active = 1;
|
||||
a->debug_started = 0;
|
||||
a->total_words = transfer_word_count(3);
|
||||
a->remaining_words = a->total_words;
|
||||
a->cycles_accum = 0;
|
||||
|
||||
if (a->total_words == 0) {
|
||||
cancel_async_transfer(3);
|
||||
complete_transfer(3);
|
||||
}
|
||||
}
|
||||
|
||||
static void finish_async_cdrom_transfer(uint32_t final_addr) {
|
||||
channels[3].madr = final_addr;
|
||||
cancel_async_transfer(3);
|
||||
complete_transfer(3);
|
||||
}
|
||||
|
||||
static void advance_mdec_channel(int ch, uint32_t cycles) {
|
||||
DMAAsyncChannel *a = &mdec_async[ch];
|
||||
if (!a->active) return;
|
||||
if (!((channels[ch].chcr >> 24) & 1u) || !channel_enabled(ch)) return;
|
||||
|
||||
uint32_t chcr = channels[ch].chcr;
|
||||
uint32_t direction = chcr & 1u;
|
||||
uint32_t step = (chcr >> 1) & 1u;
|
||||
int32_t addr_step = step ? -4 : 4;
|
||||
uint32_t cycles_per_word = (ch == 0) ? DMA_MDEC_IN_CYCLES_PER_WORD : DMA_MDEC_OUT_CYCLES_PER_WORD;
|
||||
|
||||
if ((ch == 0 && direction == 0) || (ch == 1 && direction != 0)) {
|
||||
uint32_t words = a->total_words;
|
||||
uint32_t addr = channels[ch].madr & 0x1FFFFCu;
|
||||
finish_async_mdec_transfer(ch, addr, words);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ch == 0) {
|
||||
if (!mdec_dma_write_ready()) return;
|
||||
} else {
|
||||
if (!mdec_dma_read_ready()) return;
|
||||
}
|
||||
|
||||
if (cycles > UINT32_MAX - a->cycles_accum) {
|
||||
a->cycles_accum = UINT32_MAX;
|
||||
} else {
|
||||
a->cycles_accum += cycles;
|
||||
}
|
||||
|
||||
uint32_t words_budget = a->cycles_accum / cycles_per_word;
|
||||
if (words_budget == 0) return;
|
||||
|
||||
uint32_t addr = channels[ch].madr & 0x1FFFFCu;
|
||||
uint32_t moved = 0;
|
||||
while (a->remaining_words > 0 && words_budget > 0) {
|
||||
if (ch == 0) {
|
||||
if (!mdec_dma_write_ready()) break;
|
||||
mdec_dma_write_word(psx_read_word(addr));
|
||||
} else {
|
||||
if (!mdec_dma_read_ready()) break;
|
||||
psx_write_word(addr, mdec_dma_read_word());
|
||||
}
|
||||
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
a->remaining_words--;
|
||||
words_budget--;
|
||||
moved++;
|
||||
}
|
||||
|
||||
if (moved == 0) return;
|
||||
|
||||
a->cycles_accum -= moved * cycles_per_word;
|
||||
channels[ch].madr = addr;
|
||||
|
||||
if (a->remaining_words == 0) {
|
||||
finish_async_mdec_transfer(ch, addr, a->total_words);
|
||||
}
|
||||
}
|
||||
|
||||
static void execute_ch0_mdec_in(void) {
|
||||
uint32_t chcr = channels[0].chcr;
|
||||
uint32_t direction = chcr & 1; /* 1=from RAM to MDEC */
|
||||
@@ -174,11 +362,12 @@ static void execute_ch1_mdec_out(void) {
|
||||
complete_transfer(1);
|
||||
}
|
||||
|
||||
static void execute_ch2_gpu(void) {
|
||||
static uint32_t execute_ch2_gpu(void) {
|
||||
uint32_t chcr = channels[2].chcr;
|
||||
uint32_t direction = chcr & 1; /* 0=to RAM, 1=from RAM (to device) */
|
||||
uint32_t step = (chcr >> 1) & 1; /* 0=forward(+4), 1=backward(-4) */
|
||||
uint32_t sync_mode = (chcr >> 9) & 3; /* 0=burst, 1=block, 2=linked-list */
|
||||
uint32_t actual_words = 0;
|
||||
|
||||
if (direction == 0) {
|
||||
/* GPU → RAM (VRAM read): read pixel data via GPUREAD.
|
||||
@@ -194,9 +383,10 @@ static void execute_ch2_gpu(void) {
|
||||
psx_write_word(addr, pixel_data);
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
}
|
||||
channels[2].madr = addr;
|
||||
actual_words = total_words;
|
||||
}
|
||||
complete_transfer(2);
|
||||
return;
|
||||
return actual_words;
|
||||
}
|
||||
|
||||
/* direction == 1: RAM → GPU */
|
||||
@@ -215,6 +405,7 @@ static void execute_ch2_gpu(void) {
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
}
|
||||
channels[2].madr = addr;
|
||||
actual_words = total_words;
|
||||
} else if (sync_mode == 2) {
|
||||
/* Linked-list mode: follow ordering table in RAM.
|
||||
* Each node: bits 24-31 = number of words following header,
|
||||
@@ -234,6 +425,7 @@ static void execute_ch2_gpu(void) {
|
||||
uint32_t header = psx_read_word(addr);
|
||||
uint32_t num_words = (header >> 24) & 0xFF;
|
||||
uint32_t word_addr = (addr + 4) & 0x1FFFFCu;
|
||||
actual_words += 1u;
|
||||
|
||||
for (uint32_t i = 0; i < num_words; i++) {
|
||||
uint32_t word = psx_read_word(word_addr);
|
||||
@@ -241,12 +433,19 @@ static void execute_ch2_gpu(void) {
|
||||
gpu_write_gp0(word);
|
||||
word_addr = (word_addr + 4) & 0x1FFFFCu;
|
||||
}
|
||||
actual_words += num_words;
|
||||
|
||||
/* Next node */
|
||||
uint32_t next = header & 0xFFFFFFu;
|
||||
if (next == 0xFFFFFFu) break; /* end of list */
|
||||
if (next == 0xFFFFFFu) {
|
||||
channels[2].madr = 0x00FFFFFFu;
|
||||
break; /* end of list */
|
||||
}
|
||||
addr = next & 0x1FFFFCu;
|
||||
}
|
||||
if (safety > MAX_NODES) {
|
||||
channels[2].madr = addr;
|
||||
}
|
||||
} else {
|
||||
/* Burst mode (sync_mode == 0) */
|
||||
uint32_t word_count = channels[2].bcr & 0xFFFF;
|
||||
@@ -261,45 +460,22 @@ static void execute_ch2_gpu(void) {
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
}
|
||||
channels[2].madr = addr;
|
||||
actual_words = word_count;
|
||||
}
|
||||
|
||||
complete_transfer(2);
|
||||
return actual_words;
|
||||
}
|
||||
|
||||
static void execute_ch3_cdrom(void) {
|
||||
uint32_t chcr = channels[3].chcr;
|
||||
uint32_t direction = chcr & 1; /* 0=to RAM, 1=from RAM */
|
||||
uint32_t step = (chcr >> 1) & 1; /* 0=forward(+4), 1=backward(-4) */
|
||||
uint32_t sync_mode = (chcr >> 9) & 3;
|
||||
|
||||
if (direction != 0) {
|
||||
channels[3].chcr &= ~((1u << 24) | (1u << 28));
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t block_size = channels[3].bcr & 0xFFFF;
|
||||
uint32_t block_count = (channels[3].bcr >> 16) & 0xFFFF;
|
||||
uint32_t total_words;
|
||||
if (sync_mode == 1) {
|
||||
if (block_size == 0) block_size = 0x10000;
|
||||
if (block_count == 0) block_count = 1;
|
||||
total_words = block_size * block_count;
|
||||
} else {
|
||||
total_words = block_size;
|
||||
if (total_words == 0) total_words = 0x10000;
|
||||
}
|
||||
|
||||
uint32_t addr = channels[3].madr & 0x1FFFFCu;
|
||||
int32_t addr_step = step ? -4 : 4;
|
||||
for (uint32_t i = 0; i < total_words; i++) {
|
||||
uint32_t word = cdrom_dma_read();
|
||||
psx_write_word(addr, word);
|
||||
dirty_ram_mark_executable_range(addr, 4);
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
}
|
||||
|
||||
channels[3].madr = addr;
|
||||
complete_transfer(3);
|
||||
start_async_cdrom_transfer();
|
||||
}
|
||||
|
||||
static void execute_ch4_spu(void) {
|
||||
@@ -358,17 +534,19 @@ static void try_execute(int ch) {
|
||||
if (!((chcr >> 24) & 1)) return;
|
||||
if (!channel_enabled(ch)) return;
|
||||
|
||||
channels[ch].chcr &= ~(1u << 28);
|
||||
trace_dma('S', ch, transfer_word_count(ch), dicr, i_stat);
|
||||
|
||||
switch (ch) {
|
||||
case 0:
|
||||
execute_ch0_mdec_in();
|
||||
start_async_mdec_transfer(0);
|
||||
break;
|
||||
case 1:
|
||||
execute_ch1_mdec_out();
|
||||
start_async_mdec_transfer(1);
|
||||
break;
|
||||
case 2:
|
||||
execute_ch2_gpu();
|
||||
schedule_delayed_complete(2, execute_ch2_gpu(),
|
||||
DMA_GPU_CYCLES_PER_WORD);
|
||||
break;
|
||||
case 3:
|
||||
execute_ch3_cdrom();
|
||||
@@ -392,8 +570,57 @@ static void try_execute(int ch) {
|
||||
uint32_t dma_get_dicr(void) { update_master_irq(); return dicr; }
|
||||
uint32_t dma_get_dpcr(void) { return dpcr; }
|
||||
|
||||
void dma_advance(uint32_t cycles) {
|
||||
if (cycles == 0) return;
|
||||
advance_mdec_channel(0, cycles);
|
||||
advance_mdec_channel(1, cycles);
|
||||
DMAAsyncChannel *a = &cdrom_async;
|
||||
if (a->active && ((channels[3].chcr >> 24) & 1u) && channel_enabled(3)) {
|
||||
uint32_t chcr = channels[3].chcr;
|
||||
uint32_t direction = chcr & 1u;
|
||||
uint32_t step = (chcr >> 1) & 1u;
|
||||
int32_t addr_step = step ? -4 : 4;
|
||||
|
||||
if (direction != 0) {
|
||||
cancel_async_transfer(3);
|
||||
channels[3].chcr &= ~((1u << 24) | (1u << 28));
|
||||
} else if (cdrom_dma_ready()) {
|
||||
if (cycles > UINT32_MAX - a->cycles_accum) {
|
||||
a->cycles_accum = UINT32_MAX;
|
||||
} else {
|
||||
a->cycles_accum += cycles;
|
||||
}
|
||||
|
||||
uint32_t words_budget = a->cycles_accum / DMA_CDROM_CYCLES_PER_WORD;
|
||||
uint32_t addr = channels[3].madr & 0x1FFFFCu;
|
||||
uint32_t moved = 0;
|
||||
while (a->remaining_words > 0 && words_budget > 0 && cdrom_dma_ready()) {
|
||||
uint32_t word = cdrom_dma_read();
|
||||
psx_write_word(addr, word);
|
||||
dirty_ram_mark_executable_range(addr, 4);
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
a->remaining_words--;
|
||||
words_budget--;
|
||||
moved++;
|
||||
}
|
||||
|
||||
if (moved > 0) {
|
||||
a->cycles_accum -= moved * DMA_CDROM_CYCLES_PER_WORD;
|
||||
channels[3].madr = addr;
|
||||
if (a->remaining_words == 0) {
|
||||
finish_async_cdrom_transfer(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
advance_delayed_complete(2, cycles);
|
||||
}
|
||||
|
||||
void dma_init(void) {
|
||||
memset(channels, 0, sizeof(channels));
|
||||
memset(mdec_async, 0, sizeof(mdec_async));
|
||||
memset(&cdrom_async, 0, sizeof(cdrom_async));
|
||||
memset(delayed_complete, 0, sizeof(delayed_complete));
|
||||
dpcr = 0x07654321u; /* default: priorities set, no channels enabled */
|
||||
dicr = 0;
|
||||
dma_debug_clear_trace();
|
||||
@@ -419,6 +646,7 @@ uint32_t dma_read(uint32_t addr) {
|
||||
case 0x00: return channels[ch].madr;
|
||||
case 0x04: return channels[ch].bcr;
|
||||
case 0x08: return channels[ch].chcr;
|
||||
case 0x0C: return 0;
|
||||
default: goto bad;
|
||||
}
|
||||
}
|
||||
@@ -469,11 +697,16 @@ void dma_write_masked(uint32_t addr, uint32_t val, uint32_t mask) {
|
||||
return;
|
||||
case 0x08:
|
||||
channels[ch].chcr = (channels[ch].chcr & ~mask) | (val & mask);
|
||||
if ((mask & (1u << 24)) && !((channels[ch].chcr >> 24) & 1u)) {
|
||||
cancel_async_transfer(ch);
|
||||
}
|
||||
/* Writing CHCR's start bit set triggers transfer. */
|
||||
if ((mask & (1u << 24)) && ((channels[ch].chcr >> 24) & 1)) {
|
||||
try_execute(ch);
|
||||
}
|
||||
return;
|
||||
case 0x0C:
|
||||
return;
|
||||
default:
|
||||
goto bad;
|
||||
}
|
||||
@@ -498,3 +731,26 @@ void dma_debug_clear_trace(void) {
|
||||
memset(dma_trace, 0, sizeof(dma_trace));
|
||||
dma_trace_seq = 0;
|
||||
}
|
||||
|
||||
void dma_debug_get_state(DMADebugState* out) {
|
||||
if (!out) return;
|
||||
out->dpcr = dpcr;
|
||||
out->dicr = dma_get_dicr();
|
||||
for (int i = 0; i < 7; i++) {
|
||||
out->channels[i].madr = channels[i].madr;
|
||||
out->channels[i].bcr = channels[i].bcr;
|
||||
out->channels[i].chcr = channels[i].chcr;
|
||||
out->channels[i].active =
|
||||
((i < 2) ? mdec_async[i].active : 0) ||
|
||||
((i == 3) ? cdrom_async.active : 0) ||
|
||||
delayed_complete[i].active;
|
||||
out->channels[i].remaining_words =
|
||||
(i < 2 && mdec_async[i].active) ? mdec_async[i].remaining_words :
|
||||
(i == 3 && cdrom_async.active) ? cdrom_async.remaining_words :
|
||||
delayed_complete[i].total_words;
|
||||
out->channels[i].cycles_accum =
|
||||
(i < 2 && mdec_async[i].active) ? mdec_async[i].cycles_accum :
|
||||
(i == 3 && cdrom_async.active) ? cdrom_async.cycles_accum :
|
||||
delayed_complete[i].cycles_remaining;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "fntrace.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
FntraceEntry g_fntrace_ring[FNTRACE_RING_CAP];
|
||||
uint64_t g_fntrace_seq = 0;
|
||||
@@ -87,6 +88,43 @@ uint32_t fntrace_arm_get(uint32_t i) {
|
||||
return (i < s_arm_count) ? s_arm_targets[i] : 0;
|
||||
}
|
||||
|
||||
void fntrace_arm_from_env(const char *env_name) {
|
||||
const char *spec = getenv(env_name);
|
||||
if (!spec || !*spec) return;
|
||||
|
||||
const char *p = spec;
|
||||
while (*p) {
|
||||
while (*p == ',' || *p == ';' || *p == ' ' || *p == '\t' ||
|
||||
*p == '\r' || *p == '\n') {
|
||||
p++;
|
||||
}
|
||||
if (!*p) break;
|
||||
|
||||
if ((p[0] == 'a' || p[0] == 'A') &&
|
||||
(p[1] == 'l' || p[1] == 'L') &&
|
||||
(p[2] == 'l' || p[2] == 'L') &&
|
||||
(p[3] == '\0' || p[3] == ',' || p[3] == ';' ||
|
||||
p[3] == ' ' || p[3] == '\t' || p[3] == '\r' || p[3] == '\n')) {
|
||||
fntrace_arm(0xFFFFFFFFu);
|
||||
p += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
char *end = NULL;
|
||||
unsigned long value = strtoul(p, &end, 0);
|
||||
if (end == p) {
|
||||
while (*p && *p != ',' && *p != ';' && *p != ' ' && *p != '\t' &&
|
||||
*p != '\r' && *p != '\n') {
|
||||
p++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
fntrace_arm((uint32_t)value);
|
||||
p = end;
|
||||
}
|
||||
}
|
||||
|
||||
void fntrace_clear(void) {
|
||||
g_fntrace_seq = 0;
|
||||
/* Storage left in place; consumer dumps relative to seq. */
|
||||
|
||||
@@ -351,9 +351,43 @@ const uint16_t* gpu_get_vram(void) {
|
||||
return vram;
|
||||
}
|
||||
|
||||
static uint8_t gpu_vram_byte(uint32_t byte_x, uint32_t y) {
|
||||
uint16_t hw = vram[((y & 511u) * 1024u) + ((byte_x >> 1) & 1023u)];
|
||||
return (byte_x & 1u) ? (uint8_t)(hw >> 8) : (uint8_t)hw;
|
||||
}
|
||||
|
||||
static void gpu_rgb555_to_rgb888(uint16_t c, uint8_t* r, uint8_t* g, uint8_t* b) {
|
||||
*r = (uint8_t)((c & 0x1Fu) << 3);
|
||||
*g = (uint8_t)(((c >> 5) & 0x1Fu) << 3);
|
||||
*b = (uint8_t)(((c >> 10) & 0x1Fu) << 3);
|
||||
}
|
||||
|
||||
void gpu_display_pixel_rgb(const GpuDisplayInfo* di, uint32_t x, uint32_t y,
|
||||
uint8_t* r, uint8_t* g, uint8_t* b) {
|
||||
if (di->depth24) {
|
||||
uint32_t byte_x = ((di->display_x & 1023u) * 2u) + x * 3u;
|
||||
uint32_t vy = (di->display_y + y) & 511u;
|
||||
*r = gpu_vram_byte(byte_x + 0u, vy);
|
||||
*g = gpu_vram_byte(byte_x + 1u, vy);
|
||||
*b = gpu_vram_byte(byte_x + 2u, vy);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t vx = (di->display_x + x) & 1023u;
|
||||
uint32_t vy = (di->display_y + y) & 511u;
|
||||
gpu_rgb555_to_rgb888(vram[vy * 1024u + vx], r, g, b);
|
||||
}
|
||||
|
||||
uint32_t gpu_display_pixel_argb(const GpuDisplayInfo* di, uint32_t x, uint32_t y) {
|
||||
uint8_t r, g, b;
|
||||
gpu_display_pixel_rgb(di, x, y, &r, &g, &b);
|
||||
return 0xFF000000u | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b;
|
||||
}
|
||||
|
||||
void gpu_get_display_info(GpuDisplayInfo* out) {
|
||||
out->display_x = display_area_x;
|
||||
out->display_y = display_area_y;
|
||||
out->depth24 = (int)(display_depth & 1u);
|
||||
out->disabled = (int)display_disabled;
|
||||
|
||||
/* Derive pixel width from horizontal display range and resolution mode.
|
||||
|
||||
+16
-15
@@ -496,14 +496,6 @@ static void sdl_audio_pump(void) {
|
||||
(uint32_t)frames * bytes_per_frame);
|
||||
}
|
||||
|
||||
/* Convert PS1 16-bit color (1-5-5-5) to ARGB8888 */
|
||||
static inline uint32_t psx16_to_argb(uint16_t c) {
|
||||
uint32_t r = (c & 0x1F) << 3;
|
||||
uint32_t g = ((c >> 5) & 0x1F) << 3;
|
||||
uint32_t b = ((c >> 10) & 0x1F) << 3;
|
||||
return 0xFF000000u | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
/* PS1 digital pad button bits (active-low: 0=pressed, 1=released).
|
||||
* Bit 0 = SELECT, Bit 3 = START, Bit 4 = UP, Bit 5 = RIGHT,
|
||||
* Bit 6 = DOWN, Bit 7 = LEFT, Bit 8 = L2, Bit 9 = R2,
|
||||
@@ -948,6 +940,13 @@ static void sdl_vblank_present(void) {
|
||||
sdl_audio_pump();
|
||||
#endif
|
||||
|
||||
/* TCP turbo is for automated validation and trace capture. It keeps the
|
||||
* simulation advancing and the debug server polling, but removes frontend
|
||||
* presentation and wall-clock pacing. */
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
if (debug_server_turbo_enabled()) return;
|
||||
#endif
|
||||
|
||||
/* Turbo mode: while TAB is held, skip both VRAM->ARGB conversion and
|
||||
* SDL_RenderPresent. The recompiled BIOS still advances simulated
|
||||
* cycles every vblank, so the BIOS proceeds at whatever rate the host
|
||||
@@ -1004,23 +1003,25 @@ static void sdl_vblank_present(void) {
|
||||
/* ---- Display from our VRAM ---- */
|
||||
uint32_t w = 0, h = 0;
|
||||
{
|
||||
static bool disabled_frame_presented = false;
|
||||
GpuDisplayInfo di;
|
||||
gpu_get_display_info(&di);
|
||||
if (di.disabled || di.width == 0 || di.height == 0) {
|
||||
#ifndef PSX_SDL_NO_RENDER
|
||||
SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(sdl_renderer);
|
||||
SDL_RenderPresent(sdl_renderer);
|
||||
if (!disabled_frame_presented) {
|
||||
disabled_frame_presented = true;
|
||||
SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(sdl_renderer);
|
||||
SDL_RenderPresent(sdl_renderer);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
const uint16_t* vram = gpu_get_vram();
|
||||
disabled_frame_presented = false;
|
||||
w = di.width; h = di.height;
|
||||
for (uint32_t y = 0; y < h; y++) {
|
||||
uint32_t vy = (di.display_y + y) & 511;
|
||||
for (uint32_t x = 0; x < w; x++) {
|
||||
uint32_t vx = (di.display_x + x) & 1023;
|
||||
sdl_pixel_buf[y * w + x] = psx16_to_argb(vram[vy * 1024 + vx]);
|
||||
sdl_pixel_buf[y * w + x] = gpu_display_pixel_argb(&di, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-13
@@ -161,10 +161,35 @@ static void finish_command(void) {
|
||||
mdec.expected_halfwords = 0;
|
||||
mdec.input_count = 0;
|
||||
mdec.busy = 0;
|
||||
mdec.input_full = 1;
|
||||
mdec.input_full = 0;
|
||||
trace_event(MDEC_EVT_CMD_DONE, mdec.command);
|
||||
}
|
||||
|
||||
static void soft_reset(void) {
|
||||
uint16_t *input = mdec.input;
|
||||
uint32_t input_cap = mdec.input_cap;
|
||||
uint8_t *output = mdec.output;
|
||||
uint32_t output_cap = mdec.output_cap;
|
||||
uint8_t y_quant[64];
|
||||
uint8_t uv_quant[64];
|
||||
int16_t scale[64];
|
||||
|
||||
memcpy(y_quant, mdec.y_quant, sizeof(y_quant));
|
||||
memcpy(uv_quant, mdec.uv_quant, sizeof(uv_quant));
|
||||
memcpy(scale, mdec.scale, sizeof(scale));
|
||||
|
||||
memset(&mdec, 0, sizeof(mdec));
|
||||
mdec.input = input;
|
||||
mdec.input_cap = input_cap;
|
||||
mdec.output = output;
|
||||
mdec.output_cap = output_cap;
|
||||
memcpy(mdec.y_quant, y_quant, sizeof(mdec.y_quant));
|
||||
memcpy(mdec.uv_quant, uv_quant, sizeof(mdec.uv_quant));
|
||||
memcpy(mdec.scale, scale, sizeof(mdec.scale));
|
||||
mdec.output_depth = 3;
|
||||
mdec.current_block = 4;
|
||||
}
|
||||
|
||||
static void idct_block(int16_t *block) {
|
||||
int16_t tmp[64];
|
||||
int16_t *src = block;
|
||||
@@ -414,10 +439,11 @@ uint32_t mdec_read(uint32_t addr) {
|
||||
status |= ((uint32_t)mdec.output_bit15 & 1u) << 23;
|
||||
status |= ((uint32_t)mdec.output_signed & 1u) << 24;
|
||||
status |= ((uint32_t)mdec.output_depth & 3u) << 25;
|
||||
if (mdec.enable_dma_out && mdec.output_pos < mdec.output_size) status |= 1u << 27;
|
||||
if (mdec.enable_dma_in && mdec.busy && mdec.input_count < mdec.expected_halfwords) status |= 1u << 28;
|
||||
int write_ready = mdec_dma_write_ready();
|
||||
if (!write_ready) status |= 1u << 30;
|
||||
if (mdec.enable_dma_out && mdec_dma_read_ready()) status |= 1u << 27;
|
||||
if (mdec.enable_dma_in && write_ready) status |= 1u << 28;
|
||||
if (mdec.busy) status |= 1u << 29;
|
||||
if (mdec.input_full) status |= 1u << 30;
|
||||
if (mdec.output_pos >= mdec.output_size) status |= 1u << 31;
|
||||
mdec.last_status = status;
|
||||
return status;
|
||||
@@ -431,15 +457,7 @@ void mdec_write(uint32_t addr, uint32_t value) {
|
||||
}
|
||||
|
||||
if (value & 0x80000000u) {
|
||||
free(mdec.input);
|
||||
free(mdec.output);
|
||||
memset(&mdec, 0, sizeof(mdec));
|
||||
mdec.output_depth = 3;
|
||||
mdec.current_block = 4;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
mdec.y_quant[i] = 1;
|
||||
mdec.uv_quant[i] = 1;
|
||||
}
|
||||
soft_reset();
|
||||
trace_event(MDEC_EVT_RESET, value);
|
||||
}
|
||||
mdec.enable_dma_in = (uint8_t)((value >> 30) & 1u);
|
||||
@@ -474,6 +492,7 @@ uint32_t mdec_dma_read_word(void) {
|
||||
}
|
||||
|
||||
int mdec_dma_write_ready(void) {
|
||||
if (mdec.output_pos < mdec.output_size) return 0;
|
||||
return !mdec.busy || mdec.input_count < mdec.expected_halfwords;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "psx_cycles.h"
|
||||
#include "cdrom.h"
|
||||
#include "dma.h"
|
||||
#include "interrupts.h"
|
||||
#include "sio.h"
|
||||
#include "starvation_ring.h"
|
||||
@@ -20,6 +21,7 @@ void psx_advance_cycles(uint32_t cycles) {
|
||||
psx_cycle_count += (uint64_t)cycles;
|
||||
sio_advance(cycles);
|
||||
cdrom_advance(cycles);
|
||||
dma_advance(cycles);
|
||||
timers_advance(cycles);
|
||||
interrupts_advance_cycles(cycles);
|
||||
s_watchdog_throttle += cycles;
|
||||
|
||||
+3
-3
@@ -208,9 +208,9 @@ static void psx_thread_fiber_entry(void* param)
|
||||
|
||||
debug_server_log_thread_event(11, cpu, psx_current_tcb_ptr(cpu), slot->tcb, target_pc);
|
||||
slot->closed = 1;
|
||||
if (psx_tcb_state(cpu, slot->tcb) == 0x4000u) {
|
||||
cpu->write_word(slot->tcb, 0x1000u);
|
||||
}
|
||||
/* Guest BIOS code owns the TCB state. A host fiber can finish because a
|
||||
* thread returned through the BIOS scheduler path, but changing the BIOS
|
||||
* TCB here races with games that keep that thread runnable. */
|
||||
|
||||
if (slot->return_fiber) {
|
||||
if (psx_is_valid_tcb(cpu, slot->return_tcb)) {
|
||||
|
||||
Reference in New Issue
Block a user