mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
Feature/ee timers and fixes (#203)
* refactor: from guest threads to EE scheduler * feat: bad wip mpeg fix for code veronica * feat: cheap copy from host feat: small perf o vsync tick * feat: added EE clock Hz fix: fix MPEG out of sync with new EE refactor * fix: fix lotr tests * fix: fix cri dtx loading fix: fix wrong mmi instruction translation fix: fix thread info params feat: added EE timers decoder and consumer feat: split SFI and IOP memory to prevent collision and overrides * feat: revert wrong changes
This commit is contained in:
@@ -1181,13 +1181,10 @@ namespace ps2x::iop::detail
|
||||
continue;
|
||||
}
|
||||
|
||||
appendToSjrmtData(sjrmt->second,
|
||||
chunkDataAddress,
|
||||
chunkLength);
|
||||
appendToSjrmtData(sjrmt->second, chunkDataAddress, chunkLength);
|
||||
(void)writeGuestPod(m_host, commandAddress + 4u, sjx->second.eeObjectAddress);
|
||||
constexpr uint8_t roomLine = 0u;
|
||||
(void)m_host.writeGuest(commandAddress + 1u,
|
||||
&roomLine,
|
||||
sizeof(roomLine));
|
||||
(void)m_host.writeGuest(commandAddress + 1u, &roomLine, sizeof(roomLine));
|
||||
}
|
||||
consumeActivePs2RnaStreamsLocked();
|
||||
}
|
||||
|
||||
@@ -456,7 +456,7 @@ namespace ps2recomp
|
||||
{
|
||||
// Swaps halfwords 1<->3 and 5<->7 within the 128-bit register
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_shufflelo_epi16(_mm_shufflehi_epi16(GPR_VEC(ctx, {}), _MM_SHUFFLE(2,3,0,1)), _MM_SHUFFLE(2,3,0,1)));",
|
||||
inst.rd, inst.rs);
|
||||
inst.rd, inst.rt);
|
||||
}
|
||||
|
||||
|
||||
@@ -465,7 +465,7 @@ namespace ps2recomp
|
||||
// Reverses the order of the 8 halfwords
|
||||
return fmt::format("{{ __m128i mask = _mm_setr_epi8(14,15, 12,13, 10,11, 8,9, 6,7, 4,5, 2,3, 0,1); "
|
||||
"SET_GPR_VEC(ctx, {}, PS2_SHUFFLE_EPI8(GPR_VEC(ctx, {}), mask)); }}",
|
||||
inst.rd, inst.rs);
|
||||
inst.rd, inst.rt);
|
||||
}
|
||||
|
||||
|
||||
@@ -514,7 +514,7 @@ namespace ps2recomp
|
||||
std::string CodeGenerator::translatePEXEW(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PEXEW(GPR_VEC(ctx, {})));",
|
||||
inst.rd, inst.rs);
|
||||
inst.rd, inst.rt);
|
||||
}
|
||||
|
||||
|
||||
@@ -522,7 +522,7 @@ namespace ps2recomp
|
||||
{
|
||||
// Rotates words left by 3: [d,c,b,a] -> [a,d,c,b]
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(0,3,2,1)));",
|
||||
inst.rd, inst.rs);
|
||||
inst.rd, inst.rt);
|
||||
}
|
||||
|
||||
|
||||
@@ -562,7 +562,7 @@ namespace ps2recomp
|
||||
{
|
||||
// Parallel Exchange Center Halfword (same as MMI2 PEXEH)
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_shufflelo_epi16(_mm_shufflehi_epi16(GPR_VEC(ctx, {}), _MM_SHUFFLE(2,3,0,1)), _MM_SHUFFLE(2,3,0,1)));",
|
||||
inst.rd, inst.rs);
|
||||
inst.rd, inst.rt);
|
||||
}
|
||||
|
||||
|
||||
@@ -571,7 +571,7 @@ namespace ps2recomp
|
||||
// Parallel Copy Halfword (Broadcast lower 16 bits of each 64-bit half)
|
||||
return fmt::format("{{ __m128i src = GPR_VEC(ctx, {}); uint16_t l = _mm_extract_epi16(src, 0); uint16_t h = _mm_extract_epi16(src, 4); \n"
|
||||
" SET_GPR_VEC(ctx, {}, _mm_set_epi16(h,h,h,h, l,l,l,l)); }}",
|
||||
inst.rs, inst.rd);
|
||||
inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
@@ -579,7 +579,7 @@ namespace ps2recomp
|
||||
{
|
||||
// Parallel Exchange Center Word (Swaps words 0<>2, 1<>3)
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(1,0,3,2)));",
|
||||
inst.rd, inst.rs);
|
||||
inst.rd, inst.rt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -279,6 +279,7 @@ public:
|
||||
|
||||
// Kernel object API. All calls except postEvent/requestStop execute on the
|
||||
// EE executor and therefore need no host synchronization.
|
||||
void setupCurrentThread(uint32_t stack, uint32_t stackSize, uint32_t gp);
|
||||
int createThread(const EeThreadCreateParams ¶ms);
|
||||
int deleteThread(int id, uint32_t &ownedStack);
|
||||
int startThread(int id, uint32_t arg, const R5900Context &caller, bool interruptSafe);
|
||||
@@ -416,6 +417,7 @@ private:
|
||||
bool m_rescheduleRequested = false;
|
||||
bool m_timeSliceExpired = false;
|
||||
bool m_insideInterrupt = false;
|
||||
uint32_t m_pendingEeTimerInterrupts = 0;
|
||||
uint64_t m_eeCycle = 0;
|
||||
uint64_t m_sliceEndCycle = kDefaultTimeSliceCycles;
|
||||
std::thread::id m_executorThread{};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
@@ -306,6 +307,12 @@ public:
|
||||
bool writeIORegister(uint32_t address, uint32_t value);
|
||||
uint32_t readIORegister(uint32_t address);
|
||||
|
||||
// EE timers advance from the scheduler's emulated EE-cycle clock. The
|
||||
// returned mask uses bits 0..3 for newly raised TIM0..TIM3 interrupts.
|
||||
uint32_t advanceEeTimers(uint64_t eeCycles) noexcept;
|
||||
[[nodiscard]] uint64_t cyclesUntilNextEeTimerInterrupt() const noexcept;
|
||||
void resetEeTimers() noexcept;
|
||||
|
||||
using GifPacketCallback = std::function<void(const uint8_t *, uint32_t)>;
|
||||
void setGifPacketCallback(GifPacketCallback cb) { m_gifPacketCallback = std::move(cb); }
|
||||
void setGifArbiter(GifArbiter *arbiter) { m_gifArbiter = arbiter; }
|
||||
@@ -432,10 +439,17 @@ public:
|
||||
bool isScratchpad(uint32_t address) const;
|
||||
uint8_t *mapVuMemory(uint32_t physAddr, uint32_t size, uint32_t &offset, uint32_t &limit);
|
||||
const uint8_t *mapVuMemory(uint32_t physAddr, uint32_t size, uint32_t &offset, uint32_t &limit) const;
|
||||
void updateEeTimer0Counter();
|
||||
struct EeTimer
|
||||
{
|
||||
uint32_t count = 0;
|
||||
uint32_t mode = 0;
|
||||
uint32_t compare = 0;
|
||||
uint32_t hold = 0;
|
||||
uint64_t clockRemainder = 0;
|
||||
};
|
||||
|
||||
std::array<EeTimer, 4> m_eeTimers{};
|
||||
void queueCompletedDmacCause(uint32_t cause);
|
||||
uint64_t m_timer0LastHostNs = 0;
|
||||
uint64_t m_timer0FractionNs = 0;
|
||||
};
|
||||
|
||||
#endif // PS2_MEMORY_H
|
||||
|
||||
@@ -41,6 +41,15 @@ namespace
|
||||
return (microseconds * EeScheduler::kEeClockHz + 999999ull) / 1000000ull;
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds eeCyclesToHostDuration(uint64_t cycles)
|
||||
{
|
||||
constexpr uint64_t kNanosecondsPerSecond = 1000000000ull;
|
||||
const uint64_t wholeSeconds = cycles / EeScheduler::kEeClockHz;
|
||||
const uint64_t remainingCycles = cycles % EeScheduler::kEeClockHz;
|
||||
const uint64_t remainingNanoseconds = (remainingCycles * kNanosecondsPerSecond + EeScheduler::kEeClockHz - 1u) / EeScheduler::kEeClockHz;
|
||||
return std::chrono::seconds(wholeSeconds) + std::chrono::nanoseconds(remainingNanoseconds);
|
||||
}
|
||||
|
||||
constexpr uint64_t kVBlankPeriodCycles = microsecondsToEeCycles(16667u);
|
||||
constexpr uint64_t kVBlankDurationCycles = microsecondsToEeCycles(500u);
|
||||
constexpr uint64_t kAlarmTickCycles = microsecondsToEeCycles(kAlarmTickMicroseconds);
|
||||
@@ -101,6 +110,7 @@ void EeScheduler::reset(uint8_t *rdram, const R5900Context &mainContext)
|
||||
m_rescheduleRequested = false;
|
||||
m_timeSliceExpired = false;
|
||||
m_insideInterrupt = false;
|
||||
m_pendingEeTimerInterrupts = 0u;
|
||||
m_eeCycle = 0u;
|
||||
m_sliceEndCycle = kDefaultTimeSliceCycles;
|
||||
m_stopRequested.store(false, std::memory_order_release);
|
||||
@@ -121,12 +131,15 @@ void EeScheduler::reset(uint8_t *rdram, const R5900Context &mainContext)
|
||||
m_gsVSyncCallbackGp = 0;
|
||||
m_gsVSyncCallbackSp = 0;
|
||||
m_runtime.memory().gs().vsyncTick.store(0u, std::memory_order_release);
|
||||
m_runtime.memory().resetEeTimers();
|
||||
|
||||
GuestThread main{};
|
||||
main.id = kMainThreadId;
|
||||
main.context = mainContext;
|
||||
main.entry = mainContext.pc;
|
||||
main.stack = getRegU32(&mainContext, 29);
|
||||
// $sp is live execution state, not the stable initial stack descriptor
|
||||
// returned by ReferThreadStatus. SetupThread records that metadata.
|
||||
main.stack = 0u;
|
||||
main.gp = getRegU32(&mainContext, 28);
|
||||
main.initialPriority = 0;
|
||||
main.currentPriority = 0;
|
||||
@@ -375,7 +388,13 @@ bool EeScheduler::checkpointDue(uint32_t cycles) noexcept
|
||||
|
||||
void EeScheduler::accountCycles(uint32_t cycles) noexcept
|
||||
{
|
||||
m_eeCycle += std::max<uint64_t>(1u, cycles);
|
||||
const uint64_t elapsed = std::max<uint64_t>(1u, cycles);
|
||||
m_eeCycle += elapsed;
|
||||
m_pendingEeTimerInterrupts |= m_runtime.memory().advanceEeTimers(elapsed);
|
||||
if (m_pendingEeTimerInterrupts != 0u)
|
||||
{
|
||||
m_checkpointPending.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
bool EeScheduler::isExecutingGuest() const noexcept
|
||||
@@ -383,6 +402,21 @@ bool EeScheduler::isExecutingGuest() const noexcept
|
||||
return m_guestExecuting.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void EeScheduler::setupCurrentThread(uint32_t stack, uint32_t stackSize, uint32_t gp)
|
||||
{
|
||||
assertExecutor();
|
||||
GuestThread *target = currentThread();
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
target->stack = stack;
|
||||
target->stackSize = stackSize;
|
||||
target->gp = gp;
|
||||
publishSnapshot();
|
||||
}
|
||||
|
||||
int EeScheduler::createThread(const EeThreadCreateParams ¶ms)
|
||||
{
|
||||
assertExecutor();
|
||||
@@ -1702,6 +1736,15 @@ void EeScheduler::processPendingEvents()
|
||||
{
|
||||
assertExecutor();
|
||||
processDueDeadlines();
|
||||
const uint32_t timerInterrupts = m_pendingEeTimerInterrupts;
|
||||
m_pendingEeTimerInterrupts = 0u;
|
||||
for (uint32_t timer = 0u; timer < 4u; ++timer)
|
||||
{
|
||||
if ((timerInterrupts & (1u << timer)) != 0u)
|
||||
{
|
||||
dispatchIrq(false, 9u + timer);
|
||||
}
|
||||
}
|
||||
std::deque<EeEvent> pending;
|
||||
{
|
||||
std::lock_guard lock(m_eventMutex);
|
||||
@@ -1950,30 +1993,55 @@ void EeScheduler::waitForEvent()
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (m_deadlines.empty())
|
||||
const uint64_t timerCycles = m_runtime.memory().cyclesUntilNextEeTimerInterrupt();
|
||||
const bool hasTimerDeadline = timerCycles != std::numeric_limits<uint64_t>::max();
|
||||
if (m_deadlines.empty() && !hasTimerDeadline)
|
||||
{
|
||||
m_eventCv.wait(lock, [this]()
|
||||
{ return !m_events.empty() || m_stopRequested.load(std::memory_order_acquire); });
|
||||
return;
|
||||
}
|
||||
|
||||
const auto next = std::min_element(m_deadlines.begin(), m_deadlines.end(),
|
||||
[](const ScheduledEvent &left, const ScheduledEvent &right)
|
||||
{
|
||||
if (left.deadlineCycle != right.deadlineCycle)
|
||||
uint64_t deadlineCycle = 0u;
|
||||
auto hostDeadline = std::chrono::steady_clock::time_point::max();
|
||||
if (!m_deadlines.empty())
|
||||
{
|
||||
const auto next = std::min_element(m_deadlines.begin(), m_deadlines.end(),
|
||||
[](const ScheduledEvent &left, const ScheduledEvent &right)
|
||||
{
|
||||
return left.deadlineCycle < right.deadlineCycle;
|
||||
}
|
||||
return left.sequence < right.sequence;
|
||||
});
|
||||
const uint64_t deadlineCycle = next->deadlineCycle;
|
||||
const auto hostDeadline = next->hostDeadline;
|
||||
if (left.deadlineCycle != right.deadlineCycle)
|
||||
{
|
||||
return left.deadlineCycle < right.deadlineCycle;
|
||||
}
|
||||
return left.sequence < right.sequence;
|
||||
});
|
||||
deadlineCycle = next->deadlineCycle;
|
||||
hostDeadline = next->hostDeadline;
|
||||
}
|
||||
if (hasTimerDeadline)
|
||||
{
|
||||
const auto timerHostDeadline = std::chrono::steady_clock::now() + eeCyclesToHostDuration(timerCycles);
|
||||
if (timerHostDeadline < hostDeadline)
|
||||
{
|
||||
deadlineCycle = m_eeCycle + timerCycles;
|
||||
hostDeadline = timerHostDeadline;
|
||||
}
|
||||
}
|
||||
|
||||
const bool signaled = m_eventCv.wait_until(lock, hostDeadline, [this]()
|
||||
{ return !m_events.empty() ||
|
||||
m_stopRequested.load(std::memory_order_acquire); });
|
||||
if (!signaled)
|
||||
{
|
||||
m_eeCycle = std::max(m_eeCycle, deadlineCycle);
|
||||
const uint64_t elapsed = deadlineCycle > m_eeCycle ? deadlineCycle - m_eeCycle : 0u;
|
||||
lock.unlock();
|
||||
uint64_t remaining = elapsed;
|
||||
while (remaining > 0u)
|
||||
{
|
||||
const uint32_t step = static_cast<uint32_t>(std::min<uint64_t>(remaining, std::numeric_limits<uint32_t>::max()));
|
||||
accountCycles(step);
|
||||
remaining -= step;
|
||||
}
|
||||
m_checkpointPending.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ namespace
|
||||
uint32_t g_cdStreamingEndLbn = 0xFFFFFFFFu;
|
||||
bool g_cdInitialized = false;
|
||||
|
||||
constexpr uint32_t kIopHeapBase = 0x01A00000;
|
||||
constexpr uint32_t kIopHeapLimit = 0x01F00000;
|
||||
constexpr uint32_t kIopHeapBase = 0x04000000;
|
||||
constexpr uint32_t kIopHeapLimit = 0x04500000;
|
||||
constexpr uint32_t kIopHeapAlign = 64;
|
||||
uint32_t g_iopHeapNext = kIopHeapBase;
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
#include "../../ps2_iop_transport.h"
|
||||
#include "runtime/ps2_address.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace ps2_stubs
|
||||
{
|
||||
@@ -59,6 +62,7 @@ namespace ps2_stubs
|
||||
std::unordered_map<uint32_t, uint32_t> g_sifSregs;
|
||||
std::unordered_map<uint32_t, uint32_t> g_sifCmdHandlers;
|
||||
std::map<uint32_t, uint32_t> g_sifHeapAllocations;
|
||||
std::array<uint8_t, kIopHeapLimit - kIopHeapBase> g_sifHeapStorage{};
|
||||
uint32_t g_sifCmdBuffer = 0u;
|
||||
uint32_t g_sifSysCmdBuffer = 0u;
|
||||
bool g_sifCmdInitialized = false;
|
||||
@@ -158,6 +162,9 @@ namespace ps2_stubs
|
||||
}
|
||||
|
||||
g_sifHeapAllocations[candidate] = alignedSize;
|
||||
std::fill_n(g_sifHeapStorage.data() + (candidate - kIopHeapBase),
|
||||
alignedSize,
|
||||
uint8_t{0});
|
||||
g_iopHeapNext = candidate + alignedSize;
|
||||
return candidate;
|
||||
}
|
||||
@@ -183,9 +190,29 @@ namespace ps2_stubs
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sifHeapMutex);
|
||||
g_sifHeapAllocations.clear();
|
||||
g_sifHeapStorage.fill(0u);
|
||||
g_iopHeapNext = kIopHeapBase;
|
||||
}
|
||||
|
||||
bool isAllocatedSifHeapRangeLocked(uint32_t address, size_t size)
|
||||
{
|
||||
if (address < kIopHeapBase || address >= kIopHeapLimit || size > static_cast<size_t>(kIopHeapLimit - address))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = g_sifHeapAllocations.upper_bound(address);
|
||||
if (it == g_sifHeapAllocations.begin())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
--it;
|
||||
|
||||
const uint64_t allocationEnd = static_cast<uint64_t>(it->first) + it->second;
|
||||
const uint64_t rangeEnd = static_cast<uint64_t>(address) + size;
|
||||
return address >= it->first && rangeEnd <= allocationEnd;
|
||||
}
|
||||
|
||||
bool isCopyableGuestAddress(uint32_t addr)
|
||||
{
|
||||
if (Ps2AddressInRange(addr, PS2_SCRATCHPAD_BASE, PS2_SCRATCHPAD_SIZE))
|
||||
@@ -211,39 +238,40 @@ namespace ps2_stubs
|
||||
return false;
|
||||
}
|
||||
|
||||
bool canCopyGuestByteRange(const uint8_t *rdram, uint32_t dstAddr, uint32_t srcAddr, uint32_t sizeBytes)
|
||||
bool canCopyAddressRange(const uint8_t *rdram, uint32_t address, uint32_t sizeBytes)
|
||||
{
|
||||
if (!rdram)
|
||||
if (isSifIopHeapRange(address, sizeBytes))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (isSifIopHeapAddress(address) || !rdram)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sizeBytes == 0u)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sizeBytes - 1u > std::numeric_limits<uint32_t>::max() - address)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0u; i < sizeBytes; ++i)
|
||||
{
|
||||
const uint32_t srcByteAddr = srcAddr + i;
|
||||
const uint32_t dstByteAddr = dstAddr + i;
|
||||
|
||||
if (!isCopyableGuestAddress(srcByteAddr) || !isCopyableGuestAddress(dstByteAddr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *src = getConstMemPtr(rdram, srcByteAddr);
|
||||
const uint8_t *dst = getConstMemPtr(rdram, dstByteAddr);
|
||||
if (!src || !dst)
|
||||
const uint32_t byteAddress = address + i;
|
||||
if (!isCopyableGuestAddress(byteAddress) ||getConstMemPtr(rdram, byteAddress) == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canCopyGuestByteRange(const uint8_t *rdram, uint32_t dstAddr, uint32_t srcAddr, uint32_t sizeBytes)
|
||||
{
|
||||
return canCopyAddressRange(rdram, srcAddr, sizeBytes) && canCopyAddressRange(rdram, dstAddr, sizeBytes);
|
||||
}
|
||||
|
||||
bool copyGuestByteRange(uint8_t *rdram, uint32_t dstAddr, uint32_t srcAddr, uint32_t sizeBytes)
|
||||
{
|
||||
if (!canCopyGuestByteRange(rdram, dstAddr, srcAddr, sizeBytes))
|
||||
@@ -256,6 +284,49 @@ namespace ps2_stubs
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool sourceIsIop = isSifIopHeapRange(srcAddr, sizeBytes);
|
||||
const bool destinationIsIop = isSifIopHeapRange(dstAddr, sizeBytes);
|
||||
if (sourceIsIop || destinationIsIop)
|
||||
{
|
||||
std::vector<uint8_t> payload(sizeBytes);
|
||||
if (sourceIsIop)
|
||||
{
|
||||
if (!readSifIopHeap(srcAddr, payload.data(), payload.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint32_t i = 0u; i < sizeBytes; ++i)
|
||||
{
|
||||
const uint8_t *src = getConstMemPtr(rdram, srcAddr + i);
|
||||
if (!src)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
payload[i] = *src;
|
||||
}
|
||||
}
|
||||
|
||||
if (destinationIsIop)
|
||||
{
|
||||
return writeSifIopHeap(dstAddr, payload.data(), payload.size());
|
||||
}
|
||||
|
||||
ps2TraceGuestRangeWrite(rdram, dstAddr, sizeBytes, "sifCopyGuestByteRange", nullptr);
|
||||
for (uint32_t i = 0u; i < sizeBytes; ++i)
|
||||
{
|
||||
uint8_t *dst = getMemPtr(rdram, dstAddr + i);
|
||||
if (!dst)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*dst = payload[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ps2TraceGuestRangeWrite(rdram, dstAddr, sizeBytes, "sifCopyGuestByteRange", nullptr);
|
||||
|
||||
const uint64_t srcBegin = srcAddr;
|
||||
@@ -293,6 +364,71 @@ namespace ps2_stubs
|
||||
}
|
||||
}
|
||||
|
||||
bool isSifIopHeapAddress(uint32_t address)
|
||||
{
|
||||
return address >= kIopHeapBase && address < kIopHeapLimit;
|
||||
}
|
||||
|
||||
bool isSifIopHeapRange(uint32_t address, size_t size)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sifHeapMutex);
|
||||
return isAllocatedSifHeapRangeLocked(address, size);
|
||||
}
|
||||
|
||||
bool readSifIopHeap(uint32_t address, void *destination, size_t size)
|
||||
{
|
||||
if (!destination && size != 0u)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(g_sifHeapMutex);
|
||||
if (!isAllocatedSifHeapRangeLocked(address, size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (size != 0u)
|
||||
{
|
||||
std::memcpy(destination,
|
||||
g_sifHeapStorage.data() + (address - kIopHeapBase),
|
||||
size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeSifIopHeap(uint32_t address, const void *source, size_t size)
|
||||
{
|
||||
if (!source && size != 0u)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(g_sifHeapMutex);
|
||||
if (!isAllocatedSifHeapRangeLocked(address, size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (size != 0u)
|
||||
{
|
||||
std::memcpy(g_sifHeapStorage.data() + (address - kIopHeapBase),
|
||||
source,
|
||||
size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool zeroSifIopHeap(uint32_t address, size_t size)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sifHeapMutex);
|
||||
if (!isAllocatedSifHeapRangeLocked(address, size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (size != 0u)
|
||||
{
|
||||
std::memset(g_sifHeapStorage.data() + (address - kIopHeapBase), 0, size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void resetSifState()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sifCmdStateMutex);
|
||||
|
||||
@@ -2,8 +2,16 @@
|
||||
|
||||
#include "ps2_stubs.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace ps2_stubs
|
||||
{
|
||||
bool isSifIopHeapAddress(uint32_t address);
|
||||
bool isSifIopHeapRange(uint32_t address, size_t size);
|
||||
bool readSifIopHeap(uint32_t address, void *destination, size_t size);
|
||||
bool writeSifIopHeap(uint32_t address, const void *source, size_t size);
|
||||
bool zeroSifIopHeap(uint32_t address, size_t size);
|
||||
|
||||
void sceSifCmdIntrHdlr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void sceSifSendCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
|
||||
@@ -495,6 +495,8 @@ namespace ps2_syscalls
|
||||
const uint32_t stack = getRegU32(ctx, 5);
|
||||
const int32_t stackSizeSigned = static_cast<int32_t>(getRegU32(ctx, 6));
|
||||
const uint32_t currentSp = getRegU32(ctx, 29);
|
||||
EeScheduler &scheduler = runtime->eeScheduler();
|
||||
scheduler.bindMainContextForSyscall(*ctx, rdram);
|
||||
|
||||
if (gp != 0u)
|
||||
{
|
||||
@@ -502,6 +504,10 @@ namespace ps2_syscalls
|
||||
}
|
||||
|
||||
uint32_t sp = currentSp;
|
||||
uint32_t initialStack = 0u;
|
||||
const uint32_t stackSize = stackSizeSigned > 0
|
||||
? static_cast<uint32_t>(stackSizeSigned)
|
||||
: 0u;
|
||||
if (stack == 0xFFFFFFFFu)
|
||||
{
|
||||
if (stackSizeSigned > 0)
|
||||
@@ -534,6 +540,16 @@ namespace ps2_syscalls
|
||||
}
|
||||
|
||||
sp &= ~0xFu;
|
||||
if (stack == 0xFFFFFFFFu)
|
||||
{
|
||||
initialStack = sp;
|
||||
}
|
||||
else if (stack != 0u)
|
||||
{
|
||||
initialStack = stack;
|
||||
}
|
||||
|
||||
scheduler.setupCurrentThread(initialStack, stackSize, getRegU32(ctx, 28));
|
||||
setReturnU32(ctx, sp);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "ps2_runtime.h"
|
||||
#include "ps2_stubs.h"
|
||||
#include "Kernel/Stubs/SIF.h"
|
||||
#include "runtime/ps2_memory.h"
|
||||
#include "Kernel/Stubs/MemoryCard.h"
|
||||
#include "Kernel/Syscalls/Common.h"
|
||||
@@ -134,6 +135,11 @@ bool PS2IopHostAdapter::readGuest(uint32_t address, void *destination, size_t si
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (ps2_stubs::isSifIopHeapAddress(address))
|
||||
{
|
||||
return ps2_stubs::readSifIopHeap(address, destination, size);
|
||||
}
|
||||
|
||||
uint8_t *source = nullptr;
|
||||
if (!guestRange(address, size, source))
|
||||
{
|
||||
@@ -152,6 +158,11 @@ bool PS2IopHostAdapter::writeGuest(uint32_t address, const void *source, size_t
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (ps2_stubs::isSifIopHeapAddress(address))
|
||||
{
|
||||
return ps2_stubs::writeSifIopHeap(address, source, size);
|
||||
}
|
||||
|
||||
uint8_t *destination = nullptr;
|
||||
if (!guestRange(address, size, destination))
|
||||
{
|
||||
@@ -168,6 +179,11 @@ bool PS2IopHostAdapter::writeGuest(uint32_t address, const void *source, size_t
|
||||
|
||||
bool PS2IopHostAdapter::zeroGuest(uint32_t address, size_t size)
|
||||
{
|
||||
if (ps2_stubs::isSifIopHeapAddress(address))
|
||||
{
|
||||
return ps2_stubs::zeroSifIopHeap(address, size);
|
||||
}
|
||||
|
||||
uint8_t *destination = nullptr;
|
||||
if (!guestRange(address, size, destination))
|
||||
{
|
||||
@@ -184,6 +200,12 @@ bool PS2IopHostAdapter::zeroGuest(uint32_t address, size_t size)
|
||||
|
||||
bool PS2IopHostAdapter::normalizeGuestAddress(uint32_t address, uint32_t &normalized) const
|
||||
{
|
||||
if (ps2_stubs::isSifIopHeapAddress(address))
|
||||
{
|
||||
normalized = address;
|
||||
return ps2_stubs::isSifIopHeapRange(address, 0u);
|
||||
}
|
||||
|
||||
bool scratchpad = false;
|
||||
if (!ps2ResolveGuestPointer(address, normalized, scratchpad) || scratchpad)
|
||||
{
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
#include "runtime/ps2_gs_gpu.h"
|
||||
#include "ps2_log.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
@@ -141,26 +141,55 @@ namespace
|
||||
} while (!csr.compare_exchange_weak(expected, desired));
|
||||
}
|
||||
|
||||
constexpr uint32_t kEeTimer0Count = 0x10000000u;
|
||||
constexpr uint32_t kEeTimer0Mode = 0x10000010u;
|
||||
constexpr uint32_t kEeTimer0Compare = 0x10000020u;
|
||||
constexpr uint32_t kEeTimer0Hold = 0x10000030u;
|
||||
constexpr std::array<uint32_t, 4> kEeTimerBases = {
|
||||
0x10000000u,
|
||||
0x10000800u,
|
||||
0x10001000u,
|
||||
0x10001800u,
|
||||
};
|
||||
constexpr uint32_t kEeTimerCountOffset = 0x00u;
|
||||
constexpr uint32_t kEeTimerModeOffset = 0x10u;
|
||||
constexpr uint32_t kEeTimerCompareOffset = 0x20u;
|
||||
constexpr uint32_t kEeTimerHoldOffset = 0x30u;
|
||||
constexpr uint32_t kEeTimerModeClksMask = 0x3u;
|
||||
constexpr uint32_t kEeTimerModeConfigMask = 0x3FFu;
|
||||
constexpr uint32_t kEeTimerModeStatusMask = 0xC00u;
|
||||
constexpr uint32_t kEeTimerModeZret = 1u << 6;
|
||||
constexpr uint32_t kEeTimerModeCue = 1u << 7;
|
||||
constexpr uint64_t kEeTimer0TicksPerSecond = 15720ull;
|
||||
constexpr uint64_t kNanosecondsPerSecond = 1000000000ull;
|
||||
constexpr uint32_t kEeTimerModeCmpe = 1u << 8;
|
||||
constexpr uint32_t kEeTimerModeOvfe = 1u << 9;
|
||||
constexpr uint32_t kEeTimerModeEquf = 1u << 10;
|
||||
constexpr uint32_t kEeTimerModeOvff = 1u << 11;
|
||||
constexpr uint64_t kEeClockHz = 294912000ull;
|
||||
constexpr std::array<uint64_t, 4> kEeTimerClockHz = {
|
||||
147456000ull,
|
||||
9216000ull,
|
||||
576000ull,
|
||||
15734ull,
|
||||
};
|
||||
|
||||
inline bool isEeTimer0Register(uint32_t address)
|
||||
inline bool decodeEeTimerRegister(uint32_t address, size_t &timerIndex, uint32_t &offset)
|
||||
{
|
||||
return address == kEeTimer0Count ||
|
||||
address == kEeTimer0Mode ||
|
||||
address == kEeTimer0Compare ||
|
||||
address == kEeTimer0Hold;
|
||||
for (size_t index = 0; index < kEeTimerBases.size(); ++index)
|
||||
{
|
||||
const uint32_t candidateOffset = address - kEeTimerBases[index];
|
||||
if (candidateOffset == kEeTimerCountOffset ||
|
||||
candidateOffset == kEeTimerModeOffset ||
|
||||
candidateOffset == kEeTimerCompareOffset ||
|
||||
(index < 2u && candidateOffset == kEeTimerHoldOffset))
|
||||
{
|
||||
timerIndex = index;
|
||||
offset = candidateOffset;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline uint64_t steadyClockNs()
|
||||
constexpr uint64_t ticksUntilMatch(uint32_t count, uint32_t target)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
return static_cast<uint64_t>(duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count());
|
||||
const uint32_t distance = (target - count) & 0xFFFFu;
|
||||
return distance == 0u ? 0x10000ull : static_cast<uint64_t>(distance);
|
||||
}
|
||||
|
||||
struct DmaTagView
|
||||
@@ -302,8 +331,7 @@ bool PS2Memory::initialize(size_t ramSize)
|
||||
m_path3MaskedFifo.clear();
|
||||
m_vif1PendingPath2ImageQwc = 0u;
|
||||
m_vif1PendingPath2DirectHl = false;
|
||||
m_timer0LastHostNs = 0;
|
||||
m_timer0FractionNs = 0;
|
||||
resetEeTimers();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -371,37 +399,121 @@ bool PS2Memory::initialize(size_t ramSize)
|
||||
}
|
||||
}
|
||||
|
||||
void PS2Memory::updateEeTimer0Counter()
|
||||
void PS2Memory::resetEeTimers() noexcept
|
||||
{
|
||||
const uint64_t nowNs = steadyClockNs();
|
||||
if (m_timer0LastHostNs == 0u)
|
||||
m_eeTimers = {};
|
||||
}
|
||||
|
||||
uint32_t PS2Memory::advanceEeTimers(uint64_t eeCycles) noexcept
|
||||
{
|
||||
if (eeCycles == 0u)
|
||||
{
|
||||
m_timer0LastHostNs = nowNs;
|
||||
return;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
const uint32_t mode = m_ioRegisters.count(kEeTimer0Mode) ? m_ioRegisters[kEeTimer0Mode] : 0u;
|
||||
if ((mode & kEeTimerModeCue) == 0u)
|
||||
uint32_t interruptMask = 0u;
|
||||
for (size_t index = 0; index < m_eeTimers.size(); ++index)
|
||||
{
|
||||
m_timer0LastHostNs = nowNs;
|
||||
m_timer0FractionNs = 0u;
|
||||
return;
|
||||
}
|
||||
EeTimer &timer = m_eeTimers[index];
|
||||
if ((timer.mode & kEeTimerModeCue) == 0u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint64_t elapsedNs = nowNs - m_timer0LastHostNs;
|
||||
m_timer0LastHostNs = nowNs;
|
||||
if (elapsedNs == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const uint64_t clockHz = kEeTimerClockHz[timer.mode & kEeTimerModeClksMask];
|
||||
const uint64_t wholeSeconds = eeCycles / kEeClockHz;
|
||||
const uint64_t remainingCycles = eeCycles % kEeClockHz;
|
||||
const uint64_t scaled = remainingCycles * clockHz + timer.clockRemainder;
|
||||
const uint64_t ticks = wholeSeconds * clockHz + scaled / kEeClockHz;
|
||||
timer.clockRemainder = scaled % kEeClockHz;
|
||||
if (ticks == 0u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint64_t scaled = elapsedNs * kEeTimer0TicksPerSecond + m_timer0FractionNs;
|
||||
const uint64_t ticks = scaled / kNanosecondsPerSecond;
|
||||
m_timer0FractionNs = scaled % kNanosecondsPerSecond;
|
||||
if (ticks != 0u)
|
||||
{
|
||||
m_ioRegisters[kEeTimer0Count] = m_ioRegisters[kEeTimer0Count] + static_cast<uint32_t>(ticks);
|
||||
const uint32_t oldCount = timer.count & 0xFFFFu;
|
||||
const uint32_t compare = timer.compare & 0xFFFFu;
|
||||
const uint64_t compareDistance = ticksUntilMatch(oldCount, compare);
|
||||
const uint64_t overflowDistance = 0x10000ull - oldCount;
|
||||
const bool zeroReturn = (timer.mode & kEeTimerModeZret) != 0u;
|
||||
const bool compareReached = ticks >= compareDistance;
|
||||
bool overflowReached = false;
|
||||
|
||||
if (zeroReturn)
|
||||
{
|
||||
overflowReached = ticks >= overflowDistance && overflowDistance <= compareDistance;
|
||||
if (compareReached)
|
||||
{
|
||||
const uint64_t remaining = ticks - compareDistance;
|
||||
timer.count = compare == 0u
|
||||
? static_cast<uint32_t>(remaining & 0xFFFFu)
|
||||
: static_cast<uint32_t>(remaining % compare);
|
||||
}
|
||||
else
|
||||
{
|
||||
timer.count = static_cast<uint32_t>((oldCount + ticks) & 0xFFFFu);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
overflowReached = ticks >= overflowDistance;
|
||||
timer.count = static_cast<uint32_t>((oldCount + ticks) & 0xFFFFu);
|
||||
}
|
||||
|
||||
if (compareReached && (timer.mode & kEeTimerModeCmpe) != 0u && (timer.mode & kEeTimerModeEquf) == 0u)
|
||||
{
|
||||
timer.mode |= kEeTimerModeEquf;
|
||||
interruptMask |= 1u << index;
|
||||
}
|
||||
if (overflowReached && (timer.mode & kEeTimerModeOvfe) != 0u && (timer.mode & kEeTimerModeOvff) == 0u)
|
||||
{
|
||||
timer.mode |= kEeTimerModeOvff;
|
||||
interruptMask |= 1u << index;
|
||||
}
|
||||
}
|
||||
return interruptMask;
|
||||
}
|
||||
|
||||
uint64_t PS2Memory::cyclesUntilNextEeTimerInterrupt() const noexcept
|
||||
{
|
||||
uint64_t nearest = std::numeric_limits<uint64_t>::max();
|
||||
for (const EeTimer &timer : m_eeTimers)
|
||||
{
|
||||
if ((timer.mode & kEeTimerModeCue) == 0u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t count = timer.count & 0xFFFFu;
|
||||
const uint32_t compare = timer.compare & 0xFFFFu;
|
||||
const uint64_t compareDistance = ticksUntilMatch(count, compare);
|
||||
const uint64_t overflowDistance = 0x10000ull - count;
|
||||
uint64_t eventTicks = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
if ((timer.mode & kEeTimerModeCmpe) != 0u &&
|
||||
(timer.mode & kEeTimerModeEquf) == 0u)
|
||||
{
|
||||
eventTicks = compareDistance;
|
||||
}
|
||||
const bool overflowCanOccur = (timer.mode & kEeTimerModeZret) == 0u ||
|
||||
overflowDistance <= compareDistance;
|
||||
if (overflowCanOccur &&
|
||||
(timer.mode & kEeTimerModeOvfe) != 0u &&
|
||||
(timer.mode & kEeTimerModeOvff) == 0u)
|
||||
{
|
||||
eventTicks = std::min(eventTicks, overflowDistance);
|
||||
}
|
||||
if (eventTicks == std::numeric_limits<uint64_t>::max())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint64_t clockHz = kEeTimerClockHz[timer.mode & kEeTimerModeClksMask];
|
||||
const uint64_t numerator = eventTicks * kEeClockHz - timer.clockRemainder;
|
||||
const uint64_t cycles = (numerator + clockHz - 1u) / clockHz;
|
||||
nearest = std::min(nearest, std::max<uint64_t>(1u, cycles));
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
bool PS2Memory::isScratchpad(uint32_t address) const
|
||||
@@ -991,22 +1103,36 @@ void PS2Memory::write128(uint32_t address, __m128i value)
|
||||
|
||||
bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
|
||||
{
|
||||
if (isEeTimer0Register(address))
|
||||
size_t timerIndex = 0u;
|
||||
uint32_t timerOffset = 0u;
|
||||
if (decodeEeTimerRegister(address, timerIndex, timerOffset))
|
||||
{
|
||||
if (address == kEeTimer0Count)
|
||||
EeTimer &timer = m_eeTimers[timerIndex];
|
||||
switch (timerOffset)
|
||||
{
|
||||
m_ioRegisters[address] = value;
|
||||
m_timer0LastHostNs = steadyClockNs();
|
||||
m_timer0FractionNs = 0u;
|
||||
return true;
|
||||
case kEeTimerCountOffset:
|
||||
timer.count = value & 0xFFFFu;
|
||||
timer.clockRemainder = 0u;
|
||||
break;
|
||||
case kEeTimerModeOffset:
|
||||
{
|
||||
const uint32_t previousMode = timer.mode;
|
||||
const uint32_t status = (previousMode & kEeTimerModeStatusMask) &~(value & kEeTimerModeStatusMask);
|
||||
timer.mode = (value & kEeTimerModeConfigMask) | status;
|
||||
if (((previousMode ^ timer.mode) & (kEeTimerModeClksMask | kEeTimerModeCue)) != 0u)
|
||||
{
|
||||
timer.clockRemainder = 0u;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
updateEeTimer0Counter();
|
||||
m_ioRegisters[address] = value;
|
||||
m_timer0LastHostNs = steadyClockNs();
|
||||
if (address == kEeTimer0Mode)
|
||||
{
|
||||
m_timer0FractionNs = 0u;
|
||||
case kEeTimerCompareOffset:
|
||||
timer.compare = value & 0xFFFFu;
|
||||
break;
|
||||
case kEeTimerHoldOffset:
|
||||
timer.hold = value & 0xFFFFu;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -2037,6 +2163,26 @@ int PS2Memory::pollDmaRegisters()
|
||||
|
||||
uint32_t PS2Memory::readIORegister(uint32_t address)
|
||||
{
|
||||
size_t timerIndex = 0u;
|
||||
uint32_t timerOffset = 0u;
|
||||
if (decodeEeTimerRegister(address, timerIndex, timerOffset))
|
||||
{
|
||||
const EeTimer &timer = m_eeTimers[timerIndex];
|
||||
switch (timerOffset)
|
||||
{
|
||||
case kEeTimerCountOffset:
|
||||
return timer.count & 0xFFFFu;
|
||||
case kEeTimerModeOffset:
|
||||
return timer.mode & (kEeTimerModeConfigMask | kEeTimerModeStatusMask);
|
||||
case kEeTimerCompareOffset:
|
||||
return timer.compare & 0xFFFFu;
|
||||
case kEeTimerHoldOffset:
|
||||
return timer.hold & 0xFFFFu;
|
||||
default:
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
if (isGsPrivReg(address))
|
||||
{
|
||||
// NB: unreachable from read8/16/32/64 today, same reasoning as the write
|
||||
@@ -2077,19 +2223,6 @@ uint32_t PS2Memory::readIORegister(uint32_t address)
|
||||
}
|
||||
if (address >= 0x10000000 && address < 0x10010000)
|
||||
{
|
||||
if (address >= 0x10000000 && address < 0x10000100)
|
||||
{
|
||||
if (isEeTimer0Register(address))
|
||||
{
|
||||
if (address == kEeTimer0Count)
|
||||
{
|
||||
updateEeTimer0Counter();
|
||||
}
|
||||
auto timerIt = m_ioRegisters.find(address);
|
||||
return timerIt != m_ioRegisters.end() ? timerIt->second : 0u;
|
||||
}
|
||||
}
|
||||
|
||||
if (address >= 0x10008000 && address < 0x1000F000)
|
||||
{
|
||||
if ((address & 0xFF) == 0x00)
|
||||
|
||||
@@ -1320,14 +1320,13 @@ bool PS2Runtime::dispatchGuestBranch(uint8_t *rdram,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kind == GuestBranchKind::Return)
|
||||
if (!isCall)
|
||||
{
|
||||
if (!hasFunction(targetPc))
|
||||
{
|
||||
reportMissingFunction(rdram, ctx, targetPc, sourcePc, kind, debugName);
|
||||
}
|
||||
|
||||
// Prevent nested dispatch.
|
||||
ctx->pc = targetPc;
|
||||
return false;
|
||||
}
|
||||
@@ -1362,11 +1361,6 @@ bool PS2Runtime::dispatchGuestBranch(uint8_t *rdram,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isCall)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ctx->pc == entryPc)
|
||||
{
|
||||
ctx->pc = fallthroughPc;
|
||||
|
||||
@@ -1042,7 +1042,7 @@ void register_code_generator_tests()
|
||||
"QFSRV should map to PS2_QFSRV with rs/rt ordering");
|
||||
});
|
||||
|
||||
tc.Run("PCPYLD and PEXEW use runtime helper macros", [](TestCase &t) {
|
||||
tc.Run("PCPYLD uses runtime helper macro", [](TestCase &t) {
|
||||
CodeGenerator gen({}, {});
|
||||
|
||||
Instruction pcpyld{};
|
||||
@@ -1057,18 +1057,45 @@ void register_code_generator_tests()
|
||||
std::string pcpyldOut = gen.translateInstruction(pcpyld);
|
||||
t.IsTrue(pcpyldOut.find("PS2_PCPYLD(GPR_VEC(ctx, 7), GPR_VEC(ctx, 8))") != std::string::npos,
|
||||
"PCPYLD should use PS2_PCPYLD helper");
|
||||
});
|
||||
|
||||
Instruction pexew{};
|
||||
pexew.isMMI = true;
|
||||
pexew.opcode = OPCODE_MMI;
|
||||
pexew.function = MMI_MMI2;
|
||||
pexew.sa = MMI2_PEXEW;
|
||||
pexew.rd = 9;
|
||||
pexew.rs = 10;
|
||||
tc.Run("Unary MMI permutations read their source from rt", [](TestCase &t) {
|
||||
CodeGenerator gen({}, {});
|
||||
|
||||
std::string pexewOut = gen.translateInstruction(pexew);
|
||||
t.IsTrue(pexewOut.find("PS2_PEXEW(GPR_VEC(ctx, 10))") != std::string::npos,
|
||||
"PEXEW should use PS2_PEXEW helper");
|
||||
struct UnaryMmiCase
|
||||
{
|
||||
const char *name;
|
||||
uint8_t function;
|
||||
uint8_t subfunction;
|
||||
};
|
||||
|
||||
const std::vector<UnaryMmiCase> cases = {
|
||||
{"PEXEH", MMI_MMI2, MMI2_PEXEH},
|
||||
{"PREVH", MMI_MMI2, MMI2_PREVH},
|
||||
{"PEXEW", MMI_MMI2, MMI2_PEXEW},
|
||||
{"PROT3W", MMI_MMI2, MMI2_PROT3W},
|
||||
{"PEXCH", MMI_MMI3, MMI3_PEXCH},
|
||||
{"PCPYH", MMI_MMI3, MMI3_PCPYH},
|
||||
{"PEXCW", MMI_MMI3, MMI3_PEXCW},
|
||||
};
|
||||
|
||||
for (const UnaryMmiCase &item : cases)
|
||||
{
|
||||
Instruction inst{};
|
||||
inst.isMMI = true;
|
||||
inst.opcode = OPCODE_MMI;
|
||||
inst.function = item.function;
|
||||
inst.sa = item.subfunction;
|
||||
inst.rd = 3;
|
||||
inst.rs = 4;
|
||||
inst.rt = 5;
|
||||
|
||||
const std::string out = gen.translateInstruction(inst);
|
||||
t.IsTrue(out.find("GPR_VEC(ctx, 5)") != std::string::npos,
|
||||
std::string(item.name) + " should read its source from rt");
|
||||
t.IsTrue(out.find("GPR_VEC(ctx, 4)") == std::string::npos,
|
||||
std::string(item.name) + " should not read its source from rs");
|
||||
}
|
||||
});
|
||||
|
||||
tc.Run("VU0 macro mappings cover all S1/S2 enums", [](TestCase &t) {
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
#include "Stubs/GS.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
@@ -194,7 +192,7 @@ void register_ps2_memory_tests()
|
||||
t.Equals(mem.translateAddress(PS2_SCRATCHPAD_ALIAS_BASE + 0x123u), 0x123u, "0xF000 scratchpad alias should translate to local offset");
|
||||
});
|
||||
|
||||
tc.Run("EE timer0 count advances while enabled and can be reset", [](TestCase &t)
|
||||
tc.Run("EE timer0 count advances from scheduler cycles and can be reset", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
|
||||
@@ -204,17 +202,111 @@ void register_ps2_memory_tests()
|
||||
constexpr uint32_t kTimer0Compare = 0x10000020u;
|
||||
|
||||
t.IsTrue(mem.writeIORegister(kTimer0Count, 0u), "timer count reset write should succeed");
|
||||
t.IsTrue(mem.writeIORegister(kTimer0Compare, 1u), "timer compare write should succeed");
|
||||
t.IsTrue(mem.writeIORegister(kTimer0Mode, 0x283u), "timer mode write should be retained");
|
||||
t.Equals(mem.readIORegister(kTimer0Mode), 0x283u, "timer mode should be readable");
|
||||
t.IsTrue(mem.writeIORegister(kTimer0Compare, 0xFFFFu), "timer compare write should succeed");
|
||||
t.IsTrue(mem.writeIORegister(kTimer0Mode, 0x82u), "timer mode write should be retained");
|
||||
t.Equals(mem.readIORegister(kTimer0Mode), 0x82u, "timer mode should be readable");
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(3));
|
||||
mem.advanceEeTimers(8u * 512u);
|
||||
const uint32_t firstCount = mem.readIORegister(kTimer0Count);
|
||||
t.IsTrue(firstCount > 0u, "enabled timer count should advance from host time");
|
||||
t.Equals(firstCount, 8u, "BUSCLK/256 should increment once per 512 EE cycles");
|
||||
|
||||
t.IsTrue(mem.writeIORegister(kTimer0Count, 0u), "timer count second reset should succeed");
|
||||
mem.advanceEeTimers(512u);
|
||||
const uint32_t resetCount = mem.readIORegister(kTimer0Count);
|
||||
t.IsTrue(resetCount <= firstCount, "timer reset should restart the count window");
|
||||
t.Equals(resetCount, 1u, "timer reset should restart the deterministic count window");
|
||||
});
|
||||
|
||||
tc.Run("EE timers 0 through 3 expose independent COUNT MODE and COMP registers", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
|
||||
|
||||
constexpr uint32_t kTimerBases[] = {
|
||||
0x10000000u,
|
||||
0x10000800u,
|
||||
0x10001000u,
|
||||
0x10001800u,
|
||||
};
|
||||
constexpr uint32_t kBusClockDiv256Cue = 0x82u;
|
||||
for (uint32_t index = 0u; index < 4u; ++index)
|
||||
{
|
||||
const uint32_t base = kTimerBases[index];
|
||||
t.IsTrue(mem.writeIORegister(base, 0x100u + index), "timer COUNT write should succeed");
|
||||
t.IsTrue(mem.writeIORegister(base + 0x10u, kBusClockDiv256Cue), "timer MODE write should succeed");
|
||||
t.IsTrue(mem.writeIORegister(base + 0x20u, 0x200u + index), "timer COMP write should succeed");
|
||||
}
|
||||
|
||||
mem.advanceEeTimers(512u);
|
||||
for (uint32_t index = 0u; index < 4u; ++index)
|
||||
{
|
||||
const uint32_t base = kTimerBases[index];
|
||||
t.Equals(mem.readIORegister(base), 0x101u + index, "each timer should advance its own COUNT");
|
||||
t.Equals(mem.readIORegister(base + 0x10u), kBusClockDiv256Cue, "each timer should retain MODE");
|
||||
t.Equals(mem.readIORegister(base + 0x20u), 0x200u + index, "each timer should retain COMP");
|
||||
}
|
||||
|
||||
t.IsTrue(mem.writeIORegister(kTimerBases[0] + 0x30u, 0x12345u), "Timer0 HOLD write should succeed");
|
||||
t.IsTrue(mem.writeIORegister(kTimerBases[1] + 0x30u, 0x23456u), "Timer1 HOLD write should succeed");
|
||||
t.Equals(mem.readIORegister(kTimerBases[0] + 0x30u), 0x2345u, "Timer0 HOLD should be 16-bit");
|
||||
t.Equals(mem.readIORegister(kTimerBases[1] + 0x30u), 0x3456u, "Timer1 HOLD should be 16-bit");
|
||||
});
|
||||
|
||||
tc.Run("EE Timer2 compare and overflow flags raise INTC_TIM2 and clear on write-one", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
|
||||
|
||||
constexpr uint32_t kTimer2Count = 0x10001000u;
|
||||
constexpr uint32_t kTimer2Mode = 0x10001010u;
|
||||
constexpr uint32_t kTimer2Compare = 0x10001020u;
|
||||
constexpr uint32_t kCue = 1u << 7u;
|
||||
constexpr uint32_t kCmpe = 1u << 8u;
|
||||
constexpr uint32_t kOvfe = 1u << 9u;
|
||||
constexpr uint32_t kEquf = 1u << 10u;
|
||||
constexpr uint32_t kOvff = 1u << 11u;
|
||||
constexpr uint32_t kBusClockDiv256 = 2u;
|
||||
|
||||
mem.writeIORegister(kTimer2Count, 0u);
|
||||
mem.writeIORegister(kTimer2Compare, 8u);
|
||||
mem.writeIORegister(kTimer2Mode, kBusClockDiv256 | kCue | kCmpe | kEquf | kOvff);
|
||||
|
||||
t.Equals(mem.advanceEeTimers(7u * 512u), 0u, "compare should not fire before COUNT reaches COMP");
|
||||
t.Equals(mem.readIORegister(kTimer2Count), 7u, "Timer2 should expose its live 16-bit count");
|
||||
t.Equals(mem.advanceEeTimers(512u), 1u << 2u, "Timer2 compare should raise the TIM2 interrupt bit");
|
||||
t.IsTrue((mem.readIORegister(kTimer2Mode) & kEquf) != 0u, "Timer2 compare should latch EQUF");
|
||||
|
||||
mem.writeIORegister(kTimer2Mode, mem.readIORegister(kTimer2Mode) | kEquf);
|
||||
t.IsTrue((mem.readIORegister(kTimer2Mode) & kEquf) == 0u, "writing one should clear EQUF");
|
||||
|
||||
mem.writeIORegister(kTimer2Count, 0xFFFFu);
|
||||
mem.writeIORegister(kTimer2Mode, kBusClockDiv256 | kCue | kOvfe | kOvff);
|
||||
t.Equals(mem.advanceEeTimers(512u), 1u << 2u, "Timer2 overflow should raise the TIM2 interrupt bit");
|
||||
t.Equals(mem.readIORegister(kTimer2Count), 0u, "Timer2 count should wrap at 16 bits");
|
||||
t.IsTrue((mem.readIORegister(kTimer2Mode) & kOvff) != 0u, "Timer2 overflow should latch OVFF");
|
||||
|
||||
mem.writeIORegister(kTimer2Mode, mem.readIORegister(kTimer2Mode) | kOvff);
|
||||
t.IsTrue((mem.readIORegister(kTimer2Mode) & kOvff) == 0u, "writing one should clear OVFF");
|
||||
});
|
||||
|
||||
tc.Run("EE timer zero-return clears COUNT on compare", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
|
||||
|
||||
constexpr uint32_t kTimer0Count = 0x10000000u;
|
||||
constexpr uint32_t kTimer0Mode = 0x10000010u;
|
||||
constexpr uint32_t kTimer0Compare = 0x10000020u;
|
||||
constexpr uint32_t kZret = 1u << 6u;
|
||||
constexpr uint32_t kCue = 1u << 7u;
|
||||
constexpr uint32_t kCmpe = 1u << 8u;
|
||||
constexpr uint32_t kEquf = 1u << 10u;
|
||||
|
||||
mem.writeIORegister(kTimer0Count, 0u);
|
||||
mem.writeIORegister(kTimer0Compare, 3u);
|
||||
mem.writeIORegister(kTimer0Mode, kZret | kCue | kCmpe | kEquf);
|
||||
|
||||
t.Equals(mem.advanceEeTimers(6u), 1u, "Timer0 compare should raise TIM0 after three BUSCLK ticks");
|
||||
t.Equals(mem.readIORegister(kTimer0Count), 0u, "ZRET should clear COUNT when it equals COMP");
|
||||
});
|
||||
|
||||
tc.Run("scratchpad alias accesses the same bytes as base", [](TestCase &t)
|
||||
|
||||
@@ -166,6 +166,13 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
std::atomic<uint32_t> gGuestJumpTargetCount{0u};
|
||||
|
||||
void testGuestJumpTargetHandler(uint8_t *, R5900Context *, PS2Runtime *)
|
||||
{
|
||||
gGuestJumpTargetCount.fetch_add(1u, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::atomic<uint32_t> gMpegStreamCallbackCount{0u};
|
||||
std::atomic<uint32_t> gMpegStreamCallbackMpeg{0u};
|
||||
std::atomic<uint32_t> gMpegStreamCallbackType{0u};
|
||||
@@ -399,6 +406,32 @@ void register_ps2_runtime_expansion_tests()
|
||||
"callee should still execute normally");
|
||||
});
|
||||
|
||||
tc.Run("dispatchGuestBranch jump returns to central dispatcher without nesting", [](TestCase &t)
|
||||
{
|
||||
PS2Runtime runtime;
|
||||
runtime.registerFunction(0x3400u, &testGuestJumpTargetHandler);
|
||||
gGuestJumpTargetCount.store(0u, std::memory_order_relaxed);
|
||||
|
||||
R5900Context ctx{};
|
||||
ctx.pc = 0x2000u;
|
||||
|
||||
const bool continuedInCaller = runtime.dispatchGuestBranch(
|
||||
nullptr,
|
||||
&ctx,
|
||||
0x3400u,
|
||||
0x2000u,
|
||||
0u,
|
||||
PS2Runtime::GuestBranchKind::IndirectJump,
|
||||
"test-jr");
|
||||
|
||||
t.IsFalse(continuedInCaller,
|
||||
"jump should stop the current generated wrapper");
|
||||
t.Equals(gGuestJumpTargetCount.load(std::memory_order_relaxed), 0u,
|
||||
"jump target must not execute on a nested host stack frame");
|
||||
t.Equals(ctx.pc, 0x3400u,
|
||||
"central dispatcher should receive the exact jump target");
|
||||
});
|
||||
|
||||
tc.Run("dispatchGuestBranch call returns false when callee transfers elsewhere", [](TestCase &t)
|
||||
{
|
||||
PS2Runtime runtime;
|
||||
|
||||
@@ -59,6 +59,17 @@ namespace
|
||||
constexpr uint32_t kEventWaitPc = 0x00160400u;
|
||||
constexpr uint32_t kEventResumePc = 0x00160410u;
|
||||
constexpr uint32_t kEventProducerPc = 0x00160420u;
|
||||
constexpr uint32_t kTimer2WaitPc = 0x00160500u;
|
||||
constexpr uint32_t kTimer2ResumePc = 0x00160510u;
|
||||
constexpr uint32_t kTimer2HandlerPc = 0x00160520u;
|
||||
|
||||
constexpr uint32_t kTimer2Count = 0x10001000u;
|
||||
constexpr uint32_t kTimer2Mode = 0x10001010u;
|
||||
constexpr uint32_t kTimer2Compare = 0x10001020u;
|
||||
constexpr uint32_t kTimerModeBusClockDiv256 = 2u;
|
||||
constexpr uint32_t kTimerModeCue = 1u << 7u;
|
||||
constexpr uint32_t kTimerModeCmpe = 1u << 8u;
|
||||
constexpr uint32_t kTimerModeEquf = 1u << 10u;
|
||||
|
||||
constexpr uint32_t kVSyncFlagAddr = 0x1800u;
|
||||
constexpr uint32_t kVSyncTickAddr = 0x1810u;
|
||||
@@ -71,6 +82,7 @@ namespace
|
||||
uint32_t g_vsyncFlag = 0;
|
||||
uint64_t g_vsyncTick = 0;
|
||||
uint64_t g_vsyncCsr = 0;
|
||||
std::atomic<bool> g_timer2Resumed{false};
|
||||
|
||||
void setRegU32(R5900Context &ctx, int reg, uint32_t value)
|
||||
{
|
||||
@@ -247,6 +259,41 @@ namespace
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
|
||||
void schedulerTimer2Handler(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_dispatchTrace.push_back(2);
|
||||
PS2Memory &memory = runtime->memory();
|
||||
memory.writeIORegister(kTimer2Mode, memory.readIORegister(kTimer2Mode) | kTimerModeEquf);
|
||||
runtime->eeScheduler().signalSemaphore(g_testSemaphoreId, true);
|
||||
ctx->pc = 0u;
|
||||
}
|
||||
|
||||
void schedulerTimer2Wait(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_dispatchTrace.push_back(1);
|
||||
EeScheduler &scheduler = runtime->eeScheduler();
|
||||
g_testSemaphoreId = scheduler.createSemaphore(0, 1, 0u, 0u);
|
||||
scheduler.addIrqHandler(false, 11u, kTimer2HandlerPc, true, 0u, 0u, 0u);
|
||||
|
||||
PS2Memory &memory = runtime->memory();
|
||||
memory.writeIORegister(kTimer2Count, 0u);
|
||||
memory.writeIORegister(kTimer2Compare, 8u);
|
||||
memory.writeIORegister(kTimer2Mode,
|
||||
kTimerModeBusClockDiv256 | kTimerModeCue | kTimerModeCmpe | kTimerModeEquf);
|
||||
|
||||
ctx->pc = kTimer2ResumePc;
|
||||
scheduler.waitSemaphore(g_testSemaphoreId);
|
||||
}
|
||||
|
||||
void schedulerTimer2Resume(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_dispatchTrace.push_back(3);
|
||||
g_resumedResult = getRegS32(*ctx, 2);
|
||||
g_timer2Resumed.store(true, std::memory_order_release);
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
}
|
||||
|
||||
void register_ps2_runtime_interrupt_tests()
|
||||
@@ -478,6 +525,52 @@ void register_ps2_runtime_interrupt_tests()
|
||||
}
|
||||
});
|
||||
|
||||
tc.Run("EE Timer2 compare IRQ wakes a DelayThread-style semaphore wait", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
t.IsTrue(env.runtime.memory().initialize(), "runtime memory initialize should succeed");
|
||||
env.runtime.registerFunction(kTimer2WaitPc, schedulerTimer2Wait);
|
||||
env.runtime.registerFunction(kTimer2ResumePc, schedulerTimer2Resume);
|
||||
env.runtime.registerFunction(kTimer2HandlerPc, schedulerTimer2Handler);
|
||||
|
||||
g_dispatchTrace.clear();
|
||||
g_resumedResult = -1;
|
||||
g_timer2Resumed.store(false, std::memory_order_release);
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = kTimer2WaitPc;
|
||||
std::atomic<bool> schedulerThrew{false};
|
||||
std::thread gameThread([&]()
|
||||
{
|
||||
try
|
||||
{
|
||||
env.runtime.eeScheduler().reset(env.rdram.data(), mainContext);
|
||||
env.runtime.eeScheduler().run();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
schedulerThrew.store(true, std::memory_order_release);
|
||||
}
|
||||
});
|
||||
|
||||
const bool resumed = waitUntil([]()
|
||||
{
|
||||
return g_timer2Resumed.load(std::memory_order_acquire);
|
||||
}, std::chrono::milliseconds(150));
|
||||
if (!resumed)
|
||||
{
|
||||
env.runtime.requestStop();
|
||||
}
|
||||
gameThread.join();
|
||||
|
||||
t.IsTrue(resumed, "Timer2 compare should dispatch INTC_TIM2 and wake the semaphore waiter");
|
||||
t.IsFalse(schedulerThrew.load(std::memory_order_acquire), "Timer2 IRQ path should not throw");
|
||||
const std::vector<int> expected{1, 2, 3};
|
||||
t.IsTrue(g_dispatchTrace == expected,
|
||||
"Timer2 flow should run wait, interrupt handler, then the resumed thread");
|
||||
t.Equals(g_resumedResult, g_testSemaphoreId,
|
||||
"the Timer2 handler should hand the semaphore directly to the waiter");
|
||||
});
|
||||
|
||||
tc.Run("scheduler stop wakes an idle VSync wait without a timeout", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
|
||||
@@ -1049,6 +1049,46 @@ void register_ps2_runtime_kernel_tests()
|
||||
t.Equals(setupSp & 0xFu, 0u, "SetupThread should always return a 16-byte aligned stack pointer");
|
||||
});
|
||||
|
||||
tc.Run("SetupThread exposes stable main stack metadata through ReferThreadStatus", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
constexpr uint32_t kInitialLoaderSp = PS2_RAM_SIZE - 0x10u;
|
||||
constexpr uint32_t kMainStackSize = 0x00020000u;
|
||||
constexpr uint32_t kExpectedStack = PS2_RAM_SIZE - kMainStackSize;
|
||||
constexpr uint32_t kMainGp = 0x0036A7F0u;
|
||||
|
||||
env.ctx.pc = 0x00100000u;
|
||||
setRegU32(env.ctx, 29, kInitialLoaderSp);
|
||||
setRegU32(env.ctx, 4, kMainGp);
|
||||
setRegU32(env.ctx, 5, 0xFFFFFFFFu);
|
||||
setRegU32(env.ctx, 6, kMainStackSize);
|
||||
t.IsTrue(callSyscall(0x3Cu, env.rdram.data(), &env.ctx, &env.runtime),
|
||||
"SetupThread syscall should dispatch");
|
||||
t.Equals(::getRegU32(&env.ctx, 2), kExpectedStack,
|
||||
"automatic main stack should start below the reserved top-of-RDRAM area");
|
||||
|
||||
// ReferThreadStatus can be called after many nested frames have moved $sp.
|
||||
// It must report the initial stack recorded by SetupThread, not this live snapshot.
|
||||
constexpr uint32_t kTransientSp = kExpectedStack - 0x80u;
|
||||
setRegU32(env.ctx, 29, kTransientSp);
|
||||
setRegU32(env.ctx, 4, 0u);
|
||||
setRegU32(env.ctx, 5, K_STATUS_ADDR);
|
||||
t.IsTrue(callSyscall(0x30u, env.rdram.data(), &env.ctx, &env.runtime),
|
||||
"ReferThreadStatus syscall should dispatch");
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "ReferThreadStatus should accept the current-thread id alias");
|
||||
|
||||
EeThreadStatusAbi status{};
|
||||
std::memcpy(&status, env.rdram.data() + K_STATUS_ADDR, sizeof(status));
|
||||
t.Equals(status.stack, kExpectedStack,
|
||||
"main thread status must expose SetupThread's stable initial stack");
|
||||
t.Equals(status.stack_size, static_cast<int32_t>(kMainStackSize),
|
||||
"main thread status must preserve SetupThread's stack size");
|
||||
t.Equals(status.gp_reg, kMainGp,
|
||||
"main thread status must preserve SetupThread's global pointer");
|
||||
t.IsTrue(status.stack != kInitialLoaderSp && status.stack != kTransientSp,
|
||||
"main thread status must never expose a live stack-pointer snapshot");
|
||||
});
|
||||
|
||||
tc.Run("OSD config2 syscalls round-trip extended config", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "MiniTest.h"
|
||||
#include "ps2_runtime.h"
|
||||
#include "ps2_iop_host.h"
|
||||
#include "ps2_iop_transport.h"
|
||||
#include "ps2_syscalls.h"
|
||||
#include "ps2_stubs.h"
|
||||
#include "Kernel/Stubs/SIF.h"
|
||||
#include "runtime/ee_scheduler.h"
|
||||
|
||||
#include <array>
|
||||
@@ -197,6 +199,79 @@ void register_ps2_sif_dma_tests()
|
||||
t.IsTrue(getRegS32(env.ctx, 2) < 0, "sceSifDmaStat should be negative when transfer is complete");
|
||||
});
|
||||
|
||||
tc.Run("IOP heap DMA uses private backing instead of aliasing EE RDRAM", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
|
||||
constexpr uint32_t kDescAddr = 0x00020040u;
|
||||
constexpr uint32_t kSrcAddr = 0x00020140u;
|
||||
constexpr uint32_t kRoundTripAddr = 0x00020240u;
|
||||
constexpr uint32_t kFormerAliasAddr = 0x01A53880u;
|
||||
constexpr uint32_t kIopBlockSize = 0x880u;
|
||||
|
||||
std::array<uint8_t, 32> payload{};
|
||||
for (size_t i = 0; i < payload.size(); ++i)
|
||||
{
|
||||
payload[i] = static_cast<uint8_t>(0x80u + i);
|
||||
}
|
||||
std::memcpy(env.rdram.data() + kSrcAddr, payload.data(), payload.size());
|
||||
std::memset(env.rdram.data() + kRoundTripAddr, 0, payload.size());
|
||||
std::memset(env.rdram.data() + kFormerAliasAddr, 0x5Au, payload.size());
|
||||
|
||||
setRegU32(env.ctx, 4, kIopBlockSize);
|
||||
ps2_stubs::sceSifAllocIopHeap(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
const uint32_t iopAddress = ::getRegU32(&env.ctx, 2);
|
||||
t.IsTrue(iopAddress >= PS2_RAM_SIZE,
|
||||
"sceSifAllocIopHeap should return an address outside EE RDRAM");
|
||||
|
||||
Ps2SifDmaTransfer desc{
|
||||
kSrcAddr,
|
||||
iopAddress,
|
||||
static_cast<int32_t>(payload.size()),
|
||||
0};
|
||||
std::memcpy(env.rdram.data() + kDescAddr, &desc, sizeof(desc));
|
||||
setRegU32(env.ctx, 4, kDescAddr);
|
||||
setRegU32(env.ctx, 5, 1u);
|
||||
ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.IsTrue(getRegS32(env.ctx, 2) > 0,
|
||||
"EE-to-IOP DMA should accept a private IOP heap destination");
|
||||
|
||||
const std::array<uint8_t, 32> aliasSentinel{
|
||||
0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A,
|
||||
0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A,
|
||||
0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A,
|
||||
0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A};
|
||||
t.IsTrue(std::memcmp(env.rdram.data() + kFormerAliasAddr,
|
||||
aliasSentinel.data(), aliasSentinel.size()) == 0,
|
||||
"IOP DMA must not overwrite the old 0x01A00000 EE alias range");
|
||||
|
||||
PS2IopHostAdapter host(env.runtime);
|
||||
auto scope = host.enterCall(&env.ctx, env.rdram.data());
|
||||
uint32_t normalized = 0u;
|
||||
std::array<uint8_t, 32> hostReadback{};
|
||||
t.IsTrue(host.normalizeGuestAddress(iopAddress, normalized) &&
|
||||
normalized == iopAddress,
|
||||
"IOP modules should preserve private IOP heap addresses");
|
||||
t.IsTrue(host.readGuest(iopAddress, hostReadback.data(), hostReadback.size()) &&
|
||||
hostReadback == payload,
|
||||
"IOP modules should read the private heap backing");
|
||||
|
||||
desc = {
|
||||
iopAddress,
|
||||
kRoundTripAddr,
|
||||
static_cast<int32_t>(payload.size()),
|
||||
0};
|
||||
std::memcpy(env.rdram.data() + kDescAddr, &desc, sizeof(desc));
|
||||
setRegU32(env.ctx, 4, kDescAddr);
|
||||
setRegU32(env.ctx, 5, 1u);
|
||||
ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.IsTrue(getRegS32(env.ctx, 2) > 0,
|
||||
"IOP-to-EE DMA should accept a private IOP heap source");
|
||||
t.IsTrue(std::memcmp(env.rdram.data() + kRoundTripAddr,
|
||||
payload.data(), payload.size()) == 0,
|
||||
"IOP-to-EE DMA should round-trip the payload");
|
||||
});
|
||||
|
||||
tc.Run("isceSifSetDma and isceSifSetDChain alias the SIF DMA helpers", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
@@ -438,6 +513,8 @@ void register_ps2_sif_dma_tests()
|
||||
t.IsTrue(getRegS32(env.ctx, 2) > 0, "sceSifSetDma should succeed for the SJX transport");
|
||||
t.Equals(env.rdram[kEeWorkAddr + 0x11u], static_cast<uint8_t>(0u),
|
||||
"SJX DMA ack should rewrite the response line to room so EE recycles the chunk");
|
||||
t.Equals(readGuestU32(env.rdram.data(), kEeWorkAddr + 0x14u), 0x12345678u,
|
||||
"SJX DMA ack should translate the remote handle back to the EE callback object");
|
||||
t.Equals(readGuestU32(env.rdram.data(), kEeWorkAddr + kWorkLen - sizeof(uint32_t)), 2u,
|
||||
"SJX DMA ack should still advance the EE footer ticket");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user