Fix SPU livelock from atomic DMA cache line stores

Accurate SPU DMA and Accurate Cache Line Stores were both on. Together they
route every 128-byte SPU DMA store through do_cell_atomic_128_store, turning
bulk DMA into one atomic reservation store per cache line. Reservation
contention then outruns the rate it can drain, an SPU spins in do_putllc
forever, and the PPU stalls behind it on a semaphore the SPU never signals
while the RSX idles.

Both default off upstream. Cache Line Stores was wrong in our defaults;
SPU DMA already defaulted off but was stored on, so a default change alone
would not reach anyone who had already run the app, hence the migration.

Seen on Minecraft, which froze loading world chunks, that being bulk SPU DMA
and little else. Load dependent, so it presented as an intermittent freeze.
This commit is contained in:
jpolo1224
2026-08-08 16:30:53 -04:00
parent 25d01cd7d3
commit 20aebe9517
2 changed files with 35 additions and 1 deletions
@@ -61,6 +61,7 @@ object ConfigStore {
private const val KEY_SPU_DECODER_RESTORE = "config.migrated.spuDecoderRestoreLlvm"
private const val KEY_XFLOAT_BACK_TO_APPROX = "config.migrated.xfloatBackToApprox"
private const val KEY_PRECISE_SPU_OFF = "config.migrated.preciseSpuVerifyOff"
private const val KEY_ATOMIC_DMA_OFF = "config.migrated.atomicDmaStoresOff"
private const val KEY_RELAXED_ZCULL_ON = "config.migrated.relaxedZcullOn"
private const val KEY_RELAXED_ZCULL_OFF = "config.migrated.relaxedZcullOff"
private const val KEY_AFFINITY_ON = "config.migrated.affinityScheduler"
@@ -176,6 +177,27 @@ object ConfigStore {
MainActivityRuntime.prefs.edit { putBoolean(KEY_XFLOAT_BACK_TO_APPROX, true) }
}
// Return the two reservation settings to upstream's defaults, both off.
//
// Accurate SPU DMA + Accurate Cache Line Stores together route every 128-byte
// SPU DMA store through do_cell_atomic_128_store, so bulk DMA turns into one
// atomic reservation store per cache line. Contention then outruns the rate the
// reservations can drain and an SPU spins in do_putllc indefinitely, taking the
// PPU down with it. Minecraft froze on world chunk load, which is bulk SPU DMA
// and nothing else. Load-dependent, hence the intermittency.
//
// Both are stored true on existing installs, so the default change alone would
// not reach anyone who has already run the app.
if (!MainActivityRuntime.prefs.getBoolean(KEY_ATOMIC_DMA_OFF, false)) {
if (raw != null && (parsed.ps3.accurateCacheLine || parsed.ps3.accurateSpuDma)) {
parsed = parsed.copy(
ps3 = parsed.ps3.copy(accurateCacheLine = false, accurateSpuDma = false),
)
dirty = true
}
MainActivityRuntime.prefs.edit { putBoolean(KEY_ATOMIC_DMA_OFF, true) }
}
// Move anyone still on the old Approximate xfloat default onto Accurate.
// Approximate corrupted SPU float registers badly enough that a job
// manager built a DMA command out of one; see Settings.spuXFloat. A
@@ -156,7 +156,19 @@ data class Ps3Settings(
*/
val spuXFloat: Int = 1,
val accurateSpuRsv: Boolean = true,
val accurateCacheLine: Boolean = true,
/**
* Off, matching upstream, which is what this always should have been.
*
* With this on AND Accurate SPU DMA on, every 128-byte SPU DMA store is routed through
* do_cell_atomic_128_store, i.e. an atomic reservation store per cache line of every
* transfer. A game doing bulk DMA then generates reservation contention faster than it
* can drain, and an SPU sits in do_putllc retrying forever while the PPU stalls behind
* it and the RSX spins idle. Minecraft froze loading world chunks, which is almost
* nothing but bulk SPU DMA.
*
* Load-dependent, so it presented as an intermittent freeze rather than a clean failure.
*/
val accurateCacheLine: Boolean = false,
val accurateRsxRsv: Boolean = false,
val ppuRsvPriority: Boolean = false,
val spuVerification: Boolean = true,