Files
ppsspp/Core/HLE/scePower.cpp
T
Henrik RydgårdandClaude Opus 5 f449fa6ab2 Implement a batch of the VSH's unresolved imports (and document three we must not)
Mostly small stubs:

- sceImpose: GetParam/SetParam/Changes/SetStatus, plus the 6.60 alias of
  sceImposeGetBatteryIconStatus. Also fixes that function's first output - it
  is a plain "is it charging" boolean, not a BATTICON_ value. We wrote
  PSP_IMPOSE_BATTICON_NONE (0x80000000) there, which games ignore but which the
  VSH reads as "no battery" and draws the empty-battery indicator for. These
  are the bulk of the traffic: the VSH calls sceImposeChanges once a frame, so
  this alone removes ~10000 trapped calls from a boot.
- SysMemForKernel: sceKernelSetRebootKernel, sceKernelSetUmdCacheOn.
- scePower_driver: scePowerSetWakeupCondition.
- sceHprm_driver, sceUsb: one NID-named call each, as in JPCSP.

Three groups are deliberately left unresolved, with comments explaining why,
because resolving them lets real flash0 drivers walk into hardware we do not
emulate and the boot dies where it used to reach the shell:

- ThreadManForKernel mutex/fpl NIDs: the NAND and ID storage drivers use these
  to init, then poll the NAND controller at 0xbd101300 forever.
- InterruptManagerForKernel intr registration: 31 calls, then a stall in GE
  list execution with no plugin module ever started.

73 unresolved import hits over 37 distinct module/NID pairs remain in a VSH
boot, mostly sceSysEventForKernel, sceSuspendForKernel and the various
*_driver modules that need real hardware behind them.

The sceImpose savestate section goes to v2 for the two new state variables.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-24 10:58:35 +02:00

