mirror of
https://github.com/libretro/ppsspp.git
synced 2024-12-15 13:09:25 +00:00
520 lines
13 KiB
C++
520 lines
13 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 "Core/CoreTiming.h"
|
|
#include "Core/Reporting.h"
|
|
#include "sceKernel.h"
|
|
#include "sceKernelInterrupt.h"
|
|
#include "sceKernelMemory.h"
|
|
#include "sceKernelVTimer.h"
|
|
#include "HLE.h"
|
|
#include "ChunkFile.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() {return nvt.name;}
|
|
const char *GetTypeName() {return "VTimer";}
|
|
static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_UNKNOWN_VTID; }
|
|
static int GetStaticIDType() { return SCE_KERNEL_TMID_VTimer; }
|
|
int GetIDType() const { return SCE_KERNEL_TMID_VTimer; }
|
|
|
|
virtual void DoState(PointerWrap &p) {
|
|
auto s = p.Section("VTimer", 1, 2);
|
|
if (!s)
|
|
return;
|
|
|
|
p.Do(nvt);
|
|
if (s < 2) {
|
|
u32 memoryPtr;
|
|
p.Do(memoryPtr);
|
|
}
|
|
}
|
|
|
|
NativeVTimer nvt;
|
|
};
|
|
|
|
KernelObject *__KernelVTimerObject() {
|
|
return new VTimer;
|
|
}
|
|
|
|
u64 __getVTimerRunningTime(VTimer *vt) {
|
|
if (vt->nvt.active == 0)
|
|
return 0;
|
|
|
|
return CoreTiming::GetGlobalTimeUs() - vt->nvt.base;
|
|
}
|
|
|
|
u64 __getVTimerCurrentTime(VTimer* vt) {
|
|
return vt->nvt.current + __getVTimerRunningTime(vt);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
// It seems like the minimum is approximately 200us?
|
|
if (schedule < __getVTimerCurrentTime(vt))
|
|
cyclesIntoFuture = usToCycles(200);
|
|
else {
|
|
u64 goalUs = vt->nvt.base + schedule - vt->nvt.current;
|
|
if (goalUs < CoreTiming::GetGlobalTimeUs())
|
|
cyclesIntoFuture = usToCycles(200);
|
|
else
|
|
cyclesIntoFuture = usToCycles(goalUs - CoreTiming::GetGlobalTimeUs());
|
|
}
|
|
|
|
CoreTiming::ScheduleEvent(cyclesIntoFuture, vtimerTimer, vt->GetUID());
|
|
}
|
|
}
|
|
|
|
void __rescheduleVTimer(SceUID id, u32 delay) {
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(id, error);
|
|
|
|
if (error)
|
|
return;
|
|
|
|
if (delay < 100)
|
|
delay = 100;
|
|
|
|
__KernelScheduleVTimer(vt, vt->nvt.schedule + delay);
|
|
}
|
|
|
|
class VTimerIntrHandler : public IntrHandler
|
|
{
|
|
static const int HANDLER_STACK_SPACE = 48;
|
|
|
|
public:
|
|
VTimerIntrHandler() : IntrHandler(PSP_SYSTIMER1_INTR) {}
|
|
|
|
virtual bool run(PendingInterrupt &pend) {
|
|
u32 error;
|
|
SceUID vtimerID = vtimers.front();
|
|
|
|
VTimer *vtimer = kernelObjects.Get<VTimer>(vtimerID, error);
|
|
|
|
if (error)
|
|
return false;
|
|
|
|
// Reserve some stack space for arguments.
|
|
u32 argArea = currentMIPS->r[MIPS_REG_SP];
|
|
currentMIPS->r[MIPS_REG_SP] -= HANDLER_STACK_SPACE;
|
|
|
|
Memory::Write_U64(vtimer->nvt.schedule, argArea - 16);
|
|
Memory::Write_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;
|
|
}
|
|
|
|
virtual void handleResult(PendingInterrupt &pend) {
|
|
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);
|
|
}
|
|
};
|
|
|
|
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;
|
|
|
|
p.Do(vtimerTimer);
|
|
p.Do(vtimers);
|
|
CoreTiming::RestoreRegisterEvent(vtimerTimer, "VTimer", __KernelTriggerVTimer);
|
|
|
|
if (s >= 2)
|
|
p.Do(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) {
|
|
WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVTimer(): invalid name", SCE_KERNEL_ERROR_ERROR);
|
|
return SCE_KERNEL_ERROR_ERROR;
|
|
}
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelCreateVTimer(%s, %08x)", name, optParamAddr);
|
|
|
|
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::Read_U32(optParamAddr);
|
|
if (size > 4)
|
|
WARN_LOG_REPORT(SCEKERNEL, "sceKernelCreateVTimer(%s) unsupported options parameter, size = %d", name, size);
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
u32 sceKernelDeleteVTimer(SceUID uid) {
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelDeleteVTimer(%08x)", uid);
|
|
|
|
u32 error;
|
|
VTimer* vt = kernelObjects.Get<VTimer>(uid, error);
|
|
|
|
if (error) {
|
|
WARN_LOG(SCEKERNEL, "%08x=sceKernelDeleteVTimer(%08x)", error, uid);
|
|
return error;
|
|
}
|
|
|
|
for (std::list<SceUID>::iterator it = vtimers.begin(); it != vtimers.end(); ++it) {
|
|
if (*it == vt->GetUID()) {
|
|
vtimers.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return kernelObjects.Destroy<VTimer>(uid);
|
|
}
|
|
|
|
u32 sceKernelGetVTimerBase(SceUID uid, u32 baseClockAddr) {
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelGetVTimerBase(%08x, %08x)", uid, baseClockAddr);
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
|
|
if (error) {
|
|
WARN_LOG(SCEKERNEL, "%08x=sceKernelGetVTimerBase(%08x, %08x)", error, uid, baseClockAddr);
|
|
return error;
|
|
}
|
|
|
|
if (Memory::IsValidAddress(baseClockAddr))
|
|
Memory::Write_U64(vt->nvt.base, baseClockAddr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
u64 sceKernelGetVTimerBaseWide(SceUID uid) {
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelGetVTimerBaseWide(%08x)", uid);
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
|
|
if (error) {
|
|
WARN_LOG(SCEKERNEL, "%08x=sceKernelGetVTimerBaseWide(%08x)", error, uid);
|
|
return -1;
|
|
}
|
|
|
|
return vt->nvt.base;
|
|
}
|
|
|
|
u32 sceKernelGetVTimerTime(SceUID uid, u32 timeClockAddr) {
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelGetVTimerTime(%08x, %08x)", uid, timeClockAddr);
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
|
|
if (error) {
|
|
WARN_LOG(SCEKERNEL, "%08x=sceKernelGetVTimerTime(%08x, %08x)", error, uid, timeClockAddr);
|
|
return error;
|
|
}
|
|
|
|
u64 time = __getVTimerCurrentTime(vt);
|
|
if (Memory::IsValidAddress(timeClockAddr))
|
|
Memory::Write_U64(time, timeClockAddr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
u64 sceKernelGetVTimerTimeWide(SceUID uid) {
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelGetVTimerTimeWide(%08x)", uid);
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
|
|
if (error) {
|
|
WARN_LOG(SCEKERNEL, "%08x=sceKernelGetVTimerTimeWide(%08x)", error, uid);
|
|
return -1;
|
|
}
|
|
|
|
u64 time = __getVTimerCurrentTime(vt);
|
|
return time;
|
|
}
|
|
|
|
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) {
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelSetVTimerTime(%08x, %08x)", uid, timeClockAddr);
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
|
|
if (error) {
|
|
WARN_LOG(SCEKERNEL, "%08x=sceKernelSetVTimerTime(%08x, %08x)", error, uid, timeClockAddr);
|
|
return error;
|
|
}
|
|
|
|
u64 time = Memory::Read_U64(timeClockAddr);
|
|
if (Memory::IsValidAddress(timeClockAddr))
|
|
Memory::Write_U64(__KernelSetVTimer(vt, time), timeClockAddr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
u64 sceKernelSetVTimerTimeWide(SceUID uid, u64 timeClock) {
|
|
if (__IsInInterrupt()) {
|
|
WARN_LOG(SCEKERNEL, "sceKernelSetVTimerTimeWide(%08x, %llu): in interrupt", uid, timeClock);
|
|
return -1;
|
|
}
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelSetVTimerTimeWide(%08x, %llu)", uid, timeClock);
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
|
|
if (error || vt == NULL) {
|
|
WARN_LOG(SCEKERNEL, "%08x=sceKernelSetVTimerTimeWide(%08x, %llu)", error, uid, timeClock);
|
|
return -1;
|
|
}
|
|
|
|
return __KernelSetVTimer(vt, timeClock);
|
|
}
|
|
|
|
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) {
|
|
if (uid == runningVTimer) {
|
|
WARN_LOG(SCEKERNEL, "sceKernelStartVTimer(%08x): invalid vtimer", uid);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_VTID;
|
|
}
|
|
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelStartVTimer(%08x)", uid);
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
|
|
if (vt) {
|
|
if (vt->nvt.active != 0)
|
|
return 1;
|
|
|
|
__startVTimer(vt);
|
|
return 0;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
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) {
|
|
WARN_LOG(SCEKERNEL, "sceKernelStopVTimer(%08x): invalid vtimer", uid);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_VTID;
|
|
}
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelStopVTimer(%08x)", uid);
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
|
|
if (vt) {
|
|
if (vt->nvt.active == 0)
|
|
return 0;
|
|
|
|
__stopVTimer(vt);
|
|
return 1;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
u32 sceKernelSetVTimerHandler(SceUID uid, u32 scheduleAddr, u32 handlerFuncAddr, u32 commonAddr) {
|
|
if (uid == runningVTimer) {
|
|
WARN_LOG(SCEKERNEL, "sceKernelSetVTimerHandler(%08x, %08x, %08x, %08x): invalid vtimer", uid, scheduleAddr, handlerFuncAddr, commonAddr);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_VTID;
|
|
}
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
|
|
if (error) {
|
|
WARN_LOG(SCEKERNEL, "%08x=sceKernelSetVTimerHandler(%08x, %08x, %08x, %08x)", error, uid, scheduleAddr, handlerFuncAddr, commonAddr);
|
|
return error;
|
|
}
|
|
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelSetVTimerHandler(%08x, %08x, %08x, %08x)", uid, scheduleAddr, handlerFuncAddr, commonAddr);
|
|
|
|
u64 schedule = Memory::Read_U64(scheduleAddr);
|
|
vt->nvt.handlerAddr = handlerFuncAddr;
|
|
if (handlerFuncAddr) {
|
|
vt->nvt.commonAddr = commonAddr;
|
|
__KernelScheduleVTimer(vt, schedule);
|
|
} else {
|
|
__KernelScheduleVTimer(vt, vt->nvt.schedule);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
u32 sceKernelSetVTimerHandlerWide(SceUID uid, u64 schedule, u32 handlerFuncAddr, u32 commonAddr) {
|
|
if (uid == runningVTimer) {
|
|
WARN_LOG(SCEKERNEL, "sceKernelSetVTimerHandlerWide(%08x, %llu, %08x, %08x): invalid vtimer", uid, schedule, handlerFuncAddr, commonAddr);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_VTID;
|
|
}
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
|
|
if (error) {
|
|
WARN_LOG(SCEKERNEL, "%08x=sceKernelSetVTimerHandlerWide(%08x, %llu, %08x, %08x)", error, uid, schedule, handlerFuncAddr, commonAddr);
|
|
return error;
|
|
}
|
|
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelSetVTimerHandlerWide(%08x, %llu, %08x, %08x)", uid, schedule, handlerFuncAddr, commonAddr);
|
|
|
|
vt->nvt.handlerAddr = handlerFuncAddr;
|
|
if (handlerFuncAddr) {
|
|
vt->nvt.commonAddr = commonAddr;
|
|
__KernelScheduleVTimer(vt, schedule);
|
|
} else {
|
|
__KernelScheduleVTimer(vt, vt->nvt.schedule);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
u32 sceKernelCancelVTimerHandler(SceUID uid) {
|
|
if (uid == runningVTimer) {
|
|
WARN_LOG(SCEKERNEL, "sceKernelCancelVTimerHandler(%08x): invalid vtimer", uid);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_VTID;
|
|
}
|
|
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelCancelVTimerHandler(%08x)", uid);
|
|
|
|
//__cancelVTimer checks if uid is valid
|
|
return __KernelCancelVTimer(uid);
|
|
}
|
|
|
|
u32 sceKernelReferVTimerStatus(SceUID uid, u32 statusAddr) {
|
|
DEBUG_LOG(SCEKERNEL, "sceKernelReferVTimerStatus(%08x, %08x)", uid, statusAddr);
|
|
|
|
u32 error;
|
|
VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
|
|
|
|
if (error) {
|
|
WARN_LOG(SCEKERNEL, "%08x=sceKernelReferVTimerStatus(%08x, %08x)", error, uid, statusAddr);
|
|
return error;
|
|
}
|
|
|
|
if (Memory::IsValidAddress(statusAddr)) {
|
|
NativeVTimer status = vt->nvt;
|
|
u32 size = Memory::Read_U32(statusAddr);
|
|
status.current = __getVTimerCurrentTime(vt);
|
|
Memory::Memcpy(statusAddr, &status, std::min(size, (u32)sizeof(status)));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Not sure why this is exposed...
|
|
void _sceKernelReturnFromTimerHandler() {
|
|
ERROR_LOG_REPORT(SCEKERNEL,"_sceKernelReturnFromTimerHandler - should not be called!");
|
|
}
|