Fix encoded CD-ROM interrupt mask matching

Adapt the CD interrupt mask/status comparison from kem0x PR #14 and centralize it in a tested helper. The HC05 exposes numeric INT1..INT5 reasons but gates the line with a direct mask/status AND, so the common 0x07 mask must also pass INT4 and INT5.

Source: https://github.com/mstan/psxrecomp/pull/14

Source-commit: https://github.com/mstan/psxrecomp/commit/dd9e5c65073dd41fbac2d965d2f945a866ec59e2

Co-authored-by: Kareem Olim <kareemolim@gmail.com>
This commit is contained in:
Matthew Stanley
2026-07-13 21:38:36 -07:00
parent dde268dc0f
commit 52eb8e8a24
4 changed files with 99 additions and 3 deletions
@@ -0,0 +1,29 @@
# kem0x PR 14: encoded CD-ROM interrupt matching
Evaluated and adapted on 2026-07-13.
## Provenance
The CD-ROM interrupt mask fix was rebuilt from Kareem Olim's
[PSXrecomp PR 14](https://github.com/mstan/psxrecomp/pull/14), specifically
commit
[`dd9e5c65073dd41fbac2d965d2f945a866ec59e2`](https://github.com/mstan/psxrecomp/commit/dd9e5c65073dd41fbac2d965d2f945a866ec59e2)
(`Deliver encoded CD interrupt reasons correctly`). The implementation here
centralizes the mask/status comparison in a tested internal helper rather than
copying the source patch verbatim.
The behavior follows the
[PSX-SPX CD-ROM register documentation](https://psx-spx.consoledev.net/cdromdrive/#0x1f801803-read-banks-1-and-3-hintsts):
HINTSTS bits 0..2 are an encoded HC05 interrupt reason, but the interrupt line
is asserted whenever `HINTMSK & HINTSTS` is nonzero. Converting the reason to a
one-hot bit before applying the mask is therefore incorrect. A common `0x07`
mask must deliver encoded INT4 (DataEnd) and INT5 (DiskError) as well as
INT1--INT3.
## Deliberately excluded
This branch does not import PR 14's WebAssembly/web frontend, Pepsiman-specific
hooks or patches, CD-DA playback changes, SPU changes, raster/rendering changes,
or any other runtime and build-system material. Those changes have different
scope and review requirements and are not needed for encoded interrupt mask
matching.
+5 -3
View File
@@ -11,6 +11,7 @@
*/
#include "cdrom.h"
#include "cdrom_irq.h"
#include "dma.h"
#include "spu.h"
#include "event_ring.h"
@@ -578,7 +579,8 @@ static void present_cdrom_irq(void) {
* the guest's command/ack write — and thus from being lost to that ISR's
* own trailing INTC ack. */
if (cdrom_irq_present_delay > 0) return;
if (irq_flag && (irq_enable & (1 << (irq_flag - 1))) && !cdrom_intc_request_latched) {
if (cdrom_irq_mask_matches_reason(irq_enable, irq_flag) &&
!cdrom_intc_request_latched) {
psx_irq_raise(2, irq_flag); /* IRQ_CDROM; detail = CD response/IRQ type */
cdrom_intc_request_latched = 1;
cdrom_intc_latched_generation = cdrom_irq_generation;
@@ -590,7 +592,7 @@ static void present_cdrom_irq(void) {
/* Fire CDROM IRQ into the interrupt controller (explicit, per command/response).
* Trace the masked case here only (low-frequency); refresh stays silent. */
static void fire_cdrom_irq(void) {
if (irq_flag && !(irq_enable & (1 << (irq_flag - 1)))) {
if (irq_flag && !cdrom_irq_mask_matches_reason(irq_enable, irq_flag)) {
trace_cdrom('f', 0, irq_flag, 0);
}
present_cdrom_irq();
@@ -1841,7 +1843,7 @@ uint32_t cdrom_cycles_to_irq(uint32_t i_mask) {
if (!(i_mask & (1u << 2))) return 0xFFFFFFFFu; /* IRQ_CDROM masked */
uint32_t best = 0xFFFFFFFFu;
/* Armed response awaiting presentation (will raise bit2 when delay hits 0). */
if (irq_flag && (irq_enable & (1u << (irq_flag - 1)))) {
if (cdrom_irq_mask_matches_reason(irq_enable, irq_flag)) {
uint32_t d = cdrom_irq_present_delay > 0 ? (uint32_t)cdrom_irq_present_delay : 0u;
if (d < best) best = d;
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef PSXRECOMP_CDROM_IRQ_H
#define PSXRECOMP_CDROM_IRQ_H
#include <stdint.h>
/* HINTSTS bits 0..2 contain an encoded interrupt reason (INT1..INT5), not
* independent one-hot flags. The controller nevertheless gates its IRQ line
* by directly ANDing HINTSTS with HINTMSK. In particular, mask 0x07 enables
* all five encoded reasons, including INT4 and INT5. */
static inline int cdrom_irq_mask_matches_reason(uint8_t irq_enable,
uint8_t irq_reason) {
return (irq_enable & irq_reason & 0x07u) != 0;
}
#endif /* PSXRECOMP_CDROM_IRQ_H */
+50
View File
@@ -0,0 +1,50 @@
/*
* Validate PS1 CD-ROM interrupt mask matching.
*
* Build:
* cc -std=c99 -Wall -Wextra -Werror -I../src \
* -o test_cdrom_irq_mask test_cdrom_irq_mask.c
*/
#include "cdrom_irq.h"
#include <stdint.h>
#include <stdio.h>
static int failures;
#define CHECK(expected, mask, reason, label) do { \
int actual = cdrom_irq_mask_matches_reason((uint8_t)(mask), \
(uint8_t)(reason)); \
if (actual != (expected)) { \
fprintf(stderr, "FAIL: %s (mask=0x%02X reason=%u expected=%d got=%d)\n",\
(label), (unsigned)(mask), (unsigned)(reason), \
(expected), actual); \
failures++; \
} \
} while (0)
int main(void) {
/* The conventional 0x07 mask must deliver every encoded HC05 reason. */
for (uint8_t reason = 1; reason <= 5; reason++) {
CHECK(1, 0x07, reason, "mask 0x07 enables INT1 through INT5");
}
/* The mask/status comparison is bitwise, not a reason-to-one-hot lookup. */
CHECK(0, 0x01, 2, "bit 0 does not match INT2");
CHECK(1, 0x01, 3, "bit 0 matches encoded INT3");
CHECK(0, 0x02, 4, "bit 1 does not match INT4");
CHECK(1, 0x04, 4, "bit 2 matches encoded INT4");
CHECK(1, 0x01, 5, "bit 0 matches encoded INT5");
CHECK(1, 0x04, 5, "bit 2 matches encoded INT5");
CHECK(0, 0x00, 1, "zero mask suppresses an interrupt");
CHECK(0, 0x07, 0, "reason zero means no interrupt");
CHECK(0, 0x18, 5, "audio-buffer mask bits do not match an HC05 reason");
if (failures) {
fprintf(stderr, "FAILED (%d)\n", failures);
return 1;
}
puts("ALL PASS");
return 0;
}