681 lines
26 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 <map>
#include <vector>
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/HLE/ErrorCodes.h"
#include "Core/CoreTiming.h"
#include "Core/MemMap.h"
#include "Core/Reporting.h"
#include "Core/Config.h"
#include "Core/System.h"
#include "Core/Compatibility.h"
#include "Core/Debugger/MemBlockInfo.h"
#include "Core/HLE/scePower.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/HLE/sceKernelInterrupt.h"
struct VolatileWaitingThread {
SceUID threadID;
u32 addrPtr;
u32 sizePtr;
};
const int PSP_POWER_ERROR_TAKEN_SLOT = 0x80000020;
const int PSP_POWER_ERROR_SLOTS_FULL = 0x80000022;
const int PSP_POWER_ERROR_EMPTY_SLOT = 0x80000025;
const int PSP_POWER_ERROR_INVALID_CB = 0x80000100;
const int PSP_POWER_ERROR_INVALID_SLOT = 0x80000102;
const int PSP_POWER_CB_AC_POWER = 0x00001000;
const int PSP_POWER_CB_BATTERY_EXIST = 0x00000080;
const int PSP_POWER_CB_BATTERY_FULL = 0x00000064;
const int POWER_CB_AUTO = -1;
// These are the callback slots for user mode applications.
const int numberOfCBPowerSlots = 16;
// These are the callback slots for kernel mode applications.
const int numberOfCBPowerSlotsPrivate = 32;
static bool volatileMemLocked;
static int powerCbSlots[numberOfCBPowerSlots];
static std::vector<VolatileWaitingThread> volatileWaitingThreads;
// Should this belong here, or in CoreTiming?
static int RealpllFreq = 222000000;
static int RealbusFreq = 111000000;
static int pllFreq = 222000000;
static int busFreq = 111000000;
int GetLockedCPUSpeedMhz() {
return PSP_CoreParameter().compat.flags().RequireDefaultCPUClock ? 0 : g_Config.iLockedCPUSpeed;
}
// The CPU mhz can only be a multiple of the PLL divided by 511.
int PowerCpuMhzToHz(int desired, int pllHz) {
double maxfreq = desired * 1000000.0;
double step = (double)pllHz / 511.0;
// These values seem to be locked.
if (pllHz >= 333000000 && desired == 333) {
return 333000000;
} else if (pllHz >= 222000000 && desired == 222) {
return 222000000;
}
double freq = 0;
while (freq + step < maxfreq) {
freq += step;
}
// We match the PSP's HLE funcs better when we have the same float error, it seems.
return (int)((float)(freq / 1000000.0f) * 1000000);
}
int PowerPllMhzToHz(int mhz) {
// These seem to be the only steps it has.
if (mhz <= 190)
return 190285721;
if (mhz <= 222)
return 222000000;
if (mhz <= 266)
return 266399994;
if (mhz <= 333)
return 333000000;
return mhz * 1000000;
}
int PowerBusMhzToHz(int mhz) {
// These seem to be the only steps it has.
if (mhz <= 95)
return 95142860;
if (mhz <= 111)
return 111000000;
if (mhz <= 133)
return 133199997;
if (mhz <= 166)
return 166500000;
return mhz * 1000000;
}
void __PowerInit() {
memset(powerCbSlots, 0, sizeof(powerCbSlots));
volatileMemLocked = false;
NotifyMemInfo(MemBlockFlags::ALLOC, 0x08400000, 0x400000, "Volatile memory (not locked)");
// The tags are functional, because scePower is initialized after sceKernelMemory
volatileWaitingThreads.clear();
if (GetLockedCPUSpeedMhz() > 0) {
pllFreq = PowerPllMhzToHz(GetLockedCPUSpeedMhz());
busFreq = PowerBusMhzToHz(pllFreq / 2000000);
CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(GetLockedCPUSpeedMhz(), pllFreq));
} else {
pllFreq = PowerPllMhzToHz(222);
busFreq = PowerBusMhzToHz(111);
}
RealpllFreq = PowerPllMhzToHz(222);
RealbusFreq = PowerBusMhzToHz(111);
}
void __PowerDoState(PointerWrap &p) {
auto s = p.Section("scePower",1,2);
if (!s)
return;
if (s >= 2) {
Do(p, RealpllFreq);
Do(p, RealbusFreq);
if (RealpllFreq < 1000000)
RealpllFreq = PowerPllMhzToHz(RealpllFreq);
if (RealbusFreq < 1000000)
RealbusFreq = PowerBusMhzToHz(RealbusFreq);
} else {
RealpllFreq = PowerPllMhzToHz(222);
RealbusFreq = PowerBusMhzToHz(111);
}
if (GetLockedCPUSpeedMhz() > 0) {
pllFreq = PowerPllMhzToHz(GetLockedCPUSpeedMhz());
busFreq = PowerBusMhzToHz(pllFreq / 2000000);
CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(GetLockedCPUSpeedMhz(), pllFreq));
} else {
pllFreq = RealpllFreq;
busFreq = RealbusFreq;
}
DoArray(p, powerCbSlots, ARRAY_SIZE(powerCbSlots));
Do(p, volatileMemLocked);
Do(p, volatileWaitingThreads);
}
static int scePowerGetBatteryLifePercent() {
return hleLogDebug(Log::HLE, 100);
}
static int scePowerGetBatteryLifeTime() {
// 0 means we're on AC power.
return hleLogDebug(Log::HLE, 0);
}
static int scePowerGetBatteryTemp() {
// 0 means celsius temperature of the battery
return hleLogDebug(Log::HLE, 0);
}
static int scePowerIsPowerOnline() {
return hleLogDebug(Log::HLE, 1);
}
static int scePowerIsBatteryExist() {
return hleLogDebug(Log::HLE, 1);
}
static int scePowerIsBatteryCharging() {
return hleLogDebug(Log::HLE, 0);
}
static int scePowerGetBatteryChargingStatus() {
return hleLogDebug(Log::HLE, 0);
}
static int scePowerIsLowBattery() {
return hleLogDebug(Log::HLE, 0);
}
static int scePowerIsSuspendRequired() {
return hleLogDebug(Log::HLE, 0);
}
static int scePowerCancelRequest(u32 something) {
return hleLogError(Log::sceMisc, 0, "UNIMPL");
}
static int scePowerRequestSuspend() {
// Don't think we need to do anything.
return hleLogWarning(Log::sceMisc, 0, "UNIMPL");
}
static int scePowerRequestStandby() {
// Don't think we need to do anything.
return hleLogWarning(Log::sceMisc, 0, "UNIMPL");
}
static int scePowerRegisterCallback(int slot, int cbId) {
if (slot < -1 || slot >= numberOfCBPowerSlotsPrivate) {
return hleLogError(Log::HLE, PSP_POWER_ERROR_INVALID_SLOT);
}
if (slot >= numberOfCBPowerSlots) {
return hleLogError(Log::HLE, SCE_KERNEL_ERROR_PRIV_REQUIRED);
}
// TODO: If cbId is invalid return PSP_POWER_ERROR_INVALID_CB.
if (cbId == 0) {
return hleLogError(Log::HLE, PSP_POWER_ERROR_INVALID_CB);
}
int retval = -1;
if (slot == POWER_CB_AUTO) { // -1 signifies auto select of bank
for (int i=0; i < numberOfCBPowerSlots; i++) {
if (powerCbSlots[i] == 0 && retval == -1) { // found an empty slot
powerCbSlots[i] = cbId;
retval = i;
}
}
if (retval == -1) {
return hleLogError(Log::HLE, PSP_POWER_ERROR_SLOTS_FULL);
}
} else {
if (powerCbSlots[slot] == 0) {
powerCbSlots[slot] = cbId;
retval = 0;
} else {
return hleLogError(Log::HLE, PSP_POWER_ERROR_TAKEN_SLOT);
}
}
if (retval >= 0) {
int arg = PSP_POWER_CB_AC_POWER | PSP_POWER_CB_BATTERY_EXIST | PSP_POWER_CB_BATTERY_FULL;
__KernelNotifyCallback(cbId, arg);
}
return hleLogDebugOrError(Log::HLE, retval);
}
static int scePowerUnregisterCallback(int slotId) {
DEBUG_LOG(Log::HLE, "0=scePowerUnregisterCallback(%i)", slotId);
if (slotId < 0 || slotId >= numberOfCBPowerSlotsPrivate) {
return hleLogError(Log::HLE, PSP_POWER_ERROR_INVALID_SLOT);
}
if (slotId >= numberOfCBPowerSlots) {
return hleLogError(Log::HLE, SCE_KERNEL_ERROR_PRIV_REQUIRED);
}
if (powerCbSlots[slotId] != 0) {
int cbId = powerCbSlots[slotId];
powerCbSlots[slotId] = 0;
return hleLogDebug(Log::HLE, 0, "(cbid = %i)", cbId);
} else {
return hleLogError(Log::HLE, PSP_POWER_ERROR_EMPTY_SLOT);
}
}
static int sceKernelPowerLock(int lockType) {
if (lockType == 0) {
return hleLogDebug(Log::HLE, 0);
} else {
return hleLogError(Log::HLE, SCE_KERNEL_ERROR_INVALID_MODE);
}
}
static int sceKernelPowerUnlock(int lockType) {
if (lockType == 0) {
return hleLogDebug(Log::HLE, 0);
} else {
return hleLogError(Log::HLE, SCE_KERNEL_ERROR_INVALID_MODE);
}
}
static int sceKernelPowerTick(int flag) {
return hleLogDebug(Log::HLE, 0, "UNIMPL");
}
// not a syscall
int KernelVolatileMemLock(int type, u32 paddr, u32 psize) {
if (type != 0) {
return SCE_KERNEL_ERROR_INVALID_MODE;
}
if (volatileMemLocked) {
return SCE_KERNEL_ERROR_POWER_VMEM_IN_USE;
}
// Volatile RAM is always at 0x08400000 and is of size 0x00400000.
// It's always available in the emu.
// TODO: Should really reserve this properly!
if (Memory::IsValidAddress(paddr)) {
Memory::WriteUnchecked_U32(0x08400000, paddr);
}
if (Memory::IsValidAddress(psize)) {
Memory::WriteUnchecked_U32(0x00400000, psize);
}
volatileMemLocked = true;
NotifyMemInfo(MemBlockFlags::ALLOC, 0x08400000, 0x400000, "Volatile memory (locked)");
return 0;
}
static int sceKernelVolatileMemTryLock(int type, u32 paddr, u32 psize) {
u32 error = KernelVolatileMemLock(type, paddr, psize);
switch (error) {
case 0:
// HACK: This fixes Crash Tag Team Racing.
// Should only wait 1200 cycles though according to Unknown's testing,
// and with that it's still broken. So it's not this, unfortunately.
// Leaving it in for the 0.9.8 release anyway.
hleEatCycles(500000);
DEBUG_LOG(Log::HLE, "sceKernelVolatileMemTryLock(%i, %08x, %08x) - success", type, paddr, psize);
break;
case SCE_KERNEL_ERROR_POWER_VMEM_IN_USE:
// This is OK, let's not ERROR_LOG.
return hleLogDebug(Log::HLE, error, "(%i, %08x, %08x) - already locked!", type, paddr, psize);
default:
ERROR_LOG_REPORT(Log::HLE, "%08x=sceKernelVolatileMemTryLock(%i, %08x, %08x) - error", type, paddr, psize, error);
break;
}
return hleLogDebugOrError(Log::HLE, error);
}
int KernelVolatileMemUnlock(int type) {
if (type != 0) {
return SCE_KERNEL_ERROR_INVALID_MODE;
}
if (!volatileMemLocked) {
// I guess it must use a sema.
return SCE_KERNEL_ERROR_SEMA_OVF;
}
volatileMemLocked = false;
NotifyMemInfo(MemBlockFlags::ALLOC, 0x08400000, 0x400000, "Volatile memory (not locked)");
// Wake someone, always fifo.
bool wokeThreads = false;
u32 error;
while (!volatileWaitingThreads.empty() && !volatileMemLocked) {
VolatileWaitingThread waitInfo = volatileWaitingThreads.front();
volatileWaitingThreads.erase(volatileWaitingThreads.begin());
int waitID = __KernelGetWaitID(waitInfo.threadID, WAITTYPE_VMEM, error);
// If they were force-released, just skip.
if (waitID == 1 && KernelVolatileMemLock(0, waitInfo.addrPtr, waitInfo.sizePtr) == 0) {
__KernelResumeThreadFromWait(waitInfo.threadID, 0);
wokeThreads = true;
}
}
if (wokeThreads) {
INFO_LOG(Log::HLE, "KernelVolatileMemUnlock(%i) handed over to another thread", type);
hleReSchedule("volatile mem unlocked");
}
return 0;
}
static int sceKernelVolatileMemUnlock(int type) {
int error = KernelVolatileMemUnlock(type);
if (error == SCE_KERNEL_ERROR_INVALID_MODE) {
ERROR_LOG_REPORT(Log::HLE, "sceKernelVolatileMemUnlock(%i) - invalid mode", type);
return error;
} else if (error == SCE_KERNEL_ERROR_SEMA_OVF) {
ERROR_LOG_REPORT(Log::HLE, "sceKernelVolatileMemUnlock(%i) FAILED - not locked", type);
return error;
}
return hleLogDebug(Log::HLE, 0);
}
static int sceKernelVolatileMemLock(int type, u32 paddr, u32 psize) {
u32 error = 0;
// If dispatch is disabled or in an interrupt, don't check, just return an error.
// But still write the addr and size (some games require this to work, and it's testably true.)
if (!__KernelIsDispatchEnabled()) {
error = SCE_KERNEL_ERROR_CAN_NOT_WAIT;
} else if (__IsInInterrupt()) {
error = SCE_KERNEL_ERROR_ILLEGAL_CONTEXT;
} else {
error = KernelVolatileMemLock(type, paddr, psize);
}
switch (error) {
case 0:
// Should only wait 1200 cycles though according to Unknown's testing,
hleEatCycles(1200);
DEBUG_LOG(Log::HLE, "sceKernelVolatileMemLock(%i, %08x, %08x) - success", type, paddr, psize);
break;
case SCE_KERNEL_ERROR_POWER_VMEM_IN_USE:
{
WARN_LOG(Log::HLE, "sceKernelVolatileMemLock(%i, %08x, %08x) - already locked, waiting", type, paddr, psize);
const VolatileWaitingThread waitInfo = { __KernelGetCurThread(), paddr, psize };
volatileWaitingThreads.push_back(waitInfo);
__KernelWaitCurThread(WAITTYPE_VMEM, 1, 0, 0, false, "volatile mem waited");
}
break;
case SCE_KERNEL_ERROR_CAN_NOT_WAIT:
{
WARN_LOG(Log::HLE, "sceKernelVolatileMemLock(%i, %08x, %08x): dispatch disabled", type, paddr, psize);
Memory::WriteOrException_U32(0x08400000, paddr);
Memory::WriteOrException_U32(0x00400000, psize);
}
break;
case SCE_KERNEL_ERROR_ILLEGAL_CONTEXT:
{
WARN_LOG(Log::HLE, "sceKernelVolatileMemLock(%i, %08x, %08x): in interrupt", type, paddr, psize);
Memory::WriteOrException_U32(0x08400000, paddr);
Memory::WriteOrException_U32(0x00400000, psize);
}
break;
default:
ERROR_LOG_REPORT(Log::HLE, "%08x=sceKernelVolatileMemLock(%i, %08x, %08x) - error", type, paddr, psize, error);
break;
}
return hleNoLog(error);
}
static u32 scePowerSetClockFrequency(u32 pllfreq, u32 cpufreq, u32 busfreq) {
// 190 might (probably) be a typo for 19, but it's what the actual PSP validates against.
if (pllfreq < 19 || pllfreq < cpufreq || pllfreq > 333) {
return hleLogWarning(Log::sceMisc, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid pll frequency");
}
if (cpufreq == 0 || cpufreq > 333) {
return hleLogWarning(Log::sceMisc, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid cpu frequency");
}
if (busfreq == 0 || busfreq > 166) {
return hleLogWarning(Log::sceMisc, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid bus frequency");
}
// Only reschedules when the stepped PLL frequency changes.
// It seems like the busfreq parameter has no effect (but can cause errors.)
if (RealpllFreq != PowerPllMhzToHz(pllfreq)) {
int oldPll = RealpllFreq / 1000000;
RealpllFreq = PowerPllMhzToHz(pllfreq);
RealbusFreq = PowerBusMhzToHz(RealpllFreq / 2000000);
if (GetLockedCPUSpeedMhz() <= 0) {
pllFreq = RealpllFreq;
busFreq = RealbusFreq;
CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(cpufreq, pllFreq));
}
// The delay depends on the source and destination frequency, most are 150ms.
int newPll = RealpllFreq / 1000000;
int usec = 150000;
if ((newPll == 190 && oldPll == 222) || (newPll == 222 && oldPll == 190))
usec = 15700;
else if ((newPll == 266 && oldPll == 333) || (newPll == 333 && oldPll == 266))
usec = 16600;
return hleDelayResult(hleNoLog(0), "scepower set clockFrequency", usec);
}
if (GetLockedCPUSpeedMhz() <= 0) {
if (CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(cpufreq, pllFreq))) {
return hleLogInfo(Log::HLE, 0);
} else {
return hleLogDebug(Log::HLE, 0);
}
} else {
return hleLogInfo(Log::HLE, 0, "locked by user config at %i, %i, %i", GetLockedCPUSpeedMhz(), GetLockedCPUSpeedMhz(), busFreq);
}
return hleNoLog(0);
}
static u32 scePowerSetCpuClockFrequency(u32 cpufreq) {
if (cpufreq == 0 || cpufreq > 333) {
return hleLogWarning(Log::sceMisc, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid frequency");
}
if (GetLockedCPUSpeedMhz() > 0) {
return hleLogDebug(Log::sceMisc, 0, "locked by user config at %i", GetLockedCPUSpeedMhz());
}
CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(cpufreq, pllFreq));
return hleLogDebug(Log::sceMisc, 0);
}
static u32 scePowerSetBusClockFrequency(u32 busfreq) {
if (busfreq == 0 || busfreq > 111) {
return hleLogWarning(Log::sceMisc, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid frequency");
}
if (GetLockedCPUSpeedMhz() > 0) {
return hleLogDebug(Log::sceMisc, 0, "locked by user config at %i", GetLockedCPUSpeedMhz() / 2);
}
// The value passed is validated, but then doesn't seem to matter for the result.
// However, this sets a different hz than scePowerSetClockFrequency would have.
if (pllFreq <= 190)
busFreq = 94956673;
else if (pllFreq <= 222)
busFreq = 111000000;
else if (pllFreq <= 266)
busFreq = 132939331;
else if (pllFreq <= 333)
busFreq = 165848343;
else
busFreq = pllFreq / 2;
return hleLogDebug(Log::sceMisc, 0);
}
static u32 scePowerGetCpuClockFrequencyInt() {
int cpuFreq = CoreTiming::GetClockFrequencyHz() / 1000000;
return hleLogDebug(Log::sceMisc, cpuFreq);
}
static u32 scePowerGetPllClockFrequencyInt() {
return hleLogDebug(Log::sceMisc, pllFreq / 1000000);
}
static u32 scePowerGetBusClockFrequencyInt() {
return hleLogDebug(Log::sceMisc, busFreq / 1000000);
}
static float scePowerGetCpuClockFrequencyFloat() {
float cpuFreq = CoreTiming::GetClockFrequencyHz() / 1000000.0f;
DEBUG_LOG(Log::sceMisc, "%f=scePowerGetCpuClockFrequencyFloat()", (float)cpuFreq);
return cpuFreq;
}
static float scePowerGetPllClockFrequencyFloat() {
INFO_LOG(Log::sceMisc, "%f=scePowerGetPllClockFrequencyFloat()", (float)pllFreq / 1000000.0f);
return (float) pllFreq / 1000000.0f;
}
static float scePowerGetBusClockFrequencyFloat() {
INFO_LOG(Log::sceMisc, "%f=scePowerGetBusClockFrequencyFloat()", (float)busFreq / 1000000.0f);
return (float) busFreq / 1000000.0f;
}
static int scePowerTick() {
// Don't think we need to do anything.
return hleLogDebug(Log::sceMisc, 0);
}
static u32 scePowerCheckWlanCoexistenceClock() {
// PSP-1000 vs PSP-2000/3000
return hleLogDebug(Log::sceMisc, g_Config.iPSPModel);
}
static const HLEFunction scePower[] = {
{0X04B7766E, &WrapI_II<scePowerRegisterCallback>, "scePowerRegisterCallback", 'i', "ii" },
{0X2B51FE2F, nullptr, "scePower_2B51FE2F", '?', "" },
{0X442BFBAC, nullptr, "scePowerGetBacklightMaximum", '?', "" },
{0XEFD3C963, &WrapI_V<scePowerTick>, "scePowerTick", 'i', "" },
{0XEDC13FE5, nullptr, "scePowerGetIdleTimer", '?', "" },
{0X7F30B3B1, nullptr, "scePowerIdleTimerEnable", '?', "" },
{0X972CE941, nullptr, "scePowerIdleTimerDisable", '?', "" },
{0X27F3292C, nullptr, "scePowerBatteryUpdateInfo", '?', "" },
{0XE8E4E204, nullptr, "scePowerGetForceSuspendCapacity", '?', "" },
{0XB999184C, nullptr, "scePowerGetLowBatteryCapacity", '?', "" },
{0X87440F5E, &WrapI_V<scePowerIsPowerOnline>, "scePowerIsPowerOnline", 'i', "" },
{0X0AFD0D8B, &WrapI_V<scePowerIsBatteryExist>, "scePowerIsBatteryExist", 'i', "" },
{0X1E490401, &WrapI_V<scePowerIsBatteryCharging>, "scePowerIsBatteryCharging", 'i', "" },
{0XB4432BC8, &WrapI_V<scePowerGetBatteryChargingStatus>, "scePowerGetBatteryChargingStatus", 'i', "" },
{0XD3075926, &WrapI_V<scePowerIsLowBattery>, "scePowerIsLowBattery", 'i', "" },
{0X78A1A796, &WrapI_V<scePowerIsSuspendRequired>, "scePowerIsSuspendRequired", '?', "" },
{0X94F5A53F, nullptr, "scePowerGetBatteryRemainCapacity", '?', "" },
{0XFD18A0FF, nullptr, "scePowerGetBatteryFullCapacity", '?', "" },
{0X2085D15D, &WrapI_V<scePowerGetBatteryLifePercent>, "scePowerGetBatteryLifePercent", 'i', "" },
{0X8EFB3FA2, &WrapI_V<scePowerGetBatteryLifeTime>, "scePowerGetBatteryLifeTime", 'i', "" },
{0X28E12023, &WrapI_V<scePowerGetBatteryTemp>, "scePowerGetBatteryTemp", 'i', "" },
{0X862AE1A6, nullptr, "scePowerGetBatteryElec", '?', "" },
{0X483CE86B, nullptr, "scePowerGetBatteryVolt", '?', "" },
{0XCB49F5CE, nullptr, "scePowerGetBatteryChargeCycle", '?', "" },
{0X23436A4A, nullptr, "scePowerGetInnerTemp", '?', "" },
{0X0CD21B1F, nullptr, "scePowerSetPowerSwMode", '?', "" },
{0X165CE085, nullptr, "scePowerGetPowerSwMode", '?', "" },
{0XD6D016EF, nullptr, "scePowerLock", '?', "" },
{0XCA3D34C1, nullptr, "scePowerUnlock", '?', "" },
{0XDB62C9CF, &WrapI_U<scePowerCancelRequest>, "scePowerCancelRequest", 'i', "" },
{0X7FA406DD, nullptr, "scePowerIsRequest", '?', "" },
{0X2B7C7CF4, &WrapI_V<scePowerRequestStandby>, "scePowerRequestStandby", 'I', "" },
{0XAC32C9CC, &WrapI_V<scePowerRequestSuspend>, "scePowerRequestSuspend", 'I', "" },
{0X2875994B, nullptr, "scePower_2875994B", '?', "" },
{0X0074EF9B, nullptr, "scePowerGetResumeCount", '?', "" },
{0XDFA8BAF8, &WrapI_I<scePowerUnregisterCallback>, "scePowerUnregisterCallback", 'i', "i" },
{0XDB9D28DD, &WrapI_I<scePowerUnregisterCallback>, "scePowerUnregitserCallback", 'i', "i" },
{0X843FBF43, &WrapU_U<scePowerSetCpuClockFrequency>, "scePowerSetCpuClockFrequency", 'x', "x" },
{0XB8D7B3FB, &WrapU_U<scePowerSetBusClockFrequency>, "scePowerSetBusClockFrequency", 'x', "x" },
{0XFEE03A2F, &WrapU_V<scePowerGetCpuClockFrequencyInt>, "scePowerGetCpuClockFrequency", 'x', "" },
{0X478FE6F5, &WrapU_V<scePowerGetBusClockFrequencyInt>, "scePowerGetBusClockFrequency", 'x', "" },
{0XFDB5BFE9, &WrapU_V<scePowerGetCpuClockFrequencyInt>, "scePowerGetCpuClockFrequencyInt", 'x', "" },
{0XBD681969, &WrapU_V<scePowerGetBusClockFrequencyInt>, "scePowerGetBusClockFrequencyInt", 'x', "" },
{0XB1A52C83, &WrapF_V<scePowerGetCpuClockFrequencyFloat>, "scePowerGetCpuClockFrequencyFloat", 'f', "" },
{0X9BADB3EB, &WrapF_V<scePowerGetBusClockFrequencyFloat>, "scePowerGetBusClockFrequencyFloat", 'f', "" },
{0X737486F2, &WrapU_UUU<scePowerSetClockFrequency>, "scePowerSetClockFrequency", 'x', "xxx"},
{0X34F9C463, &WrapU_V<scePowerGetPllClockFrequencyInt>, "scePowerGetPllClockFrequencyInt", 'x', "" },
{0XEA382A27, &WrapF_V<scePowerGetPllClockFrequencyFloat>, "scePowerGetPllClockFrequencyFloat", 'f', "" },
{0XEBD177D6, &WrapU_UUU<scePowerSetClockFrequency>, "scePowerSetClockFrequency350", 'x', "xxx"}, // This is also the same as SetClockFrequency
{0X469989AD, &WrapU_UUU<scePowerSetClockFrequency>, "scePower_469989ad", 'x', "xxx"}, // This is also the same as SetClockFrequency
{0X545A7F3C, nullptr, "scePower_545A7F3C", '?', "" }, // TODO: Supposedly the same as SetClockFrequency also?
{0XA4E93389, nullptr, "scePower_A4E93389", '?', "" }, // TODO: Supposedly the same as SetClockFrequency also?
{0XA85880D0, &WrapU_V<scePowerCheckWlanCoexistenceClock>, "scePowerCheckWlanCoexistenceClock", 'x', "" },
{0X3951AF53, nullptr, "scePowerWaitRequestCompletion", '?', "" },
{0X0442D852, nullptr, "scePowerRequestColdReset", '?', "" },
{0XBAFA3DF0, nullptr, "scePowerGetCallbackMode", '?', "" },
{0XA9D22232, nullptr, "scePowerSetCallbackMode", '?', "" },
// These seem to be aliases.
{0X23C31FFE, &WrapI_IUU<sceKernelVolatileMemLock>, "scePowerVolatileMemLock", 'i', "ixx"},
{0XFA97A599, &WrapI_IUU<sceKernelVolatileMemTryLock>, "scePowerVolatileMemTryLock", 'i', "ixx"},
{0XB3EDD801, &WrapI_I<sceKernelVolatileMemUnlock>, "scePowerVolatileMemUnlock", 'i', "i" },
};
//890129c in tyshooter looks bogus
const HLEFunction sceSuspendForUser[] = {
{0XEADB1BD7, &WrapI_I<sceKernelPowerLock>, "sceKernelPowerLock", 'i', "i" }, //(int param) set param to 0
{0X3AEE7261, &WrapI_I<sceKernelPowerUnlock>, "sceKernelPowerUnlock", 'i', "i" }, //(int param) set param to 0
{0X090CCB3F, &WrapI_I<sceKernelPowerTick>, "sceKernelPowerTick", 'i', "i" },
// There's an extra 4MB that can be allocated, which seems to be "volatile". These functions
// let you grab it.
{0XA14F40B2, &WrapI_IUU<sceKernelVolatileMemTryLock>, "sceKernelVolatileMemTryLock", 'i', "ixx"},
{0XA569E425, &WrapI_I<sceKernelVolatileMemUnlock>, "sceKernelVolatileMemUnlock", 'i', "i" },
{0X3E0271D3, &WrapI_IUU<sceKernelVolatileMemLock>, "sceKernelVolatileMemLock", 'i', "ixx"},
};
void Register_scePower() {
RegisterHLEModule("scePower",ARRAY_SIZE(scePower),scePower);
}
// Real name unknown (jpcsp's scePower.java doesn't have one either - it's named after its
// own NID, scePower_driver_5F5006D2, and just returns 0 unconditionally). Kernel-only alias
// module some firmware-660+ code (e.g. the VSH's sceVshBridge_Driver) imports from.
static int scePower_driver_5F5006D2() {
return hleLogDebug(Log::HLE, 0, "UNTESTED");
}
// Configures which events would wake the console from suspend. We never suspend, so there's
// nothing to arm - but the VSH calls it during startup and wants a success back.
static int scePowerSetWakeupCondition(u32 condition) {
return hleLogWarning(Log::sceMisc, 0, "UNIMPL");
}
const HLEFunction scePower_driver[] = {
{0X5F5006D2, &WrapI_V<scePower_driver_5F5006D2>, "scePower_driver_5F5006D2", 'i', "" },
{0XBA566CD0, &WrapI_U<scePowerSetWakeupCondition>, "scePowerSetWakeupCondition", 'i', "x" },
};
void Register_scePower_driver() {
RegisterHLEModule("scePower_driver", ARRAY_SIZE(scePower_driver), scePower_driver);
}
void Register_sceSuspendForUser() {
RegisterHLEModule("sceSuspendForUser", ARRAY_SIZE(sceSuspendForUser), sceSuspendForUser);
}