mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
cdrom: model timed tray cycle for reinsert
This commit is contained in:
@@ -330,6 +330,10 @@ if(BUILD_TESTING)
|
||||
target_include_directories(cdrom_irq_mask_test PRIVATE src)
|
||||
add_test(NAME cdrom_irq_mask_test COMMAND cdrom_irq_mask_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)
|
||||
|
||||
# Cross-device I_STAT ownership: a memory-card ACK IRQ (SIO0) must not
|
||||
# clear another device's pending source — pinned on SPU (I_STAT bit 9)
|
||||
# staying set across card ACK delivery until the guest acknowledges it.
|
||||
@@ -504,6 +508,9 @@ if(BUILD_TESTING)
|
||||
add_test(NAME pal_cadence_host_refresh_test
|
||||
COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_pal_cadence_host_refresh.py)
|
||||
add_test(NAME cdrom_lid_integration_test
|
||||
COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_cdrom_lid_integration.py)
|
||||
endif()
|
||||
|
||||
# Full card protocol regression: read, write, address echo, per-slot state,
|
||||
|
||||
@@ -67,6 +67,9 @@ typedef struct CdTimingPub {
|
||||
} CdTimingPub;
|
||||
uint64_t cdrom_timing_total(void);
|
||||
int cdrom_timing_record(uint64_t seq, CdTimingPub* out);
|
||||
/* Notify the guest that the mounted disc was reinserted. The controller stops
|
||||
* active transfers, reports an open shell, waits two emulated seconds, then
|
||||
* makes the mounted media readable. This call does not mount a different image. */
|
||||
void debug_force_cd_reinsert(void);
|
||||
/* FMV auto-skip detection: cdrom_xa_stream_active() lets the frontend detect
|
||||
* that streaming XA (FMV/CDDA) is in progress. The skip itself is done by the
|
||||
|
||||
+59
-17
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "cdrom.h"
|
||||
#include "cdrom_irq.h"
|
||||
#include "cdrom_lid.h"
|
||||
#include "dma.h"
|
||||
#include "spu.h"
|
||||
#include "event_ring.h"
|
||||
@@ -61,6 +62,8 @@ static uint8_t stat_reg;
|
||||
static uint8_t request_reg;
|
||||
static uint8_t irq_enable;
|
||||
static uint8_t irq_flag;
|
||||
static CdromLidState s_lid;
|
||||
static int s_lid_irq_pending;
|
||||
|
||||
/* Disc license region string returned in GetID's last four response bytes
|
||||
* ("SCEE" PAL / "SCEA" NTSC-U / "SCEI" NTSC-J). Real hardware reports the
|
||||
@@ -350,6 +353,10 @@ void cdrom_resync_deadlines_after_restore(void)
|
||||
s_cd_timing_next_due = psx_cycle_count + (uint64_t)read_delay;
|
||||
else if (!reading)
|
||||
s_cd_timing_next_due = 0;
|
||||
/* Disc reinsertion is disabled in netplay. Reset the host-side lid timer
|
||||
* when a rollback snapshot restores the shared controller wire state. */
|
||||
cdrom_lid_reset(&s_lid);
|
||||
s_lid_irq_pending = 0;
|
||||
/* pending.due_cyc / cdrom_irq_present_due rebuilt in snap_parse from
|
||||
* relative remaining (same guest clock as the restore). */
|
||||
}
|
||||
@@ -870,7 +877,7 @@ static void record_sector_history(int lba, int size, uint8_t mode, int have_raw,
|
||||
}
|
||||
|
||||
static int has_disc(void) {
|
||||
return iso_handle != NULL;
|
||||
return cdrom_lid_media_ready(&s_lid, iso_handle != NULL);
|
||||
}
|
||||
|
||||
/* CD status bits */
|
||||
@@ -917,6 +924,34 @@ static void set_irq(int type) {
|
||||
event_ring_record_aux(EV_DEQ, (uint8_t)SRC_CD_IRQ, (uint32_t)type);
|
||||
}
|
||||
|
||||
static void fire_cdrom_irq(void);
|
||||
|
||||
static void present_lid_open_irq_if_ready(void)
|
||||
{
|
||||
if (!s_lid_irq_pending || irq_flag != 0)
|
||||
return;
|
||||
stat_reg = CDSTAT_ERROR | CDSTAT_SHELL;
|
||||
response_clear();
|
||||
response_push(stat_reg);
|
||||
response_push(0x08); /* shell open */
|
||||
set_irq(CDIRQ_ERROR);
|
||||
fire_cdrom_irq();
|
||||
s_lid_irq_pending = 0;
|
||||
}
|
||||
|
||||
static void process_lid_state(void)
|
||||
{
|
||||
present_lid_open_irq_if_ready();
|
||||
if (!s_lid_irq_pending && cdrom_lid_close_if_due(&s_lid, psx_cycle_count)) {
|
||||
stat_reg &= (uint8_t)~(CDSTAT_ERROR | CDSTAT_READ | CDSTAT_PLAY |
|
||||
CDSTAT_SEEK);
|
||||
if (iso_handle)
|
||||
stat_reg |= CDSTAT_MOTOR;
|
||||
stat_reg |= CDSTAT_SHELL;
|
||||
trace_cdrom('w', 0, iso_handle ? 1u : 0u, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Present the current CD INT to the CPU interrupt controller exactly once per
|
||||
* generation. Re-presentation of the same unacked INT is suppressed by the
|
||||
* latch (this is what stops the CPS exception re-entry storm). The next
|
||||
@@ -2043,6 +2078,8 @@ static void exec_command(uint8_t cmd) {
|
||||
stat_reg |= CDSTAT_SHELL;
|
||||
}
|
||||
response_push(stat_reg);
|
||||
if (cdrom_lid_acknowledge_closed_shell(&s_lid))
|
||||
stat_reg &= (uint8_t)~CDSTAT_SHELL;
|
||||
set_irq(CDIRQ_ACK);
|
||||
break;
|
||||
|
||||
@@ -2721,6 +2758,8 @@ void cdrom_init(const char* cue_path) {
|
||||
request_reg = 0;
|
||||
irq_enable = 0x1F;
|
||||
irq_flag = 0;
|
||||
cdrom_lid_reset(&s_lid);
|
||||
s_lid_irq_pending = 0;
|
||||
cdrom_intc_request_latched = 0;
|
||||
cdrom_irq_generation = 0;
|
||||
cdrom_intc_latched_generation = 0;
|
||||
@@ -2906,6 +2945,7 @@ void cdrom_write(uint32_t addr, uint32_t value) {
|
||||
irq_flag &= ~(val & 0x1F);
|
||||
if (had_active_irq && (irq_flag & 0x1F) == 0) {
|
||||
cdrom_intc_request_latched = 0;
|
||||
present_lid_open_irq_if_ready();
|
||||
}
|
||||
if (val & 0x40) {
|
||||
param_count = 0;
|
||||
@@ -2971,6 +3011,7 @@ void cdrom_advance(uint32_t cycles) {
|
||||
* it. Note: do NOT freeze CD during rollback Replay. MotK FMV skip resim
|
||||
* resumes into VLC/sector waits; frozen IRQs → no finish_frame hang.
|
||||
* Menu CD asymmetry is contained by no-invent during media + core POST. */
|
||||
process_lid_state();
|
||||
refresh_cdrom_irq_line();
|
||||
process_pending(cycles);
|
||||
try_execute_queued_command();
|
||||
@@ -2983,6 +3024,7 @@ void cdrom_advance(uint32_t cycles) {
|
||||
}
|
||||
process_read_stream(cycles);
|
||||
process_cdda_stream(cycles);
|
||||
process_lid_state();
|
||||
refresh_cdrom_irq_line();
|
||||
}
|
||||
|
||||
@@ -3382,30 +3424,30 @@ int cdrom_snapshot_read(const uint8_t *p, uint32_t len) {
|
||||
/* Absolute host deadlines are not on the wire — rebuild from restored
|
||||
* relative read_delay (psx_cycle_count is resynced by the load caller). */
|
||||
cdrom_resync_deadlines_after_restore();
|
||||
/* The snapshot wire predates the timed-lid helper. Reconstruct its short
|
||||
* host-side deadline from the saved hardware status instead of changing
|
||||
* the section size and invalidating existing save states. */
|
||||
if (iso_handle && (stat_reg & (CDSTAT_ERROR | CDSTAT_SHELL)) ==
|
||||
(CDSTAT_ERROR | CDSTAT_SHELL)) {
|
||||
cdrom_lid_begin_open(&s_lid, psx_cycle_count);
|
||||
} else if (iso_handle && (stat_reg & CDSTAT_SHELL)) {
|
||||
s_lid.shell_open_latched = 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void debug_force_cd_reinsert(void) {
|
||||
// Simulamos la apertura de la bandeja borrando los flujos actuales
|
||||
/* Stop the old transfer before exposing a physical tray-open event. */
|
||||
stop_read_stream();
|
||||
stop_cdda_playback();
|
||||
xa_reset_decode();
|
||||
spu_cd_audio_reset();
|
||||
|
||||
// Forzamos el estado de la lectora a "Bandeja Abierta" temporalmente
|
||||
stat_reg = CDSTAT_SHELL;
|
||||
stat_reg = CDSTAT_ERROR | CDSTAT_SHELL;
|
||||
cdrom_clear_pending_dataready();
|
||||
response_clear();
|
||||
|
||||
// Forzamos al emulador a reinicializar el lector con el archivo de disco actual
|
||||
if (iso_handle) {
|
||||
stat_reg = CDSTAT_MOTOR; // Volvemos a encender el motor virtual
|
||||
}
|
||||
|
||||
// Emitimos una interrupción de ACK para despertar al kernel del juego
|
||||
set_irq(CDIRQ_ACK);
|
||||
fire_cdrom_irq();
|
||||
|
||||
// Forzamos la ejecución de cualquier comando atascado en cola
|
||||
try_execute_queued_command();
|
||||
cdrom_lid_begin_open(&s_lid, psx_cycle_count);
|
||||
s_lid_irq_pending = 1;
|
||||
present_lid_open_irq_if_ready();
|
||||
trace_cdrom('O', 0, (uint32_t)CDROM_LID_CLOSE_DELAY_CYCLES,
|
||||
(uint32_t)(CDROM_LID_CLOSE_DELAY_CYCLES >> 32));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef PSXRECOMP_CDROM_LID_H
|
||||
#define PSXRECOMP_CDROM_LID_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* The PS1 CPU clock is 33.8688 MHz. Keep inserted media hidden from the
|
||||
* guest for two seconds so software can observe a physical open/close cycle. */
|
||||
#define CDROM_LID_CLOSE_DELAY_CYCLES (33868800ull * 2ull)
|
||||
|
||||
typedef struct CdromLidState {
|
||||
uint64_t close_due;
|
||||
uint8_t physical_open;
|
||||
uint8_t shell_open_latched;
|
||||
} CdromLidState;
|
||||
|
||||
static inline void cdrom_lid_reset(CdromLidState *state)
|
||||
{
|
||||
state->close_due = 0;
|
||||
state->physical_open = 0;
|
||||
state->shell_open_latched = 0;
|
||||
}
|
||||
|
||||
static inline void cdrom_lid_begin_open(CdromLidState *state, uint64_t now)
|
||||
{
|
||||
state->close_due = now + CDROM_LID_CLOSE_DELAY_CYCLES;
|
||||
state->physical_open = 1;
|
||||
state->shell_open_latched = 1;
|
||||
}
|
||||
|
||||
static inline int cdrom_lid_media_ready(const CdromLidState *state,
|
||||
int host_media_present)
|
||||
{
|
||||
return host_media_present && !state->physical_open;
|
||||
}
|
||||
|
||||
static inline int cdrom_lid_close_if_due(CdromLidState *state, uint64_t now)
|
||||
{
|
||||
if (!state->physical_open || now < state->close_due)
|
||||
return 0;
|
||||
state->physical_open = 0;
|
||||
state->close_due = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* GetStat returns the latched ShellOpen bit once after the physical lid has
|
||||
* closed. The next GetStat can report the inserted disc normally. */
|
||||
static inline int cdrom_lid_acknowledge_closed_shell(CdromLidState *state)
|
||||
{
|
||||
if (state->physical_open || !state->shell_open_latched)
|
||||
return 0;
|
||||
state->shell_open_latched = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,43 @@
|
||||
"""Guard the CD controller wiring for the timed tray cycle."""
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
SOURCE = (Path(__file__).parents[1] / "src" / "cdrom.c").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
|
||||
def function_body(name: str) -> str:
|
||||
match = re.search(rf"\b{name}\s*\([^)]*\)\s*\{{", SOURCE)
|
||||
assert match, f"missing function: {name}"
|
||||
start = match.end()
|
||||
depth = 1
|
||||
cursor = start
|
||||
while cursor < len(SOURCE) and depth:
|
||||
if SOURCE[cursor] == "{":
|
||||
depth += 1
|
||||
elif SOURCE[cursor] == "}":
|
||||
depth -= 1
|
||||
cursor += 1
|
||||
assert depth == 0, f"unterminated function: {name}"
|
||||
return SOURCE[start : cursor - 1]
|
||||
|
||||
|
||||
reinsert = function_body("debug_force_cd_reinsert")
|
||||
assert "cdrom_lid_begin_open" in reinsert
|
||||
assert "s_lid_irq_pending = 1" in reinsert
|
||||
assert "set_irq(CDIRQ_ACK)" not in reinsert
|
||||
|
||||
present = function_body("present_lid_open_irq_if_ready")
|
||||
assert "response_push(0x08)" in present
|
||||
assert "set_irq(CDIRQ_ERROR)" in present
|
||||
|
||||
has_disc = function_body("has_disc")
|
||||
assert "cdrom_lid_media_ready" in has_disc
|
||||
|
||||
advance = function_body("cdrom_advance")
|
||||
assert advance.count("process_lid_state()") >= 2
|
||||
|
||||
print("PASS: timed CD lid integration guards")
|
||||
@@ -0,0 +1,47 @@
|
||||
/* Validate the guest-visible CD tray interval used for disc reinsertion. */
|
||||
#include "cdrom_lid.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static int failures;
|
||||
|
||||
#define CHECK(expr, label) do { \
|
||||
if (!(expr)) { \
|
||||
fprintf(stderr, "FAIL: %s\n", (label)); \
|
||||
failures++; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
CdromLidState lid;
|
||||
const uint64_t start = 1234567ull;
|
||||
|
||||
cdrom_lid_reset(&lid);
|
||||
CHECK(cdrom_lid_media_ready(&lid, 1), "mounted disc starts readable");
|
||||
|
||||
cdrom_lid_begin_open(&lid, start);
|
||||
CHECK(lid.physical_open, "reinsert opens the physical lid");
|
||||
CHECK(lid.shell_open_latched, "reinsert latches ShellOpen");
|
||||
CHECK(!cdrom_lid_media_ready(&lid, 1),
|
||||
"mounted image stays hidden while the lid is open");
|
||||
CHECK(!cdrom_lid_close_if_due(
|
||||
&lid, start + CDROM_LID_CLOSE_DELAY_CYCLES - 1),
|
||||
"lid remains open for the complete delay");
|
||||
CHECK(cdrom_lid_close_if_due(
|
||||
&lid, start + CDROM_LID_CLOSE_DELAY_CYCLES),
|
||||
"lid closes exactly at the deadline");
|
||||
CHECK(cdrom_lid_media_ready(&lid, 1),
|
||||
"mounted image becomes readable after the lid closes");
|
||||
CHECK(cdrom_lid_acknowledge_closed_shell(&lid),
|
||||
"first closed-lid GetStat consumes the ShellOpen latch");
|
||||
CHECK(!cdrom_lid_acknowledge_closed_shell(&lid),
|
||||
"later GetStat calls do not repeat ShellOpen");
|
||||
|
||||
if (failures) {
|
||||
fprintf(stderr, "FAILED (%d)\n", failures);
|
||||
return 1;
|
||||
}
|
||||
puts("ALL PASS");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user