mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-21 18:29:05 +02:00
Merge pull request #22703 from hrydgard/me-wait
Media Engine part 4: Wait loops and DMACplus
This commit is contained in:
8 files changed
+572
-1
No files matched your search
@@ -97,6 +97,7 @@ void Init() {
|
||||
MeMMIO::SysregDevice(),
|
||||
MeMMIO::InterruptDevice(),
|
||||
MeMMIO::MemProtectDevice(),
|
||||
MeMMIO::DmacplusDevice(),
|
||||
};
|
||||
for (MMIODevice *device : g_devices) {
|
||||
device->Reset();
|
||||
|
||||
@@ -15,7 +15,16 @@
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Log.h"
|
||||
#include "Core/Debugger/MemBlockInfo.h"
|
||||
#include "Core/HLE/sceDisplay.h"
|
||||
#include "Core/HLE/sceMpegbase.h"
|
||||
#include "GPU/GPUCommon.h"
|
||||
#include "GPU/ge_constants.h"
|
||||
#include "Core/HW/MMIO.h"
|
||||
#include "Core/HW/MeMMIO.h"
|
||||
#include "Core/MemMap.h"
|
||||
@@ -162,6 +171,265 @@ public:
|
||||
void DoState(PointerWrap &p) override {}
|
||||
};
|
||||
|
||||
// A PL080-like DMA controller with the LCD and colour space conversion registers below its
|
||||
// channels (4-7, at 0x180). Transfers run at once, when a channel is enabled. Addresses are physical;
|
||||
// on the ME's bus (a master bit in the control word), the low ones are its eDRAM. Each linked list
|
||||
// item is five words (source, destination, next, control, configuration).
|
||||
class MeDmacplusDevice : public MMIODevice {
|
||||
public:
|
||||
MeDmacplusDevice() : MMIODevice("DMACplus", 0xBC800000, 0x200) {}
|
||||
|
||||
bool Read(MIPSState *mips, u32 offset, int size, u32 *value) override {
|
||||
u32 *reg = Register(offset);
|
||||
if (!reg)
|
||||
return false;
|
||||
*value = *reg;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Write(MIPSState *mips, u32 offset, int size, u32 value) override {
|
||||
if (!g_meState || (offset & 3))
|
||||
return false;
|
||||
MeCore::MEState::Dmacplus &d = g_meState->dmacplus;
|
||||
if (offset == 0x08) {
|
||||
d.intTCStatus &= ~value;
|
||||
return true;
|
||||
}
|
||||
if (offset == 0x10) {
|
||||
// No errors to clear.
|
||||
return true;
|
||||
}
|
||||
u32 *reg = Register(offset);
|
||||
if (!reg)
|
||||
return false;
|
||||
*reg = value;
|
||||
if (offset == 0x100) {
|
||||
SetLCD(d);
|
||||
} else if (offset == 0x160 && (value & 1)) {
|
||||
RunCSC(d);
|
||||
} else if (offset >= 0x180 && (offset & 0x1F) == 0x10 && (value & 1)) {
|
||||
RunChannel(d, (offset - 0x180) / 0x20);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Reset() override {}
|
||||
void DoState(PointerWrap &p) override {}
|
||||
|
||||
private:
|
||||
static u32 *Register(u32 offset) {
|
||||
if (!g_meState || (offset & 3))
|
||||
return nullptr;
|
||||
MeCore::MEState::Dmacplus &d = g_meState->dmacplus;
|
||||
if (offset == 0x00 || offset == 0x04 || offset == 0x14)
|
||||
return &d.intTCStatus;
|
||||
if (offset >= 0x100 && offset < 0x110)
|
||||
return &d.lcd[(offset - 0x100) / 4];
|
||||
if (offset >= 0x120 && offset <= 0x160)
|
||||
return &d.csc[(offset - 0x120) / 4];
|
||||
if (offset >= 0x180 && (offset & 0x1F) < 0x14)
|
||||
return &d.channels[(offset - 0x180) / 0x20][(offset & 0x1F) / 4];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Pixel format 0 is 8888 (mcidclan's samples). The others aren't known.
|
||||
static void SetLCD(const MeCore::MEState::Dmacplus &d) {
|
||||
if (d.lcd[1] != 0) {
|
||||
WARN_LOG(Log::ME, "DMACplus: LCD pixel format %d", d.lcd[1]);
|
||||
}
|
||||
__DisplaySetFramebuf(d.lcd[0], d.lcd[3], GE_FORMAT_8888, 1);
|
||||
}
|
||||
|
||||
static u8 *PhysicalPointer(u32 addr, u32 size, bool meBus) {
|
||||
addr &= 0x1FFFFFFF;
|
||||
const Memory::AddressSpace *space = g_meState->cpu.space;
|
||||
if (meBus && addr + size <= space->edramSize)
|
||||
return space->GetPointerUnchecked(addr);
|
||||
if (Memory::IsValidRange(addr, size))
|
||||
return Memory::GetPointerWriteUnchecked(addr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The colour space conversion, as the firmware's DMACplus driver programs it (lowio.prx 6.61,
|
||||
// sceDmacplus_driver_d126494b and 9c492b9b; dmacplus.prx 1.50 the same). The frame is two sets of
|
||||
// half its height: buffers 0, 1, 4 and 5, and 2, 3, 6 and 7. In AVC mode they're the even and odd
|
||||
// rows of a frame in the decoder's tiled layout (see UntileYCbCr); in VME mode, planar Y at 0x120
|
||||
// (0x128), Cb at 0x130 (0x138) and Cr at 0x134 (0x13C). Each set goes to its own destination, or
|
||||
// both interleave by rows into the first. Not checked on hardware: what 0x140 bits 1 and 2 do
|
||||
// (taken as chroma line replication and leaving out the second set, from mcidclan's samples),
|
||||
// and 0x15C bit 8 (taken as chroma being offset by 128).
|
||||
static void RunCSC(MeCore::MEState::Dmacplus &d) {
|
||||
const u32 *r = d.csc;
|
||||
auto reg = [&](u32 offset) { return r[(offset - 0x120) / 4]; };
|
||||
const u32 config = reg(0x140);
|
||||
const bool vme = (config & 1) != 0;
|
||||
const bool replicateChroma = (config & 2) != 0;
|
||||
const bool onlyFirst = (config & 4) != 0;
|
||||
const int width = ((config >> 8) & 0x3F) * 16;
|
||||
const int height = ((config >> 16) & 0x3F) * 16;
|
||||
const u32 output = reg(0x14C);
|
||||
const int stride = output >> 8;
|
||||
const int format = (output >> 1) & 3;
|
||||
const bool separate = (output & 1) != 0;
|
||||
d.csc[(0x160 - 0x120) / 4] &= ~1;
|
||||
d.intTCStatus |= 2;
|
||||
if (width == 0 || height == 0 || stride < width) {
|
||||
ERROR_LOG(Log::ME, "DMACplus: bad colour space conversion %dx%d, stride %d", width, height, stride);
|
||||
return;
|
||||
}
|
||||
|
||||
const int setHeight = height / 2;
|
||||
const int width2 = width / 2;
|
||||
// Each set's planes, as rows: luma, and chroma at half width.
|
||||
const u8 *luma[2]{}, *cb[2]{}, *cr[2]{};
|
||||
int lumaStride = width, chromaStride = width2, chromaRowShift = 0;
|
||||
std::vector<u8> planes;
|
||||
if (vme) {
|
||||
const int chromaRows = replicateChroma ? setHeight / 2 : setHeight;
|
||||
chromaRowShift = replicateChroma ? 1 : 0;
|
||||
for (int s = 0; s < 2; ++s) {
|
||||
luma[s] = PhysicalPointer(reg(0x120 + s * 8), width * setHeight, true);
|
||||
cb[s] = PhysicalPointer(reg(0x130 + s * 8), width2 * chromaRows, true);
|
||||
cr[s] = PhysicalPointer(reg(0x134 + s * 8), width2 * chromaRows, true);
|
||||
}
|
||||
} else {
|
||||
const int blocks = (width / 16) * (height / 16);
|
||||
const int sizes[8] = { 64 * blocks, 64 * blocks, 64 * blocks, 64 * blocks, 32 * blocks, 32 * blocks, 32 * blocks, 32 * blocks };
|
||||
const u8 *src[8];
|
||||
for (int i = 0; i < 8; ++i)
|
||||
src[i] = PhysicalPointer(reg(0x120 + i * 4), sizes[i], true);
|
||||
planes.resize(width * height + 2 * width2 * (height / 2));
|
||||
u8 *frameY = planes.data();
|
||||
u8 *frameCb = frameY + width * height;
|
||||
u8 *frameCr = frameCb + width2 * (height / 2);
|
||||
if (!separate) {
|
||||
// The sets are the frame's even and odd rows, both with the frame's chroma rows.
|
||||
UntileYCbCr(frameY, frameCb, frameCr, src, sizes, width, height);
|
||||
lumaStride = width * 2;
|
||||
for (int s = 0; s < 2; ++s) {
|
||||
luma[s] = frameY + s * width;
|
||||
cb[s] = frameCb;
|
||||
cr[s] = frameCr;
|
||||
}
|
||||
} else {
|
||||
// Each set is a picture of its own, with its own chroma at half its height (as
|
||||
// mcidclan's me-avc-csc-hd lays out its data, n12ToHw.py).
|
||||
std::vector<u8> tall(width * height + 2 * width2 * (height / 2));
|
||||
for (int s = 0; s < 2; ++s) {
|
||||
const u8 *setSrc[8] = { src[s * 2], src[s * 2 + 1], nullptr, nullptr, src[4 + s * 2], src[5 + s * 2], nullptr, nullptr };
|
||||
// Untiled as the even rows of a picture twice as tall.
|
||||
UntileYCbCr(tall.data(), tall.data() + width * height, tall.data() + width * height + width2 * (height / 2), setSrc, sizes, width, height);
|
||||
u8 *setY = frameY + s * width * setHeight;
|
||||
for (int y = 0; y < setHeight; ++y)
|
||||
memcpy(setY + y * width, tall.data() + 2 * y * width, width);
|
||||
u8 *cbOut = frameCb + s * width2 * (setHeight / 2);
|
||||
u8 *crOut = frameCr + s * width2 * (setHeight / 2);
|
||||
const u8 *tallCb = tall.data() + width * height;
|
||||
const u8 *tallCr = tallCb + width2 * (height / 2);
|
||||
for (int y = 0; y < setHeight / 2; ++y) {
|
||||
memcpy(cbOut + y * width2, tallCb + 2 * y * width2, width2);
|
||||
memcpy(crOut + y * width2, tallCr + 2 * y * width2, width2);
|
||||
}
|
||||
luma[s] = setY;
|
||||
cb[s] = cbOut;
|
||||
cr[s] = crOut;
|
||||
}
|
||||
chromaRowShift = 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto coefficient = [](u32 value, int shift) {
|
||||
int c = (value >> shift) & 0x3FF;
|
||||
return c >= 0x200 ? c - 0x400 : c;
|
||||
};
|
||||
int coef[3][3];
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
const u32 value = reg(0x150 + c * 4);
|
||||
coef[c][0] = coefficient(value, 0);
|
||||
coef[c][1] = coefficient(value, 10);
|
||||
coef[c][2] = coefficient(value, 20);
|
||||
}
|
||||
const int lumaOffset = reg(0x15C) & 0xFF;
|
||||
const int chromaOffset = (reg(0x15C) & 0x100) ? 128 : 0;
|
||||
|
||||
static const GEBufferFormat formats[4] = { GE_FORMAT_8888, GE_FORMAT_565, GE_FORMAT_5551, GE_FORMAT_4444 };
|
||||
const int bpp = format == 0 ? 4 : 2;
|
||||
const u32 rows = separate ? setHeight : height;
|
||||
const u32 dstAddr[2] = { reg(0x144), reg(0x148) };
|
||||
for (int s = 0; s < 2; ++s) {
|
||||
if (separate && s == 1 && onlyFirst)
|
||||
break;
|
||||
if (!luma[s] || !cb[s] || !cr[s]) {
|
||||
ERROR_LOG(Log::ME, "DMACplus: colour space conversion source %d not readable", s);
|
||||
continue;
|
||||
}
|
||||
const u32 dst = separate ? dstAddr[s] : dstAddr[0];
|
||||
u8 *out = PhysicalPointer(dst, stride * rows * bpp, false);
|
||||
if (!out) {
|
||||
ERROR_LOG(Log::ME, "DMACplus: colour space conversion destination %08x not writable", dst);
|
||||
continue;
|
||||
}
|
||||
for (int y = 0; y < setHeight; ++y) {
|
||||
const u8 *rowY = luma[s] + y * lumaStride;
|
||||
const u8 *rowCb = cb[s] + (y >> chromaRowShift) * chromaStride;
|
||||
const u8 *rowCr = cr[s] + (y >> chromaRowShift) * chromaStride;
|
||||
const int outRow = separate ? y : y * 2 + s;
|
||||
u8 *outPixels = out + outRow * stride * bpp;
|
||||
for (int x = 0; x < width; ++x) {
|
||||
const int Y = rowY[x] - lumaOffset;
|
||||
const int Cb = rowCb[x >> 1] - chromaOffset;
|
||||
const int Cr = rowCr[x >> 1] - chromaOffset;
|
||||
int rgb[3];
|
||||
for (int c = 0; c < 3; ++c)
|
||||
rgb[c] = std::clamp((coef[c][0] * Y + coef[c][1] * Cb + coef[c][2] * Cr + 64) >> 7, 0, 255);
|
||||
switch (format) {
|
||||
case 0: ((u32_le *)outPixels)[x] = rgb[0] | (rgb[1] << 8) | (rgb[2] << 16); break;
|
||||
case 1: ((u16_le *)outPixels)[x] = (rgb[0] >> 3) | ((rgb[1] >> 2) << 5) | ((rgb[2] >> 3) << 11); break;
|
||||
case 2: ((u16_le *)outPixels)[x] = (rgb[0] >> 3) | ((rgb[1] >> 3) << 5) | ((rgb[2] >> 3) << 10); break;
|
||||
default: ((u16_le *)outPixels)[x] = (rgb[0] >> 4) | ((rgb[1] >> 4) << 4) | ((rgb[2] >> 4) << 8); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const u32 size = stride * rows * bpp;
|
||||
NotifyMemInfo(MemBlockFlags::WRITE, dst & 0x1FFFFFFF, size, "DMACplusCsc");
|
||||
if (gpu)
|
||||
gpu->PerformWriteFormattedFromMemory(dst & 0x1FFFFFFF, size, stride, formats[format], width);
|
||||
}
|
||||
}
|
||||
|
||||
static void RunChannel(MeCore::MEState::Dmacplus &d, int channel) {
|
||||
u32 *regs = d.channels[channel];
|
||||
// Bounded, in case of a list that loops.
|
||||
for (int item = 0; item < 0x10000; ++item) {
|
||||
const u32 control = regs[3];
|
||||
const u32 width = 1 << std::min((control >> 18) & 7, 4U);
|
||||
const u32 bytes = (control & 0xFFF) * width;
|
||||
u8 *src = PhysicalPointer(regs[0], bytes, (control >> 24) & 1);
|
||||
u8 *dst = PhysicalPointer(regs[1], bytes, (control >> 25) & 1);
|
||||
if (src && dst) {
|
||||
memmove(dst, src, bytes);
|
||||
NotifyMemInfo(MemBlockFlags::WRITE, regs[1] & 0x1FFFFFFF, bytes, "DMACplus");
|
||||
} else {
|
||||
ERROR_LOG(Log::ME, "DMACplus: bad transfer of %d bytes from %08x to %08x", bytes, regs[0], regs[1]);
|
||||
}
|
||||
if (control & 0x80000000)
|
||||
d.intTCStatus |= 1 << (channel + 4);
|
||||
const u32 next = regs[2];
|
||||
if (next == 0)
|
||||
break;
|
||||
const u8 *lli = PhysicalPointer(next & ~3, 20, false);
|
||||
if (!lli) {
|
||||
ERROR_LOG(Log::ME, "DMACplus: bad linked list item at %08x", next);
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < 5; ++i)
|
||||
regs[i] = ((const u32_le *)lli)[i];
|
||||
}
|
||||
regs[4] &= ~1;
|
||||
}
|
||||
};
|
||||
|
||||
static MeDmacplusDevice g_dmacplus;
|
||||
static MeSysregDevice g_sysreg;
|
||||
static MeInterruptDevice g_interrupts;
|
||||
static MeMemProtectDevice g_memProtect;
|
||||
@@ -174,6 +442,10 @@ MMIODevice *InterruptDevice() {
|
||||
return &g_interrupts;
|
||||
}
|
||||
|
||||
MMIODevice *DmacplusDevice() {
|
||||
return &g_dmacplus;
|
||||
}
|
||||
|
||||
MMIODevice *MemProtectDevice() {
|
||||
return &g_memProtect;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ namespace MeMMIO {
|
||||
MMIODevice *SysregDevice();
|
||||
// The ME's interrupt controller (0xBC300000). The main CPU has its own there.
|
||||
MMIODevice *InterruptDevice();
|
||||
// DMACplus (0xBC800000): DMA between the CPUs' memories, and the LCD.
|
||||
MMIODevice *DmacplusDevice();
|
||||
// Memory protection (0xBC000000), which the ME's startup code unlocks.
|
||||
MMIODevice *MemProtectDevice();
|
||||
|
||||
|
||||
+127
-1
@@ -28,6 +28,7 @@
|
||||
#include "Core/MIPS/MeCore.h"
|
||||
#include "Core/MIPS/Interpreter.h"
|
||||
#include "Core/MIPS/JitCommon/JitCommon.h"
|
||||
#include "Core/MIPS/MIPSAnalyst.h"
|
||||
#include "Core/MIPS/MIPSTables.h"
|
||||
|
||||
namespace MeCore {
|
||||
@@ -93,6 +94,8 @@ static void PowerOn(MEState *me) {
|
||||
cpu.cop0[22] = ME_CPU_ID;
|
||||
cpu.r[MIPS_REG_SP] = 0x80000000 + cpu.space->edramSize - 0x20;
|
||||
me->ramConfig = 0x20000002;
|
||||
// The luma offset (16) and chroma offset of the firmware's default conversion (avcodec.prx).
|
||||
me->dmacplus.csc[(0x15C - 0x120) / 4] = 0x110;
|
||||
me->busClock = 0xD61D;
|
||||
// Held in reset, as the register says: a read-modify-write of it mustn't release the ME.
|
||||
me->resetFlags = 4;
|
||||
@@ -166,6 +169,10 @@ void DoState(PointerWrap &p) {
|
||||
Do(p, me->ramConfig);
|
||||
Do(p, me->mutex);
|
||||
DoArray(p, me->memProtect, ARRAY_SIZE(me->memProtect));
|
||||
Do(p, me->dmacplus.intTCStatus);
|
||||
DoArray(p, me->dmacplus.lcd, ARRAY_SIZE(me->dmacplus.lcd));
|
||||
DoArray(p, me->dmacplus.csc, ARRAY_SIZE(me->dmacplus.csc));
|
||||
DoArray(p, &me->dmacplus.channels[0][0], 4 * 5);
|
||||
DoArray(p, me->cpu.space->GetPointerUnchecked(0), me->cpu.space->edramSize);
|
||||
DoArray(p, me->cpu.space->GetPointerUnchecked(0x04000000), me->cpu.space->localSize);
|
||||
Do(p, meIntrEvent);
|
||||
@@ -193,6 +200,7 @@ static void CheckInterrupts(MEState *me) {
|
||||
if ((status & (STATUS_IE | STATUS_EXL | STATUS_ERL)) != STATUS_IE) {
|
||||
return;
|
||||
}
|
||||
me->waiting = false;
|
||||
// Interrupts go straight to EBase (COP0 25), with ExcCode 0.
|
||||
cpu.cop0[14] = cpu.pc;
|
||||
cpu.cop0[13] &= ~CAUSE_EXCCODE_BD;
|
||||
@@ -201,6 +209,117 @@ static void CheckInterrupts(MEState *me) {
|
||||
cpu.llBit = 0;
|
||||
}
|
||||
|
||||
// Most ME code that waits for the main CPU polls in a loop like lw t0, flag; beq t0, zero, loop; nop.
|
||||
// Such a loop stores nothing and reads only what it wrote itself earlier in the same pass (or what
|
||||
// it doesn't write), so every pass goes the same way while memory stays the same - and it does
|
||||
// while the ME runs, since the main CPU doesn't. One that loops back once will loop until the end
|
||||
// of the slice. Sony's firmware waits with halt instead, and homebrew loops that also store (a
|
||||
// heartbeat) or span several blocks aren't recognized.
|
||||
static const int MAX_WAIT_LOOP_INSTRUCTIONS = 16;
|
||||
|
||||
static bool IsPlainOp(MIPSOpcode op, MIPSInfo info) {
|
||||
const u64 notPlain = BAD_INSTRUCTION | OUT_MEM | OUT_OTHER | IN_OTHER | IS_SYSCALL | IS_FPU | IS_VFPU |
|
||||
IN_FPUFLAG | OUT_FPUFLAG | IN_VFPU_CC | OUT_VFPU_CC | IN_LO | IN_HI | OUT_LO | OUT_HI | OUT_RA |
|
||||
IN_FS | IN_FT | OUT_FD | OUT_FS | OUT_FT | IN_VS | IN_VT | OUT_VD;
|
||||
if (info & notPlain)
|
||||
return false;
|
||||
switch (op >> 26) {
|
||||
case 0x10: // cop0, eret
|
||||
case 0x1C: // halt, mfic, mtic
|
||||
case 0x22: // lwl
|
||||
case 0x26: // lwr
|
||||
case 0x2F: // cache
|
||||
case 0x30: // ll
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Where a conditional branch or j goes, or 0 if it isn't one (or is a jr, jal or similar).
|
||||
static u32 BranchTarget(u32 pc, MIPSOpcode op) {
|
||||
const u32 code = op >> 26;
|
||||
const bool regimm = code == 1 && ((op >> 16) & 0x1C) == 0; // bltz, bgez and their likely forms
|
||||
if ((code >= 4 && code <= 7) || (code >= 0x14 && code <= 0x17) || regimm)
|
||||
return pc + 4 + ((s32)(s16)(op & 0xFFFF) << 2);
|
||||
if (code == 2)
|
||||
return ((pc + 4) & 0xF0000000) | ((op & 0x03FFFFFF) << 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The end (after the delay slot) of a wait loop starting at start, or 0 if it isn't one.
|
||||
static u32 FindWaitLoop(const Memory::AddressSpace &space, u32 start) {
|
||||
MIPSOpcode ops[MAX_WAIT_LOOP_INSTRUCTIONS + 1];
|
||||
int count = 0;
|
||||
bool loops = false;
|
||||
for (u32 pc = start; count < MAX_WAIT_LOOP_INSTRUCTIONS; pc += 4) {
|
||||
if (!space.IsValid4AlignedAddress(pc))
|
||||
return 0;
|
||||
const MIPSOpcode op(space.ReadUnchecked_U32(pc));
|
||||
const MIPSInfo info = MIPSGetInfo(op);
|
||||
ops[count++] = op;
|
||||
if (loops) {
|
||||
// The back edge's delay slot.
|
||||
if ((info & (IS_CONDBRANCH | IS_JUMP)) || !IsPlainOp(op, info))
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
if (info & (IS_CONDBRANCH | IS_JUMP)) {
|
||||
const u32 target = BranchTarget(pc, op);
|
||||
// An exit is fine, but likely branches skip their delay slot when not taken.
|
||||
if (target == 0 || ((info & LIKELY) && target != start))
|
||||
return 0;
|
||||
loops = target == start;
|
||||
} else if (!IsPlainOp(op, info)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!loops)
|
||||
return 0;
|
||||
|
||||
// Every register the loop writes has to be written before it's read in a pass.
|
||||
u32 written = 0;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const MIPSGPReg out = MIPSAnalyst::GetOutGPReg(ops[i]);
|
||||
if (out != MIPS_REG_INVALID && out != MIPS_REG_ZERO)
|
||||
written |= 1U << out;
|
||||
}
|
||||
u32 writtenSoFar = 0;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
for (int r = 1; r < 32; ++r) {
|
||||
if ((written & (1U << r)) && !(writtenSoFar & (1U << r)) && MIPSAnalyst::ReadsFromGPReg(ops[i], (MIPSGPReg)r))
|
||||
return 0;
|
||||
}
|
||||
const MIPSGPReg out = MIPSAnalyst::GetOutGPReg(ops[i]);
|
||||
if (out != MIPS_REG_INVALID && out != MIPS_REG_ZERO)
|
||||
writtenSoFar |= 1U << out;
|
||||
}
|
||||
return start + count * 4;
|
||||
}
|
||||
|
||||
// At the top of a wait loop, runs a pass of it. If that comes back to the top, it would only go on
|
||||
// doing so: true, and the ME waits out the rest of the slice.
|
||||
static bool WaitsForMainCPU(MEState *me) {
|
||||
MIPSState &cpu = me->cpu;
|
||||
const u32 start = cpu.pc;
|
||||
auto it = me->waitLoopEnds.find(start);
|
||||
if (it == me->waitLoopEnds.end())
|
||||
it = me->waitLoopEnds.emplace(start, FindWaitLoop(*cpu.space, start)).first;
|
||||
const u32 end = it->second;
|
||||
if (end == 0 || cpu.inDelaySlot)
|
||||
return false;
|
||||
for (int i = 0; i <= MAX_WAIT_LOOP_INSTRUCTIONS; ++i) {
|
||||
cpu.downcount -= MIPS_InterpretSingleStep(&cpu);
|
||||
if (cpu.inDelaySlot)
|
||||
continue;
|
||||
if (cpu.pc == start)
|
||||
return true;
|
||||
if (cpu.pc < start || cpu.pc >= end)
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void RunCPU(MIPSState &cpu) {
|
||||
if (!cpu.jit) {
|
||||
MIPSInterpret_RunDowncount(&cpu);
|
||||
@@ -225,9 +344,16 @@ void RunFor(int cycles) {
|
||||
}
|
||||
MIPSState &cpu = me->cpu;
|
||||
cpu.downcount += cycles;
|
||||
if (!cpu.pendingInvalidates_.empty() || cpu.invalidateAll_) {
|
||||
me->waitLoopEnds.clear();
|
||||
}
|
||||
// The main CPU may have given a waiting loop what it waits for.
|
||||
me->waiting = false;
|
||||
while (cpu.downcount >= 0 && coreState == CORE_RUNNING_CPU) {
|
||||
CheckInterrupts(me);
|
||||
if (me->halted) {
|
||||
if (!me->halted && !me->waiting)
|
||||
me->waiting = WaitsForMainCPU(me);
|
||||
if (me->halted || me->waiting) {
|
||||
cpu.downcount = -1;
|
||||
} else {
|
||||
// Run a part, with the rest set aside so the clock stays right.
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
|
||||
@@ -56,6 +58,21 @@ struct MEState {
|
||||
u32 ramConfig = 0; // 0x40. Bits 0-1: RAM size, 16 << n MB.
|
||||
u32 mutex = 0; // 0x48. Bit 0 is the main CPU's, bit 1 the ME's.
|
||||
u32 memProtect[20]{}; // 0xBC000000-0xBC00004C
|
||||
|
||||
// DMACplus (0xBC800000), which moves data between the CPUs' memories, and drives the LCD and the
|
||||
// colour space conversion. Only what homebrew's ME code uses.
|
||||
struct Dmacplus {
|
||||
u32 intTCStatus = 0; // 0x04, channels that finished with their interrupt bit set
|
||||
u32 lcd[4]{}; // 0x100: framebuffer, pixel format, width, stride
|
||||
u32 csc[0x11]{}; // 0x120-0x160: colour space conversion
|
||||
u32 channels[4][5]{}; // 0x180-0x1FF: source, destination, next LLI, control, configuration
|
||||
} dmacplus;
|
||||
|
||||
// Not saved: whether the code at an address is a wait loop (see IsWaitLoop), and where it ends.
|
||||
// 0 for not one. Cleared when the ME's code changes.
|
||||
std::unordered_map<u32, u32> waitLoopEnds;
|
||||
// Waiting for the main CPU, for the rest of this RunFor.
|
||||
bool waiting = false;
|
||||
};
|
||||
|
||||
// Null when the ME isn't emulated.
|
||||
|
||||
@@ -918,6 +918,155 @@ bool TestMeInterrupts() {
|
||||
return TestMeInterruptsOn(CPUCore::INTERPRETER) && TestMeInterruptsOn(CPUCore::IR_INTERPRETER) && TestMeInterruptsOn(CPUCore::JIT);
|
||||
}
|
||||
|
||||
// The ME skips the rest of a slice spent polling for the main CPU, and only then.
|
||||
static bool TestMeWaitOn(CPUCore core) {
|
||||
Memory::g_PSPModel = PSP_MODEL_FAT;
|
||||
SetupJitHarness();
|
||||
PSP_CoreParameter().cpuCore = core;
|
||||
MMIO::Init();
|
||||
bool success = true;
|
||||
|
||||
if (!MeCore::Init()) {
|
||||
printf("No Media Engine on this platform, skipping\n");
|
||||
MMIO::Shutdown();
|
||||
DestroyJitHarness();
|
||||
return true;
|
||||
}
|
||||
MeCore::MEState *me = MeCore::g_meState;
|
||||
|
||||
const u32 code = 0x08900000;
|
||||
const u32 flag = code + 0x200;
|
||||
const u32 marker = code + 0x204;
|
||||
const u32 heartbeat = code + 0x208;
|
||||
const char *lines[] = {
|
||||
"lui t0, 0x0890",
|
||||
"lw t1, 0x200(t0)", // 0x08900004: waits for the flag
|
||||
"beq t1, zero, 0x08900004",
|
||||
"nop",
|
||||
"ori t2, zero, 0x1234",
|
||||
"sw t2, 0x204(t0)",
|
||||
"lui t3, 0x0010", // then counts down, which isn't waiting
|
||||
"addiu t3, t3, -1", // 0x0890001C
|
||||
"bne t3, zero, 0x0890001C",
|
||||
"nop",
|
||||
"lw t4, 0x208(t0)", // 0x08900028: and beats, which isn't either
|
||||
"addiu t4, t4, 1",
|
||||
"sw t4, 0x208(t0)",
|
||||
"j 0x08900028",
|
||||
"nop",
|
||||
};
|
||||
u32 addr = code;
|
||||
for (const char *line : lines) {
|
||||
std::string error;
|
||||
if (!MipsAssembleOpcode(line, currentDebugMIPS, addr, &error)) {
|
||||
printf("ERROR: %s: %s\n", line, error.c_str());
|
||||
success = false;
|
||||
}
|
||||
addr += 4;
|
||||
}
|
||||
const u32 jump[] = { MIPS_MAKE_LUI(MIPS_REG_T0, 0x0890), 0x01000008 /* jr t0 */, MIPS_MAKE_NOP() };
|
||||
memcpy(Memory::GetBootPage() + 0x40, jump, sizeof(jump));
|
||||
|
||||
MeCore::SetReset(false);
|
||||
MeCore::RunFor(100000);
|
||||
EXPECT_TRUE(me->waiting);
|
||||
EXPECT_EQ_HEX(me->cpu.pc, code + 4);
|
||||
EXPECT_TRUE(MeCore::GetTicks() >= 100000 && MeCore::GetTicks() < 100010);
|
||||
|
||||
Memory::WriteUnchecked_U32(1, flag);
|
||||
MeCore::RunFor(100000);
|
||||
EXPECT_FALSE(me->waiting);
|
||||
EXPECT_EQ_HEX(Memory::ReadUnchecked_U32(marker), 0x1234);
|
||||
const u32 left = me->cpu.r[MIPS_REG_T3];
|
||||
EXPECT_TRUE(left > 0 && left < 0x100000 - 1000);
|
||||
|
||||
MeCore::RunFor(10000000);
|
||||
EXPECT_EQ_INT(me->cpu.r[MIPS_REG_T3], 0);
|
||||
EXPECT_FALSE(me->waiting);
|
||||
EXPECT_TRUE(Memory::ReadUnchecked_U32(heartbeat) > 1000);
|
||||
|
||||
MeCore::Shutdown();
|
||||
MMIO::Shutdown();
|
||||
DestroyJitHarness();
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TestMeWait() {
|
||||
return TestMeWaitOn(CPUCore::INTERPRETER) && TestMeWaitOn(CPUCore::IR_INTERPRETER) && TestMeWaitOn(CPUCore::JIT);
|
||||
}
|
||||
|
||||
// DMACplus moves data between the ME's eDRAM and main memory, following linked list items.
|
||||
bool TestMeDmacplus() {
|
||||
Memory::g_PSPModel = PSP_MODEL_FAT;
|
||||
SetupJitHarness();
|
||||
MMIO::Init();
|
||||
bool success = true;
|
||||
|
||||
if (!MeCore::Init()) {
|
||||
printf("No Media Engine on this platform, skipping\n");
|
||||
MMIO::Shutdown();
|
||||
DestroyJitHarness();
|
||||
return true;
|
||||
}
|
||||
const Memory::AddressSpace *space = MeCore::g_meState->cpu.space;
|
||||
for (u32 i = 0; i < 0x2000; i += 4)
|
||||
space->WriteUnchecked_U32(0xA0000000 | i, 0x80000000 + i);
|
||||
|
||||
// ME to main CPU (channel 5): 0x1000 bytes in 16-byte units, then 0x1000 more from the list.
|
||||
auto control = [](bool srcME, bool dstME, u32 widthBit, u32 count) {
|
||||
return (dstME ? 1U << 25 : 0) | (srcME ? 1U << 24 : 0) | (widthBit << 21) | (widthBit << 18) | (1U << 15) | (1U << 12) | count;
|
||||
};
|
||||
const u32 lli = 0x08900000;
|
||||
const u32 item[5] = { 0x1000, 0x08A01000, 0, control(true, false, 2, 0x400), 1 };
|
||||
for (int i = 0; i < 5; ++i)
|
||||
Memory::WriteUnchecked_U32(item[i], lli + i * 4);
|
||||
MMIO::Write(&mipsr4k, 0xBC8001B0, 4, 0);
|
||||
MMIO::Write(&mipsr4k, 0xBC8001A0, 4, 0);
|
||||
MMIO::Write(&mipsr4k, 0xBC8001A4, 4, 0x48A00000);
|
||||
MMIO::Write(&mipsr4k, 0xBC8001A8, 4, lli);
|
||||
MMIO::Write(&mipsr4k, 0xBC8001AC, 4, control(true, false, 4, 0x100) | 0x80000000);
|
||||
MMIO::Write(&mipsr4k, 0xBC8001B0, 4, 1);
|
||||
EXPECT_EQ_HEX(MMIO::Read(&mipsr4k, 0xBC8001B0, 4), 0);
|
||||
EXPECT_EQ_HEX(Memory::ReadUnchecked_U32(0x08A00000), 0xA0000000);
|
||||
EXPECT_EQ_HEX(Memory::ReadUnchecked_U32(0x08A01FFC), 0xA0001FFC);
|
||||
EXPECT_EQ_HEX(MMIO::Read(&mipsr4k, 0xBC800004, 4), 1 << 5);
|
||||
MMIO::Write(&mipsr4k, 0xBC800008, 4, 1 << 5);
|
||||
EXPECT_EQ_HEX(MMIO::Read(&mipsr4k, 0xBC800004, 4), 0);
|
||||
|
||||
// And back (channel 4), to further into the eDRAM.
|
||||
MMIO::Write(&mipsr4k, 0xBC800180, 4, 0x08A00000);
|
||||
MMIO::Write(&mipsr4k, 0xBC800184, 4, 0x10000);
|
||||
MMIO::Write(&mipsr4k, 0xBC800188, 4, 0);
|
||||
MMIO::Write(&mipsr4k, 0xBC80018C, 4, control(false, true, 2, 0x800));
|
||||
MMIO::Write(&mipsr4k, 0xBC800190, 4, 1);
|
||||
EXPECT_EQ_HEX(space->ReadUnchecked_U32(0x80011FFC), 0xA0001FFC);
|
||||
|
||||
// Colour space conversion: a planar (VME) 32x32 grey frame to 8888, with the default matrix.
|
||||
for (u32 i = 0; i < 32 * 32; ++i)
|
||||
space->WriteUnchecked_U8(128, 0x80020000 + i);
|
||||
for (u32 i = 0; i < 16 * 16 * 2; ++i)
|
||||
space->WriteUnchecked_U8(128, 0x80030000 + i);
|
||||
const u32 csc[] = {
|
||||
0x120, 0x20000, 0x128, 0x20000 + 32 * 16, 0x130, 0x30000, 0x134, 0x30000 + 16 * 8,
|
||||
0x138, 0x30000 + 16 * 16, 0x13C, 0x30000 + 16 * 24,
|
||||
0x140, (2 << 16) | (2 << 8) | (1 << 1) | 1,
|
||||
0x144, 0x48B00000, 0x148, 0x48B00000 + 64 * 16 * 4,
|
||||
0x14C, (64 << 8) | 1,
|
||||
0x150, (0x0CC << 20) | 0x095, 0x154, (0x398 << 20) | (0x3CE << 10) | 0x095, 0x158, (0x102 << 10) | 0x095,
|
||||
};
|
||||
for (size_t i = 0; i < ARRAY_SIZE(csc); i += 2)
|
||||
MMIO::Write(&mipsr4k, 0xBC800000 + csc[i], 4, csc[i + 1]);
|
||||
MMIO::Write(&mipsr4k, 0xBC800160, 4, 1);
|
||||
EXPECT_EQ_HEX(MMIO::Read(&mipsr4k, 0xBC800160, 4) & 1, 0);
|
||||
EXPECT_EQ_HEX(Memory::ReadUnchecked_U32(0x08B00000), 0x828282);
|
||||
EXPECT_EQ_HEX(Memory::ReadUnchecked_U32(0x08B00000 + 64 * 31 * 4 + 31 * 4), 0x828282);
|
||||
|
||||
MeCore::Shutdown();
|
||||
MMIO::Shutdown();
|
||||
DestroyJitHarness();
|
||||
return success;
|
||||
}
|
||||
|
||||
// Differential fuzzing of the CPU cores. Random straight-line code (ALU, FPU, VFPU, loads and
|
||||
// stores) runs from a random register state on each core, and every core has to end up with the
|
||||
// interpreter's state, NaN payloads aside.
|
||||
|
||||
@@ -25,6 +25,8 @@ bool TestMMIO();
|
||||
bool TestMESpace();
|
||||
bool TestMeCore();
|
||||
bool TestMeInterrupts();
|
||||
bool TestMeWait();
|
||||
bool TestMeDmacplus();
|
||||
bool TestCPUEquivalence();
|
||||
bool TestCPURoundingMode();
|
||||
bool TestVFPUDotJit();
|
||||
@@ -3738,6 +3738,8 @@ TestItem availableTests[] = {
|
||||
TEST_ITEM(MESpace),
|
||||
TEST_ITEM(MeCore),
|
||||
TEST_ITEM(MeInterrupts),
|
||||
TEST_ITEM(MeWait),
|
||||
TEST_ITEM(MeDmacplus),
|
||||
TEST_ITEM(CPUEquivalence),
|
||||
TEST_ITEM(CPURoundingMode),
|
||||
TEST_ITEM(VFPUDotJit),
|
||||
|
||||
Reference in new issue
Block a user