mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-21 10:19:04 +02:00
Each due alarm or vtimer queues its id next to a pending interrupt, which pops it when it runs. An alarm cancelled with its interrupt pending was never popped, so every later alarm interrupt found the cancelled one at the front and was dropped: no alarm ran again. Deleting a vtimer erased its id instead, leaving the last pending interrupt with an empty list. Both now pop a timer that's gone when its interrupt comes, as threads/alarm/pending and threads/vtimers/pending show the PSP still runs the rest, in order. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
494 lines
14 KiB
C++
494 lines
14 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include <algorithm>
|
|
#include <list>
|
|
#include "Common/Serialize/Serializer.h"
|
|
#include "Common/Serialize/SerializeFuncs.h"
|
|
#include "Common/Serialize/SerializeList.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/MemMapHelpers.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/HLE/sceKernel.h"
|
|
#include "Core/HLE/sceKernelInterrupt.h"
|
|
#include "Core/HLE/sceKernelMemory.h"
|
|
#include "Core/HLE/sceKernelVTimer.h"
|
|
#include "Core/HLE/HLE.h"
|
|
#include "Core/HLE/ErrorCodes.h"
|
|
|
|
static int vtimerTimer = -1;
|
|
static SceUID runningVTimer = 0;
|
|
static std::list<SceUID> vtimers;
|
|
|
|
struct NativeVTimer {
|
|
SceSize_le size;
|
|
char name[KERNELOBJECT_MAX_NAME_LENGTH+1];
|
|
s32_le active;
|
|
u64_le base;
|
|
u64_le current;
|
|
u64_le schedule;
|
|
u32_le handlerAddr;
|
|
u32_le commonAddr;
|
|
};
|
|
|
|
struct VTimer : public KernelObject {
|
|
const char *GetName() override { return nvt.name; }
|
|
const char *GetTypeName() override { return GetStaticTypeName(); }
|
|
static const char *GetStaticTypeName() { return "VTimer"; }
|
|
static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_UNKNOWN_VTID; }
|
|
static int GetStaticIDType() { return SCE_KERNEL_TMID_VTimer; }
|
|
int GetIDType() const override { return SCE_KERNEL_TMID_VTimer; }
|
|
|
|
void DoState(PointerWrap &p) override {
|
|
auto s = p.Section("VTimer", 1, 2);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, nvt);
|
|
TerminateLoadedCString(p, nvt.name);
|
|
if (s < 2) {
|
|
u32 memoryPtr;
|
|
Do(p, memoryPtr);
|
|
}
|
|
}
|
|
|
|
NativeVTimer nvt{};
|
|
};
|
|
|
|
KernelObject *__KernelVTimerObject() {
|
|
return new VTimer();
|
|
}
|
|
|
|
static u64 __getVTimerRunningTime(const VTimer *vt) {
|
|
if (vt->nvt.active == 0)
|
|
return 0;
|
|
|
|
return CoreTiming::GetGlobalTimeUs() - vt->nvt.base;
|
|
}
|
|
|
|
static u64 __getVTimerCurrentTime(VTimer* vt) {
|
|
return vt->nvt.current + __getVTimerRunningTime(vt);
|
|
}
|
|
|
|
static int __KernelCancelVTimer(SceUID id) {
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(id, error);
|
|
if (!vt) {
|
|
return error;
|
|
}
|
|
|
|
CoreTiming::UnscheduleEvent(vtimerTimer, id);
|
|
vt->nvt.handlerAddr = 0;
|
|
return 0;
|
|
}
|
|
|
|
static void __KernelScheduleVTimer(VTimer *vt, u64 schedule) {
|
|
CoreTiming::UnscheduleEvent(vtimerTimer, vt->GetUID());
|
|
|
|
vt->nvt.schedule = schedule;
|
|
|
|
if (vt->nvt.active == 1 && vt->nvt.handlerAddr != 0) {
|
|
// The "real" base is base + current. But when setting the time, base is important.
|
|
// The schedule is relative to those.
|
|
u64 cyclesIntoFuture;
|
|
if (schedule < 250) {
|
|
schedule = 250;
|
|
}
|
|
s64 goalUs = (u64)vt->nvt.base + schedule - (u64)vt->nvt.current;
|
|
s64 minGoalUs = CoreTiming::GetGlobalTimeUs() + 250;
|
|
if (goalUs < minGoalUs) {
|
|
cyclesIntoFuture = usToCycles(250);
|
|
} else {
|
|
cyclesIntoFuture = usToCycles(goalUs - CoreTiming::GetGlobalTimeUs());
|
|
}
|
|
|
|
CoreTiming::ScheduleEvent(cyclesIntoFuture, vtimerTimer, vt->GetUID());
|
|
}
|
|
}
|
|
|
|
static void __rescheduleVTimer(SceUID id, u32 delay) {
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(id, error);
|
|
|
|
if (error)
|
|
return;
|
|
|
|
__KernelScheduleVTimer(vt, vt->nvt.schedule + delay);
|
|
}
|
|
|
|
class VTimerIntrHandler : public IntrHandler
|
|
{
|
|
static const int HANDLER_STACK_SPACE = 48;
|
|
|
|
public:
|
|
VTimerIntrHandler() : IntrHandler(PSP_SYSTIMER1_INTR) {}
|
|
|
|
bool run(PendingInterrupt &pend) override {
|
|
u32 error;
|
|
if (vtimers.empty()) {
|
|
return false;
|
|
}
|
|
SceUID vtimerID = vtimers.front();
|
|
|
|
VTimer *vtimer = kernelObjects.Get<VTimer>(vtimerID, error);
|
|
if (error) {
|
|
// Deleted while its interrupt was pending. handleResult won't run, so it's popped here.
|
|
WARN_LOG(Log::sceKernel, "Ignoring deleted vtimer %08x", vtimerID);
|
|
vtimers.pop_front();
|
|
return false;
|
|
}
|
|
|
|
// Reserve some stack space for arguments.
|
|
u32 argArea = currentMIPS->r[MIPS_REG_SP];
|
|
currentMIPS->r[MIPS_REG_SP] -= HANDLER_STACK_SPACE;
|
|
if (Memory::IsValidRange(argArea - HANDLER_STACK_SPACE, HANDLER_STACK_SPACE)) {
|
|
Memory::WriteUnchecked_U64(vtimer->nvt.schedule, argArea - 16);
|
|
Memory::WriteUnchecked_U64(__getVTimerCurrentTime(vtimer), argArea - 8);
|
|
}
|
|
|
|
currentMIPS->pc = vtimer->nvt.handlerAddr;
|
|
currentMIPS->r[MIPS_REG_A0] = vtimer->GetUID();
|
|
currentMIPS->r[MIPS_REG_A1] = argArea - 16;
|
|
currentMIPS->r[MIPS_REG_A2] = argArea - 8;
|
|
currentMIPS->r[MIPS_REG_A3] = vtimer->nvt.commonAddr;
|
|
|
|
runningVTimer = vtimerID;
|
|
|
|
return true;
|
|
}
|
|
|
|
void handleResult(PendingInterrupt &pend) override {
|
|
u32 result = currentMIPS->r[MIPS_REG_V0];
|
|
|
|
currentMIPS->r[MIPS_REG_SP] += HANDLER_STACK_SPACE;
|
|
|
|
int vtimerID = vtimers.front();
|
|
vtimers.pop_front();
|
|
|
|
runningVTimer = 0;
|
|
|
|
if (result == 0)
|
|
__KernelCancelVTimer(vtimerID);
|
|
else
|
|
__rescheduleVTimer(vtimerID, result);
|
|
}
|
|
};
|
|
|
|
static void __KernelTriggerVTimer(u64 userdata, int cyclesLate) {
|
|
SceUID uid = (SceUID) userdata;
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (vt) {
|
|
vtimers.push_back(uid);
|
|
__TriggerInterrupt(PSP_INTR_IMMEDIATE, PSP_SYSTIMER1_INTR);
|
|
}
|
|
}
|
|
|
|
void __KernelVTimerDoState(PointerWrap &p) {
|
|
auto s = p.Section("sceKernelVTimer", 1, 2);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, vtimerTimer);
|
|
Do(p, vtimers);
|
|
CoreTiming::RestoreRegisterEvent(vtimerTimer, "VTimer", __KernelTriggerVTimer);
|
|
|
|
if (s >= 2)
|
|
Do(p, runningVTimer);
|
|
else
|
|
runningVTimer = 0;
|
|
}
|
|
|
|
void __KernelVTimerInit() {
|
|
vtimers.clear();
|
|
__RegisterIntrHandler(PSP_SYSTIMER1_INTR, new VTimerIntrHandler());
|
|
vtimerTimer = CoreTiming::RegisterEvent("VTimer", __KernelTriggerVTimer);
|
|
|
|
// Intentionally starts at 0. This explains the behavior where 0 is treated differently outside a timer.
|
|
runningVTimer = 0;
|
|
}
|
|
|
|
u32 sceKernelCreateVTimer(const char *name, u32 optParamAddr) {
|
|
if (!name) {
|
|
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ERROR, "invalid name");
|
|
}
|
|
|
|
VTimer *vtimer = new VTimer;
|
|
SceUID id = kernelObjects.Create(vtimer);
|
|
|
|
memset(&vtimer->nvt, 0, sizeof(NativeVTimer));
|
|
vtimer->nvt.size = sizeof(NativeVTimer);
|
|
strncpy(vtimer->nvt.name, name, KERNELOBJECT_MAX_NAME_LENGTH);
|
|
vtimer->nvt.name[KERNELOBJECT_MAX_NAME_LENGTH] = '\0';
|
|
|
|
if (optParamAddr != 0) {
|
|
u32 size = Memory::ReadOrException_U32(optParamAddr);
|
|
if (size > 4)
|
|
WARN_LOG_REPORT_ONCE(vtimeropt, Log::sceKernel, "sceKernelCreateVTimer(%s) unsupported options parameter, size = %d", name, size);
|
|
}
|
|
|
|
return hleLogDebug(Log::sceKernel, id);
|
|
}
|
|
|
|
u32 sceKernelDeleteVTimer(SceUID uid) {
|
|
u32 error;
|
|
VTimer* vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (!vt) {
|
|
return hleLogError(Log::sceKernel, error, "bad timer ID");
|
|
}
|
|
|
|
// Its entry in vtimers stays: each one belongs to a pending interrupt, which pops it when it runs
|
|
// (or right away, finding the timer gone), so erasing it here would hand that interrupt to the
|
|
// next timer's entry, or to an empty list if the handler is deleting its own timer.
|
|
|
|
return hleLogDebugOrError(Log::sceKernel, kernelObjects.Destroy<VTimer>(uid));
|
|
}
|
|
|
|
u32 sceKernelGetVTimerBase(SceUID uid, u32 baseClockAddr) {
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (!vt) {
|
|
return hleLogError(Log::sceKernel, error, "bad timer ID");
|
|
}
|
|
|
|
if (Memory::IsValid4AlignedRange(baseClockAddr, 8)) {
|
|
Memory::WriteUnchecked_U64(vt->nvt.base, baseClockAddr);
|
|
}
|
|
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
u64 sceKernelGetVTimerBaseWide(SceUID uid) {
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (!vt) {
|
|
return hleLogError(Log::sceKernel, -1, "bad timer ID");
|
|
}
|
|
|
|
return hleLogDebug(Log::sceKernel, vt->nvt.base);
|
|
}
|
|
|
|
u32 sceKernelGetVTimerTime(SceUID uid, u32 timeClockAddr) {
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (!vt) {
|
|
return hleLogError(Log::sceKernel, error, "bad timer ID");
|
|
}
|
|
|
|
u64 time = __getVTimerCurrentTime(vt);
|
|
if (Memory::IsValid4AlignedRange(timeClockAddr, 8)) {
|
|
Memory::WriteUnchecked_U64(time, timeClockAddr);
|
|
}
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
u64 sceKernelGetVTimerTimeWide(SceUID uid) {
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (!vt) {
|
|
// Strange error code! But, it matches tests.
|
|
return hleLogError(Log::sceKernel, -1, "bad timer ID. error=%08x", error);
|
|
}
|
|
|
|
u64 time = __getVTimerCurrentTime(vt);
|
|
return hleLogDebug(Log::sceKernel, time);
|
|
}
|
|
|
|
static u64 __KernelSetVTimer(VTimer *vt, u64 time) {
|
|
u64 current = __getVTimerCurrentTime(vt);
|
|
vt->nvt.current = time - __getVTimerRunningTime(vt);
|
|
|
|
// Run if we're now passed the schedule.
|
|
__KernelScheduleVTimer(vt, vt->nvt.schedule);
|
|
|
|
return current;
|
|
}
|
|
|
|
u32 sceKernelSetVTimerTime(SceUID uid, u32 timeClockAddr) {
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (!vt) {
|
|
return hleLogError(Log::sceKernel, error, "bad timer ID");
|
|
}
|
|
|
|
if (Memory::IsValid4AlignedRange(timeClockAddr, 8)) {
|
|
u64 time = Memory::ReadUnchecked_U64(timeClockAddr);
|
|
Memory::WriteUnchecked_U64(__KernelSetVTimer(vt, time), timeClockAddr);
|
|
} else {
|
|
_dbg_assert_(false);
|
|
}
|
|
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
u64 sceKernelSetVTimerTimeWide(SceUID uid, u64 timeClock) {
|
|
if (__IsInInterrupt()) {
|
|
return hleLogWarning(Log::sceKernel, -1, "in interrupt");
|
|
}
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (!vt) {
|
|
// Strange error code! But, it matches tests.
|
|
return hleLogError(Log::sceKernel, -1, "bad timer ID. error=%08x", error);
|
|
}
|
|
|
|
return hleLogDebug(Log::sceKernel, __KernelSetVTimer(vt, timeClock));
|
|
}
|
|
|
|
static void __startVTimer(VTimer *vt) {
|
|
vt->nvt.active = 1;
|
|
vt->nvt.base = CoreTiming::GetGlobalTimeUs();
|
|
|
|
if (vt->nvt.handlerAddr != 0)
|
|
__KernelScheduleVTimer(vt, vt->nvt.schedule);
|
|
}
|
|
|
|
u32 sceKernelStartVTimer(SceUID uid) {
|
|
hleEatCycles(12200);
|
|
if (uid == runningVTimer) {
|
|
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_VTID, "invalid vtimer - can't be runningVTimer");
|
|
}
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (!vt) {
|
|
return hleLogError(Log::sceKernel, error, "bad timer ID");
|
|
}
|
|
|
|
if (vt->nvt.active) {
|
|
return hleLogDebug(Log::sceKernel, 1);
|
|
}
|
|
|
|
__startVTimer(vt);
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
static void __stopVTimer(VTimer *vt) {
|
|
// This increases (__getVTimerCurrentTime includes nvt.current.)
|
|
vt->nvt.current = __getVTimerCurrentTime(vt);
|
|
vt->nvt.active = 0;
|
|
vt->nvt.base = 0;
|
|
}
|
|
|
|
u32 sceKernelStopVTimer(SceUID uid) {
|
|
if (uid == runningVTimer) {
|
|
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_VTID, "invalid vtimer - can't be runningVTimer");
|
|
}
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (!vt) {
|
|
return hleLogError(Log::sceKernel, error, "bad timer ID");
|
|
}
|
|
|
|
if (vt->nvt.active == 0) {
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
__stopVTimer(vt);
|
|
return hleLogDebug(Log::sceKernel, 1);
|
|
}
|
|
|
|
u32 sceKernelSetVTimerHandler(SceUID uid, u32 scheduleAddr, u32 handlerFuncAddr, u32 commonAddr) {
|
|
hleEatCycles(900);
|
|
if (uid == runningVTimer) {
|
|
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_VTID, "invalid vtimer - can't be runningVTimer");
|
|
}
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (!vt) {
|
|
return hleLogError(Log::sceKernel, error, "bad timer ID");
|
|
}
|
|
|
|
hleEatCycles(2000);
|
|
u64 schedule;
|
|
if (!Memory::IsValidAddress(scheduleAddr)) {
|
|
ERROR_LOG(Log::sceKernel, "sceKernelSetVTimerHandler: invalid schedule address %08x", scheduleAddr);
|
|
schedule = 0;
|
|
} else {
|
|
schedule = Memory::ReadUnchecked_U64(scheduleAddr);
|
|
}
|
|
|
|
vt->nvt.handlerAddr = handlerFuncAddr;
|
|
if (handlerFuncAddr) {
|
|
vt->nvt.commonAddr = commonAddr;
|
|
__KernelScheduleVTimer(vt, schedule);
|
|
} else {
|
|
__KernelScheduleVTimer(vt, vt->nvt.schedule);
|
|
}
|
|
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
u32 sceKernelSetVTimerHandlerWide(SceUID uid, u64 schedule, u32 handlerFuncAddr, u32 commonAddr) {
|
|
hleEatCycles(900);
|
|
if (uid == runningVTimer) {
|
|
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_VTID, "invalid vtimer - can't be runningVTimer");
|
|
}
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (!vt) {
|
|
return hleLogError(Log::sceKernel, error, "bad timer ID");
|
|
}
|
|
|
|
vt->nvt.handlerAddr = handlerFuncAddr;
|
|
if (handlerFuncAddr) {
|
|
vt->nvt.commonAddr = commonAddr;
|
|
__KernelScheduleVTimer(vt, schedule);
|
|
} else {
|
|
__KernelScheduleVTimer(vt, vt->nvt.schedule);
|
|
}
|
|
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
u32 sceKernelCancelVTimerHandler(SceUID uid) {
|
|
if (uid == runningVTimer) {
|
|
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_VTID, "invalid vtimer - can't be runningVTimer");
|
|
}
|
|
|
|
//__cancelVTimer checks if uid is valid
|
|
return hleLogDebugOrError(Log::sceKernel, __KernelCancelVTimer(uid));
|
|
}
|
|
|
|
u32 sceKernelReferVTimerStatus(SceUID uid, u32 statusAddr) {
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
if (!vt) {
|
|
return hleLogError(Log::sceKernel, error, "bad timer ID");
|
|
}
|
|
|
|
if (Memory::IsValid4AlignedAddress(statusAddr)) {
|
|
NativeVTimer status = vt->nvt;
|
|
u32 size = Memory::ReadUnchecked_U32(statusAddr);
|
|
status.current = __getVTimerCurrentTime(vt);
|
|
Memory::Memcpy(statusAddr, &status, std::min(size, (u32)sizeof(status)), "VTimerStatus");
|
|
}
|
|
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
// Not sure why this is exposed...
|
|
void _sceKernelReturnFromTimerHandler() {
|
|
ERROR_LOG_REPORT(Log::sceKernel,"_sceKernelReturnFromTimerHandler - should not be called!");
|
|
return hleNoLogVoid();
|
|
}
|