mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Add guarded single-sector resident CD FIFO delivery
This commit is contained in:
@@ -386,3 +386,38 @@ rejected on paper.
|
||||
`spike/load-shards` (TombaRecomp). Recreate worktrees before E4.
|
||||
- `cdrom_bursts` ring (`cdrom.c:311-360`) already measures load
|
||||
durations — E0's window boundary source.
|
||||
|
||||
|
||||
## Incremental resident sectors
|
||||
|
||||
`psx_mod_resident_cd_sector(lba, user_data)` admits exactly one verified data
|
||||
sector through the actual controller FIFO and IRQ path. It keeps ReadN active,
|
||||
advances the drive cursor by one sector, and restores ordinary timing for the
|
||||
next sector. The original guest Ready callback, header/payload DMA, resource
|
||||
consumers and final Pause acknowledgement still run. It never writes guest
|
||||
RAM, calls a guest callback or retains a host pointer/transfer cursor.
|
||||
|
||||
The title must prove its stock file/span, original code and live callback,
|
||||
and that the next destination is free of pending consumers. A zero ring
|
||||
occupancy byte alone is insufficient when an SDK queue retains its source
|
||||
pointer. Check both the software GPU queue and active GPU/SPU DMA before each
|
||||
admission. The shared helper checks CD ownership only; it does not know the
|
||||
title's upload queues or grant a whole-file transfer.
|
||||
|
||||
Admission requires the original ReadN ACK and response to be fully consumed,
|
||||
mode 0x80 or 0xA0, the exact next drive LBA, and no pending command, IRQ, CD DMA
|
||||
or unread payload. Mode 0xA0 may retain only the already-consumed sector's ECC
|
||||
tail. The helper witnesses the real raw sector's mode, submode, duplicate
|
||||
subheader and BCD address and compares every user byte with resident data.
|
||||
XA, FMV, Form2, realtime, changed bytes and busy ownership decline without
|
||||
controller mutation. This first version still reads raw sector metadata from
|
||||
the mounted image for verification; it does not claim removal of all host I/O.
|
||||
|
||||
No extra snapshot fields are needed: every accepted effect lives in the
|
||||
ordinary controller buffers, cursor, status and IRQ state. The real-controller
|
||||
`cdrom_resident_sector_test` covers both modes, repeated one-sector admission,
|
||||
24 current-format snapshot restores, subsequent original-stream delivery and
|
||||
real Pause completion, plus 33 byte-for-byte atomic decline cases. Existing
|
||||
FIFO and IRQ-mask tests also pass. These tests qualify the helper's controller
|
||||
contract, not a title adapter or load-time improvement. Tron consumer integration
|
||||
and measured runtime outcomes remain pending under beads-eio.3.412/eio.27.3.
|
||||
|
||||
@@ -1624,6 +1624,10 @@ if(BUILD_TESTING)
|
||||
target_include_directories(cdrom_sector_fifo_test PRIVATE include src)
|
||||
add_test(NAME cdrom_sector_fifo_test COMMAND cdrom_sector_fifo_test)
|
||||
|
||||
add_executable(cdrom_resident_sector_test tests/test_cdrom_resident_sector.c)
|
||||
target_include_directories(cdrom_resident_sector_test PRIVATE include src)
|
||||
add_test(NAME cdrom_resident_sector_test COMMAND cdrom_resident_sector_test)
|
||||
|
||||
add_executable(cdrom_lid_state_test tests/test_cdrom_lid_state.c)
|
||||
target_include_directories(cdrom_lid_state_test PRIVATE src)
|
||||
add_test(NAME cdrom_lid_state_test COMMAND cdrom_lid_state_test)
|
||||
|
||||
@@ -33,6 +33,12 @@ void cdrom_notify_game_started(void);
|
||||
* ring buffer to correlate each sector transfer with its disc position. */
|
||||
int cdrom_get_setloc_lba(void);
|
||||
|
||||
/* Admit one verified resident data sector into the real ReadN FIFO/IRQ path.
|
||||
* Requires an ACKed stream at lba, no pending controller/DMA/payload owner,
|
||||
* and matching pure-data raw bytes. Never stops the stream or calls a guest
|
||||
* callback. The title must separately prove its destination/upload ownership. */
|
||||
int cdrom_resident_feed_sector(uint32_t lba, const uint8_t* user_data);
|
||||
|
||||
/* 'instant' per-frame sector-IRQ budget (step 3). Clamped to [1, 4096];
|
||||
* the per-sector period additionally floors at CDROM_MIN_DELAY. Writers:
|
||||
* game.toml [runtime] instant_max_per_frame, the cdrom_instant_rate TCP
|
||||
|
||||
@@ -48,6 +48,18 @@ typedef struct PSXResidentFile {
|
||||
* the original file; NULL = not catalogued */
|
||||
} PSXResidentFile;
|
||||
|
||||
/* Deliver exactly one stock data sector through the actual controller FIFO,
|
||||
* IRQ and original guest Ready/DMA path. The ReadN stream remains active and
|
||||
* its next sector keeps ordinary timing. Busy/changed/media sectors decline
|
||||
* without mutation. No host pointer or transfer cursor is retained; ordinary
|
||||
* controller snapshots contain every accepted effect.
|
||||
*
|
||||
* Trusted title code must prove the file/span/code/callback and a free next
|
||||
* destination, including queued GPU source pointers and active GPU/SPU DMA.
|
||||
* The controller checks CD ownership only. Do not use for XA/FMV or to bypass
|
||||
* original consumers, publish completion, or replace a final Pause ack. */
|
||||
int psx_mod_resident_cd_sector(uint32_t lba, const void* user_data);
|
||||
|
||||
typedef struct PSXResidentSink PSXResidentSink;
|
||||
|
||||
/* Store one derived blob (decoded asset, table, ...) for source file `file`.
|
||||
|
||||
+72
-4
@@ -1434,7 +1434,8 @@ static int maybe_deliver_xa_audio(const uint8_t* raw_data, int lba,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int read_sector_at(int min, int sec, int sect) {
|
||||
static int read_sector_at_with_raw(int min, int sec, int sect,
|
||||
const uint8_t* supplied_raw) {
|
||||
int lba = msf_to_lba(min, sec, sect);
|
||||
uint8_t user_data[SECTOR_SIZE];
|
||||
uint8_t raw_data[RAW_SECTOR_SIZE];
|
||||
@@ -1443,7 +1444,12 @@ static int read_sector_at(int min, int sec, int sect) {
|
||||
const uint8_t *history_bytes = user_data;
|
||||
int history_size = SECTOR_SIZE;
|
||||
|
||||
if (iso_handle) {
|
||||
if (supplied_raw) {
|
||||
memcpy(raw_data, supplied_raw, sizeof raw_data);
|
||||
memcpy(user_data, raw_data + RAW_USER_DATA_OFFSET, sizeof user_data);
|
||||
have_raw = 1;
|
||||
if (subq_replacements_active) update_last_valid_subq((uint32_t)lba);
|
||||
} else if (iso_handle) {
|
||||
have_raw = iso_read_raw_sector(iso_handle, lba, raw_data, RAW_SECTOR_SIZE);
|
||||
if (have_raw) {
|
||||
memcpy(user_data, raw_data + RAW_USER_DATA_OFFSET, SECTOR_SIZE);
|
||||
@@ -1540,6 +1546,10 @@ static int read_sector_at(int min, int sec, int sect) {
|
||||
return delivery.data_delivered ? 1 : 0;
|
||||
}
|
||||
|
||||
static int read_sector_at(int min, int sec, int sect) {
|
||||
return read_sector_at_with_raw(min, sec, sect, NULL);
|
||||
}
|
||||
|
||||
static void advance_msf(int* m, int* s, int* f) {
|
||||
(*f)++;
|
||||
if (*f >= 75) { *f = 0; (*s)++; }
|
||||
@@ -1877,8 +1887,9 @@ static int data_fifo_ready(void) {
|
||||
static uint64_t s_dataready_fires; /* INT1 (data-ready) raised per streamed sector — FMV dispatch probe */
|
||||
uint64_t cdrom_get_dataready_fires(void) { return s_dataready_fires; }
|
||||
|
||||
static int deliver_read_sector(void) {
|
||||
int delivered = read_sector_at(read_min, read_sec, read_sect);
|
||||
static int deliver_read_sector_with_raw(const uint8_t* supplied_raw) {
|
||||
int delivered = read_sector_at_with_raw(read_min, read_sec, read_sect,
|
||||
supplied_raw);
|
||||
advance_msf(&read_min, &read_sec, &read_sect);
|
||||
if (xa_data_end_pending) {
|
||||
xa_data_end_pending = 0;
|
||||
@@ -1900,6 +1911,10 @@ static int deliver_read_sector(void) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int deliver_read_sector(void) {
|
||||
return deliver_read_sector_with_raw(NULL);
|
||||
}
|
||||
|
||||
/* A continuing DMA needs the newly filled slot immediately. A sector whose
|
||||
* INT1 is merely pending must leave the previously announced slot readable:
|
||||
* Final Bout drains that slot after acknowledging its IRQ. Redirecting it
|
||||
@@ -3271,6 +3286,59 @@ int cdrom_data_read_active(void) {
|
||||
return reading && !xa_stream_active;
|
||||
}
|
||||
|
||||
int cdrom_resident_feed_sector(uint32_t lba, const uint8_t* user_data) {
|
||||
uint8_t raw[RAW_SECTOR_SIZE];
|
||||
uint64_t timing_seq;
|
||||
int min, sec, frame;
|
||||
/* The title owns the verified file, callback and destination consumer.
|
||||
* This helper owns one real controller delivery, never guest RAM or a
|
||||
* host-side continuation. A declined call leaves the controller intact. */
|
||||
if (!user_data || !has_disc() || lba >= iso_sector_count(iso_handle) ||
|
||||
lba > 0x7FFFFF00u || !reading || read_cmd != 0x06 ||
|
||||
(mode_reg != 0x80u && mode_reg != 0xA0u) ||
|
||||
xa_stream_active || xa_data_end_pending || cdda_playing ||
|
||||
cdda_data_end_pending || setloc_pending ||
|
||||
msf_to_lba(read_min, read_sec, read_sect) != (int)lba ||
|
||||
irq_flag || pending.pending || queued_cmd.pending ||
|
||||
pending_dataready || pending_present_due || param_count ||
|
||||
response_read < response_count || dma_cdrom_transfer_active())
|
||||
return 0;
|
||||
/* PsyQ reads 12 header bytes and 2048 user bytes, retaining 280 ECC
|
||||
* bytes in whole-sector mode. That tail is not another owner's payload.
|
||||
* A partial header/payload or any other unread FIFO must not be replaced. */
|
||||
if (rb_available() &&
|
||||
!(mode_reg == 0xA0u && RB_.size == WHOLE_SECTOR_SIZE &&
|
||||
RB_.pos == 12 + SECTOR_SIZE && last_sector_mode == mode_reg &&
|
||||
last_sector_have_raw && last_sector_lba >= 0 &&
|
||||
(uint32_t)last_sector_lba + 1u == lba))
|
||||
return 0;
|
||||
if (!iso_read_raw_sector(iso_handle, lba, raw, sizeof raw) ||
|
||||
raw[15] != CDROM_SECTOR_MODE2 ||
|
||||
memcmp(raw + 16, raw + 20, 4) || !(raw[18] & 0x08u) ||
|
||||
(raw[18] & 0x66u) ||
|
||||
memcmp(raw + RAW_USER_DATA_OFFSET, user_data, SECTOR_SIZE))
|
||||
return 0;
|
||||
lba_to_msf((int)lba, 150, &min, &sec, &frame);
|
||||
if (raw[12] != bin_to_bcd(min) || raw[13] != bin_to_bcd(sec) ||
|
||||
raw[14] != bin_to_bcd(frame))
|
||||
return 0;
|
||||
|
||||
/* Keep the stock header/subheader/ECC and supply the verified resident
|
||||
* payload. The ordinary FIFO, status response, IRQ presentation and DMA
|
||||
* paths handle the sector; the guest still executes its original Ready.
|
||||
* There is no saved host pointer or multi-sector grant to invalidate. */
|
||||
memcpy(raw + RAW_USER_DATA_OFFSET, user_data, SECTOR_SIZE);
|
||||
timing_seq = cd_timing_begin_sector((int)lba);
|
||||
(void)deliver_read_sector_with_raw(raw);
|
||||
cd_timing_flag(timing_seq, CDT_DATA);
|
||||
cd_timing_arm_irq(timing_seq);
|
||||
s_accel_block_accum = 0;
|
||||
read_delay = sector_delay_cycles();
|
||||
s_cd_timing_next_due = psx_cycle_count + (uint64_t)read_delay;
|
||||
trace_cdrom('H', lba, 1, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Savestate post-load: authentic CD second-response delays (ReadTOC ~30M
|
||||
* cycles, Init ~1.1M, far seeks, etc.) leave the restored frame on screen
|
||||
* for up to ~1s+ of wall time. Clamp those timers so the next cdrom_advance
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* the header for the contract. */
|
||||
#include "mod_plugins.h"
|
||||
#include "cpu_state.h"
|
||||
#include "cdrom.h"
|
||||
#include "dma.h"
|
||||
#include "gpu.h"
|
||||
#include "interrupts.h"
|
||||
@@ -15,6 +16,11 @@
|
||||
* guest-mutating service refuses then (docs/NETPLAY.md). */
|
||||
int psx_mod_host_output_scope(void);
|
||||
|
||||
int psx_mod_resident_cd_sector(uint32_t lba, const void* user_data) {
|
||||
if (psx_mod_host_output_scope()) return 0;
|
||||
return cdrom_resident_feed_sector(lba, (const uint8_t*)user_data);
|
||||
}
|
||||
|
||||
uint32_t psx_mod_call_guest(struct CPUState* cpu, uint32_t function,
|
||||
uint32_t return_address, uint32_t a0, uint32_t a1,
|
||||
uint32_t a2, uint32_t a3) {
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/* Actual controller integration: original FIFO/IRQ/DMA/snapshots, no guest
|
||||
* callback or controller-state stubs. The backing disc alone is synthetic. */
|
||||
#define main fifo_baseline_main
|
||||
#include "test_cdrom_sector_fifo.c"
|
||||
#undef main
|
||||
|
||||
static uint8_t resident_data[SECTOR_SIZE];
|
||||
|
||||
static void setup_resident(uint8_t mode) {
|
||||
setup();
|
||||
mode_reg = mode;
|
||||
reading = 0;
|
||||
exec_command(0x06);
|
||||
CHECK(reading && irq_flag == CDIRQ_ACK);
|
||||
CHECK(cdrom_read(0x1F801801) == stat_reg);
|
||||
ack();
|
||||
memset(resident_data, 100, sizeof resident_data);
|
||||
}
|
||||
|
||||
static void consume_resident(unsigned lba, uint8_t mode) {
|
||||
int m, s, f;
|
||||
CHECK(irq_flag == CDIRQ_DATA_READY);
|
||||
CHECK(cdrom_read(0x1F801801) == stat_reg);
|
||||
ack(); request();
|
||||
if (mode == 0xA0) {
|
||||
lba_to_msf((int)lba, 150, &m, &s, &f);
|
||||
CHECK(cdrom_dma_read() == (uint32_t)(bin_to_bcd(m) |
|
||||
bin_to_bcd(s) << 8 | bin_to_bcd(f) << 16 | 2u << 24));
|
||||
CHECK(cdrom_dma_read() == 0x00080000u);
|
||||
CHECK(cdrom_dma_read() == 0x00080000u);
|
||||
}
|
||||
drain(lba, 512);
|
||||
}
|
||||
|
||||
static void original_continuation_and_pause(void) {
|
||||
for (unsigned k = 0; k < 2; ++k) {
|
||||
uint8_t mode = k ? 0xA0 : 0x80;
|
||||
setup_resident(mode);
|
||||
const uint64_t fires = s_dataready_fires;
|
||||
CHECK(cdrom_resident_feed_sector(100, resident_data));
|
||||
CHECK(reading && read_cmd == 0x06 && last_sector_lba == 100);
|
||||
CHECK(msf_to_lba(read_min, read_sec, read_sect) == 101);
|
||||
CHECK(s_dataready_fires == fires + 1);
|
||||
CHECK(read_delay == sector_delay_cycles());
|
||||
CHECK(!cdrom_resident_feed_sector(101, resident_data));
|
||||
consume_resident(100, mode);
|
||||
/* No host transfer state is required to continue through real timing. */
|
||||
sector();
|
||||
consume_resident(101, mode);
|
||||
CHECK(msf_to_lba(read_min, read_sec, read_sect) == 102);
|
||||
exec_command(0x09);
|
||||
CHECK(!reading && irq_flag == CDIRQ_ACK && pending.pending);
|
||||
(void)cdrom_read(0x1F801801); ack();
|
||||
psx_cycle_count += 10000000;
|
||||
process_pending(0);
|
||||
CHECK(irq_flag == CDIRQ_COMPLETE && !pending.pending);
|
||||
}
|
||||
}
|
||||
|
||||
static void repeated_one_sector_and_snapshot(void) {
|
||||
for (unsigned k = 0; k < 2; ++k) {
|
||||
uint8_t mode = k ? 0xA0 : 0x80;
|
||||
setup_resident(mode);
|
||||
for (unsigned lba = 100; lba < 112; ++lba) {
|
||||
memset(resident_data, lba, sizeof resident_data);
|
||||
CHECK(cdrom_resident_feed_sector(lba, resident_data));
|
||||
const uint32_t size = cdrom_snapshot_bytes();
|
||||
uint8_t* wire = malloc(size); CHECK(wire);
|
||||
cdrom_snapshot_write(wire);
|
||||
consume_resident(lba, mode);
|
||||
CHECK(cdrom_snapshot_read(wire, size));
|
||||
consume_resident(lba, mode);
|
||||
free(wire);
|
||||
CHECK(reading && msf_to_lba(read_min, read_sec, read_sect) == (int)lba + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void decline_without_mutation(void) {
|
||||
for (unsigned n = 0; n < 33; ++n) {
|
||||
setup_resident(0xA0);
|
||||
uint32_t lba = 100;
|
||||
const uint8_t* data = resident_data;
|
||||
switch (n) {
|
||||
case 0: data = NULL; break;
|
||||
case 1: lba = 99; break;
|
||||
case 2: reading = 0; break;
|
||||
case 3: read_cmd = 0x1B; break;
|
||||
case 4: mode_reg = 0xE0; break;
|
||||
case 5: xa_stream_active = 1; break;
|
||||
case 6: cdda_playing = 1; break;
|
||||
case 7: setloc_pending = 1; break;
|
||||
case 8: irq_flag = CDIRQ_ACK; break;
|
||||
case 9: pending.pending = 1; break;
|
||||
case 10: queued_cmd.pending = 1; break;
|
||||
case 11: pending_dataready = 1; break;
|
||||
case 12: pending_present_due = psx_cycle_count + 10; break;
|
||||
case 13: param_count = 1; break;
|
||||
case 14: response_read = 0; response_count = 1; break;
|
||||
case 15: test_dma_active = 1; break;
|
||||
case 16: RB_.size = SECTOR_SIZE; RB_.pos = 4; break;
|
||||
case 17: test_raw_fail = 1; break;
|
||||
case 18: resident_data[42] ^= 1; break;
|
||||
case 19: test_raw_bad_header = 1; break;
|
||||
case 20: test_raw_bad_copy = 1; break;
|
||||
case 21: test_raw_mode = 1; break;
|
||||
case 22: test_raw_submode = 0x0A; break; /* video */
|
||||
case 23: test_raw_submode = 0x0C; break; /* audio */
|
||||
case 24: test_raw_submode = 0x28; break; /* Form2 */
|
||||
case 25: test_raw_submode = 0x48; break; /* realtime */
|
||||
case 26: test_raw_submode = 0; break;
|
||||
case 27: lba = 1000; read_sec = 15; read_sect = 25; break;
|
||||
case 28: mode_reg = 0; break;
|
||||
case 29:
|
||||
case 30:
|
||||
RB_.size = WHOLE_SECTOR_SIZE; RB_.pos = 12 + SECTOR_SIZE;
|
||||
last_sector_mode = mode_reg; last_sector_have_raw = 1;
|
||||
last_sector_lba = n == 29 ? 98 : 99;
|
||||
if (n == 30) --RB_.pos;
|
||||
break;
|
||||
case 31: xa_data_end_pending = 1; break;
|
||||
case 32: cdda_data_end_pending = 1; break;
|
||||
}
|
||||
const uint32_t size = cdrom_snapshot_bytes();
|
||||
uint8_t *before = malloc(size), *after = malloc(size);
|
||||
CHECK(before && after);
|
||||
cdrom_snapshot_write(before);
|
||||
CHECK(!cdrom_resident_feed_sector(lba, data));
|
||||
cdrom_snapshot_write(after);
|
||||
CHECK(!memcmp(before, after, size));
|
||||
free(before); free(after);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
CHECK(fifo_baseline_main() == 0);
|
||||
original_continuation_and_pause();
|
||||
repeated_one_sector_and_snapshot();
|
||||
decline_without_mutation();
|
||||
puts("PASS: resident single-sector FIFO/IRQ, 24 snapshot restores, original continuation/Pause, 33 atomic declines");
|
||||
return 0;
|
||||
}
|
||||
@@ -5,16 +5,27 @@
|
||||
uint32_t i_stat, g_debug_current_func_addr, g_debug_last_store_pc;
|
||||
uint64_t s_frame_count, psx_cycle_count;
|
||||
static int test_dma_active;
|
||||
static int test_raw_fail, test_raw_bad_header, test_raw_bad_copy;
|
||||
static uint8_t test_raw_mode = 2, test_raw_submode = 8;
|
||||
int dma_cdrom_transfer_active(void) { return test_dma_active; }
|
||||
void psx_irq_raise(uint32_t bit, uint32_t detail) { (void)detail; i_stat |= bit; }
|
||||
int iso_read_raw_sector(void* h, uint32_t lba, uint8_t* out, int size) {
|
||||
(void)h; memset(out, 0, size); out[15]=2; out[18]=out[22]=8;
|
||||
int m, s, f;
|
||||
(void)h; if (test_raw_fail) return 0;
|
||||
memset(out, 0, size);
|
||||
lba_to_msf((int)lba, 150, &m, &s, &f);
|
||||
out[12]=bin_to_bcd(m); out[13]=bin_to_bcd(s);
|
||||
out[14]=bin_to_bcd(f) ^ (test_raw_bad_header ? 1u : 0u);
|
||||
out[15]=test_raw_mode; out[18]=out[22]=test_raw_submode;
|
||||
if (test_raw_bad_copy) out[20]=1;
|
||||
memset(out+24, (unsigned char)lba, 2048); return 1;
|
||||
}
|
||||
int iso_read_sector(void* h,uint32_t lba,uint8_t* out,int size) {(void)h;memset(out,(unsigned char)lba,size);return 1;}
|
||||
|
||||
static void setup(void) {
|
||||
test_dma_active = 0;
|
||||
test_raw_fail = test_raw_bad_header = test_raw_bad_copy = 0;
|
||||
test_raw_mode = 2; test_raw_submode = 8;
|
||||
psx_cycle_count = 1000000;
|
||||
cdrom_init("synthetic");
|
||||
mode_reg = 0x80;
|
||||
|
||||
Reference in New Issue
Block a user