Skip to content

Start the main thread with COP0 Status.IE set - #214

Merged
ran-j merged 1 commit into
ran-j:mainfrom
Sinan-Karakaya:fix/cop0-status-init
Aug 18, 2026
Merged

Start the main thread with COP0 Status.IE set#214
ran-j merged 1 commit into
ran-j:mainfrom
Sinan-Karakaya:fix/cop0-status-init

Conversation

@Sinan-Karakaya

@Sinan-Karakaya Sinan-Karakaya commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Guest code reads COP0 Status to decide whether interrupts are enabled, and two different bits are involved:

bit name who sets it
0 IE the architectural MIPS interrupt enable. The kernel sets it once during boot and normally leaves it set
16 EIE the EE-specific enable that ei / di toggle

We never execute the boot ROM, so nothing set either one, and R5900Context started with Status at 0.

That is not cosmetic. libkernel's StartThread opens with

mfc0  $v0, Status
xori  $v0, $v0, 1
andi  $v0, $v0, 1     ; -> 1 when IE is clear

and returns -1 when that is true: its "you must call iStartThread from an interrupt handler" guard. With Status at zero the guard fires every time, so every StartThread fails.

Dragon Quest VIII (SLUS-21207) hits this during boot. It creates its CD streaming thread, gets -1, prints Can't start thread for streaming. and then deadlocks every guest thread, none runnable, EeScheduler::waitForEvent in 10 of 10 stack samples. Nothing in the runtime logs anything, because from its point of view the guest asked a question and got an answer.

EIE matters for the matching reason: DIntr reports whether it was set so the caller knows whether to pair it with an EIntr. Starting at zero makes DIntr always answer "already disabled", so the re-enable never happens.

Two changes, both needed:

  • R5900Context's constructor sets Status to EIE | IE instead of 0. BEV is deliberately left clear — that selects the boot exception vectors, which is the pre-handoff state, not this one.
  • PS2Runtime's constructor no longer memsets m_cpuContext. R5900Context already zeroes itself before applying its reset values, so the memset only threw them away. Threads created later were unaffected, because EeScheduler::startThread assigns R5900Context{}, which is why this presented as "the main thread cannot start threads" rather than something more obviously global.

Verified on a fully recompiled retail binary: before, the streaming thread never starts; after, it does and boot proceeds to the next stage.

@ran-j ran-j left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch, almost ready to merge

Comment thread ps2xRuntime/include/ps2_runtime.h Outdated
// 0x00400000 = BEV (Boot Exception Vectors).
// 0x00000000 = Normal mode (after BIOS handoff).
cop0_status = 0x00000000;
// Status as the EE kernel leaves it when it hands control to the game,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you reduce this long comment please

Comment thread ps2xRuntime/src/lib/ps2_runtime.cpp Outdated
#endif

std::memset(&m_cpuContext, 0, sizeof(m_cpuContext));
// Assign a default-constructed context rather than memset-ing this one.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we dont need this comment

Guest code reads COP0 Status to decide whether interrupts are enabled,
and two different bits are involved:

  IE  (bit 0)   the architectural MIPS interrupt enable, set once by the
                kernel during boot and normally left set.
  EIE (bit 16)  the EE-specific enable that `ei` and `di` toggle.

We never execute the boot ROM, so nothing was setting either one, and
R5900Context started with Status at zero.

That is not cosmetic. libkernel's StartThread opens with
`mfc0 Status; xori 1; andi 1` and bails out with -1 when IE is clear --
its "you must call iStartThread from an interrupt handler" guard. With
Status at zero that guard fired every time, so every StartThread failed.
Dragon Quest VIII hits this during boot: it creates its CD streaming
thread, gets -1, prints "Can't start thread for streaming." and then
deadlocks with every thread blocked and none runnable. Nothing in the
runtime logs anything, because from its point of view the guest simply
asked a question and got an answer.

EIE matters for the matching reason: DIntr reports whether it was set so
the caller knows whether to pair it with an EIntr. Starting at zero makes
DIntr always answer "already disabled", so the re-enable never happens.

Two changes, both needed:

- R5900Context's constructor now sets Status to EIE | IE rather than 0.
  BEV is deliberately left clear -- that selects the boot exception
  vectors, which is the pre-handoff state, not this one.

- PS2Runtime's constructor no longer memsets m_cpuContext. R5900Context
  already zeroes itself before applying its reset values, so the memset
  only threw those values away. Threads created later were unaffected
  because EeScheduler::startThread assigns `R5900Context{}`, which is
  why this presented as "the main thread cannot start threads" rather
  than something more obviously global.

(cherry picked from commit 2ac2ce632082ff8b7683f0380361ddcbc410bdbc)
@ran-j
ran-j merged commit 14b1e5c into ran-j:main Aug 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants