A PS1 EBOOT now boots the installed firmware's POPS manager
(flash0:/kd/popsman.prx), which loads its PS1 emulator (pops_0Xg.prx) for
the game. Load_PSP_POPS stands in for the kernel's reboot into its POPS
configuration: it loads the manager and that configuration's user modules,
and a helper thread makes the kernel init's scePopsManLoadModule(path, 0)
call. InitForKernel reports a POPS boot from the memory stick.
The emulator plays its sound on the Media Engine, so a PS1 boot always
gets the emulated ME, whatever the homebrew setting says, and is refused
on platforms that can't emulate it. The manager boots the ME with its own
code (POPS's reboot has no me_wrapper, so Sony's ME firmware isn't booted
for it), and its ME loop runs there unchanged, playing through the audio
block.
Everything in POPS that reaches hardware registers is kernel code, so a
POPS boot leaves the ME's kernel mode stopgap (g_kernelModeAnywhere) off:
7% fewer host instructions in Crash Bandicoot.
The manager also drives the GE directly: its stall register and FINISH
status are an MMIO device that only answers during a POPS boot, and stall
writes extend the HLE GPU's current list.
The 6.60 and 6.61 popsman.prx and pops_01g/02g.prx have the same code. The
manager's is checked when it's loaded, and any other one fails the boot
with a message. Savestates include the boot's threads.
Reading a game's disc image needs the next two changes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The audio hardware at 0xBE000000, as POPS's ME loop (popsman.prx +2F88)
drives it: after setting it up, it writes one stereo sample (left in the
low half) to 0x70 whenever status 0x28 bit 5 says the FIFO has room,
and sleeps in halt in between on the ME's interrupt source 12. Measured
from the ME on a PSP-1000 (pspautotest me/audiofifo): the FIFO holds 24
samples and plays them at 44.1kHz while 0x04 bit 1 is set; status 0x28
has bit 4 while playing, bit 5 while there's room, and bit 1 once it has
played out its last sample, a sample after the FIFO empties; and source
12 is a level, up while there's room, which halt wakes on with
interrupts disabled. What it plays is mixed into the output.
A halted ME used to sleep until the main CPU's next slice. It now wakes
within the slice when the full FIFO next has room, as POPS's loop sleeps
for every sample.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Kernel code the ME runs calls a few firmware functions directly: POPS's
ME loop calls sceKernelDcacheWritebackInvalidateAll for every sample. On
a PSP the loader links kernel imports as direct jumps, so the ME runs
the function's code; here they're HLE syscall stubs. The cache functions
only work on the calling CPU's caches, and the ME's aren't emulated,
so they return 0 and do nothing, as does an unresolved import (as on
the main CPU). Others are still an error, now naming the function.
The IR ends the block after one, as for a syscall on the main CPU: a jr
with it in its delay slot leaves the exit to it, and the ME ran on into
the next stub.
A syscall instruction of the ME's own should take its exception vector
(ExcCode 8); none has come up yet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
popsman.prx starts the Media Engine and sets up audio through them. The
ME reset, bus clock and AVC reset are the calls the older NIDs already
have. The others set bits of registers lowio.prx 6.61 writes for them
(0x50 bit 15+n, 0x58 bit 16+n, 0x60 bit 18, 0x78 bits 7+n and 11), and
C36775AD (called with 0, 1 before audio is set up) isn't read yet. The
system register device now keeps every register it doesn't model.
Under --vsh these stand in for lowio.prx's own, which write the same
registers.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MIST (and psp-media-engine-safe-task, and NJEMU through it) restart the
ME through me_wrapper's bootstrap at 0xBFC00040, after patching it to
point a0 at main RAM, which makes the firmware take its resume-from-
sleep path: restore eDRAM from a0 and go back to the main loop. They
then put a task's address in the firmware's command table with
DMACplus and send a command with the soft interrupt.
- The boot now leaves a bootstrap with 6.61's layout at 0xBFC00040,
and a stand-in for me_wrapper's ME stub (which isn't loaded under
HLE) that runs the image.
- The firmware never sets Status.IE: it gates interrupts with mtic
alone, so either one enables them now. Its main loop enables them for
only a few instructions, so mtic takes a pending one right away.
- Opcode 0x2C, which the resume path runs, is a no-op on the ME.
- Sysreg 0x00 reads 0 (the bootstrap's debug handler check), 0x70 is
kept, and the DDR flush register (0xBD000004) is always done.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Instead of only putting the image's code at 0x88300000, the ME now runs
it to where a PSP's ME is after boot: idle in its main loop's halt,
waiting for commands. As me_wrapper's boot (6.61, sceMeCore_driver
5dff5c50 and 0x1c30) and its ME stub leave things: the code at
0x88300000, the image's second stream still compressed after it, the
boot page's parameters (0x70C, 0x710 the CPU clock in MHz, 0x714), and
the ME at 0x88300000 with a0 at the second stream, which the code
decompresses into eDRAM itself. The stub's part (KL4E, caches) is done
here, since it runs main CPU kernel routines.
On a PSP the firmware has long since booted when a game starts, so it
runs to its idle halt the first time the ME runs, outside emulated time
(the JIT can't run earlier, at kernel init). About 80ms of startup in a
Debug build.
The compressed copy and the starting stack go below our idle threads'
stacks at the top of kernel memory, where a PSP has them higher up.
The firmware also needs:
- the ME to ignore the low two bits of jr/jalr targets: its command
table keeps flags there (interpreter, and the IR, for the ME only)
- source 5 of the ME's interrupt controller, the codec hardware it
starts and waits for in halt during init, which isn't emulated, so it
finishes at once
- sysreg 0x54 and 0x5C kept
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ME's startup code (custom-core, libmecore) sets them, and Sony's ME
firmware lowers them around its halt. Only kept, like the other ME
devices, and only while the ME is emulated.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
What the firmware sets up for the ME at boot, which mcidclan's custom-core
libraries rely on, at kernel init when the ME is emulated:
- Sony's ME image at 0x88300000, as me_wrapper loads it: meimg.img on a
PSP-1000, me_t2img.img on later models, from the installed firmware's
flash0:/kd/resource, decrypted and KL4E-decompressed (only the first of
its two streams, the code). Custom-core calls functions in it, picking
per-model address tables by the word at 0x18. Without the firmware it's
logged and left out.
- A "SceMeRpc" sysevent handler record, which sceKernelReferSysEventHandler
(new, sceSysEventForKernel) returns, and custom-core takes over.
Also the VME's control registers (0xBCC00000), kept, with the status at
0x10 reading as done: custom-core's ME startup writes 1 there and waits.
mcidclan's custom-core poc, hw-mutex, gu-shared-list and exceptions
samples now run.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
As the firmware's DMACplus driver programs it (lowio.prx 6.61,
sceDmacplus_driver_d126494b for AVC and 9c492b9b for VME, the same in
1.50's dmacplus.prx): eight source buffers at 0x120-0x13C (two half
height sets), size in macroblocks and the mode at 0x140, two destinations
at 0x144/0x148, stride and output format (0 8888, 1 565, 2 5551, 3 4444)
at 0x14C, a Q3.7 matrix at 0x150-0x158 and the luma offset at 0x15C,
whose default (0x110, 16) is the firmware's. Writing 0x160 runs it, and
it finishes at once, with 0x04 bit 1 set.
AVC sources are in the decoder's tiled layout (UntileYCbCr): interleaved
into one destination they're the frame's even and odd rows, and going
to two, each set is a picture of its own with its own chroma, as
mcidclan's me-avc-csc-hd lays out its data. VME sources are planar.
Not checked on hardware: 0x140 bits 1 and 2 (taken as chroma line
replication and leaving out the second set, from mcidclan's samples)
and 0x15C bit 8 (taken as offsetting chroma by 128). mcidclan's
vme-hardware-csc, avc-csc-hd and vme-csc-hd-992x992 show their pictures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A PL080-like controller: channels 4-7 at 0xBC800180, run at once when
enabled, following linked list items of five words (source, destination,
next, control, configuration). Addresses are physical, and on the ME's
bus (a master bit in the control word) the low ones are its eDRAM.
Transfer size is in units of the source width. A finished channel with
its interrupt bit set shows in 0x04, cleared through 0x08.
0xBC800100-0x10C set the LCD's framebuffer, which goes to sceDisplay.
Pixel format 0 is 8888 (mcidclan's samples); others are logged. The
colour space conversion registers (0x120-0x160) are only kept so far.
Like the other ME devices, this answers only while the ME is emulated.
mcidclan's dmacplus-me2sc and sc2me-then-me2sc samples show their
transferred eDRAM.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Most homebrew ME code waits in a loop like lw t0, flag; beq t0, zero,
loop; nop: Daedalus/MElib/NooDS's mediaengine.prx, snes9xTYL, PMP Mod,
Yabause, FBA, the Touhou ports (with a few nops in it). Such a loop stores
nothing, has only plain loads, integer ALU ops, nops and branches, and
writes every register it reads before reading it in a pass, so every
pass goes the same way while memory stays the same - which it does while
the ME runs, since the main CPU doesn't. At the top of one, between
parts of a slice, the ME interprets a pass, and if it comes back to the
top, waits out the rest of the slice.
Loops that also store (a heartbeat counter) or span several blocks
aren't recognized, nor are delay loops counting down a register.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Unless the interpreter is selected, the ME gets its own IRInterpreterJit,
with its own block shadow, and runs it without CoreTiming
(RunDowncount, split out of the dispatcher loop). Its MIPSState never
touches the main CPU's replacement hook.
The IR frontend compiles through the CPU's address space: instructions
read as they are in the ME's memory (no main CPU replacements or hooks),
kernel mode always, jump targets and block bookkeeping checked against
its rules, syscall left to the interpreter (which traps it). An
interpreted instruction that ends a block now gets its own pc first, so
halt goes on from the right place.
The main CPU's icache invalidations are passed on to the ME, which runs
code from the same RAM.
The unit tests run the ME on both cores. mcidclan's me-soft-interrupt
runs about four times as fast.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sysreg (0xBC100000) gets the ME's reset bit (0x4C bit 2), the soft
interrupt (0x44, which interrupts the other CPU: the ME's controller from
the main CPU, PSP_MECODEC_INTR from the ME), the hardware mutex (0x48,
each CPU sets its own bit only while the other's is clear) and the clock
and config registers it writes. The ME's interrupt controller (0xBC300000,
write-1-to-clear, mask at 8) answers the ME only. All of it is the ME's
state and stays unhandled without an ME.
On the ME: interrupts to EBase with EPC and EXL, halt sleeping until an
unmasked interrupt, and Count, which doesn't run: it stays 0 across 10ms
with ERL set after reset and with Status cleared, and reads back what was
written. Reset keeps the GPRs and sets Status ERL/BEV/SR, which homebrew
relies on: mcidclan's samples run C on the stack Sony's ME code left. The
values are from a kernel probe on a PSP-1000 (sp 0x801FFFE0, COP0 22 = 1,
Status 0x20500404, mfic 0, which doesn't gate interrupts).
The main CPU runs base interrupt handlers the way interruptman calls them
(a0 intno, a1 arg, a2 interrupted pc; -2 disables), and interrupt 31 has
a stand-in for me_wrapper's handler, which homebrew releases first.
While the ME is emulated, any main CPU code may access hardware registers:
homebrew calls its own code in kernel mode through a kcall module. This
is a stopgap (g_kernelModeAnywhere, marked HACK): kernel mode should be
part of the CPU's state, switched on while a kernel module's export runs.
The memcpy/memmove replacements copy through MMIO when an end is a
register address, for code copied to the boot page.
mcidclan's me-soft-interrupt sample now runs, counting each interrupt in
both directions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MeCore owns a second MIPSState in the ME's address space, held in reset
until released (SetReset). It runs on the interpreter, a slice behind the
main CPU: CoreTiming runs it for the cycles the main CPU has just used, in
Advance and ForceCheck. Its own PRId (COP0 22) and mfic/mtic flag; MMIO
counts as kernel mode on it, and a syscall stops it with an error rather
than reaching HLE. The interpreter's block stepping reads delay slots
through the stepping CPU's memory, and cache instructions log at verbose
level, as ME code flushes its caches in a loop.
Created at boot only for homebrew (PBP with MG category or no SFO, ELF;
the VSH counts, as vshmain.prx loads as an ELF), when "Enable ME
emulation for homebrew" is on: a global checkbox in developer settings,
off by default and not translated yet, or --me-core. Saved in its own
section after MMIO (SaveStart v5), with its own memory; loading a state
creates or removes the ME to match.
The boot page now starts with a reset stub that sends the ME on to
0xBFC00040, where homebrew puts its code (from the firmware's reset code,
read on a PSP-1000).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>