Files
ppsspp/Core/HLE/sceKernel.h
T
Henrik RydgårdandClaude Opus 5.5 0b1e1a9bb3 POPS: Boot PS1 EBOOTs through the firmware's POPS manager
A PS1 EBOOT now boots the installed firmware's POPS manager
(flash0:/kd/popsman.prx), which loads its PS1 emulator (pops_0Xg.prx) for
the game. Load_PSP_POPS stands in for the kernel's reboot into its POPS
configuration: it loads the manager and that configuration's user modules,
and a helper thread makes the kernel init's scePopsManLoadModule(path, 0)
call. InitForKernel reports a POPS boot from the memory stick.

The emulator plays its sound on the Media Engine, so a PS1 boot always
gets the emulated ME, whatever the homebrew setting says, and is refused
on platforms that can't emulate it. The manager boots the ME with its own
code (POPS's reboot has no me_wrapper, so Sony's ME firmware isn't booted
for it), and its ME loop runs there unchanged, playing through the audio
block.

Everything in POPS that reaches hardware registers is kernel code, so a
POPS boot leaves the ME's kernel mode stopgap (g_kernelModeAnywhere) off:
7% fewer host instructions in Crash Bandicoot.

The manager also drives the GE directly: its stall register and FINISH
status are an MMIO device that only answers during a POPS boot, and stall
writes extend the HLE GPU's current list.

The 6.60 and 6.61 popsman.prx and pops_01g/02g.prx have the same code. The
manager's is checked when it's loaded, and any other one fails the boot
with a message. Savestates include the boot's threads.

Reading a game's disc image needs the next two changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 13:15:30 +02:00

347 lines
10 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/.
#pragma once
#include <map>
#include <string>
#include "Common/CommonTypes.h"
#include "Common/Log.h"
#include "Common/Swap.h"
class PointerWrap;
class MIPSState;
// If you add to this, make sure to check KernelObjectPool::CreateByIDType().
enum TMIDType : int {
SCE_KERNEL_TMID_Thread = 1,
SCE_KERNEL_TMID_Semaphore = 2,
SCE_KERNEL_TMID_EventFlag = 3,
SCE_KERNEL_TMID_Mbox = 4,
SCE_KERNEL_TMID_Vpl = 5,
SCE_KERNEL_TMID_Fpl = 6,
SCE_KERNEL_TMID_Mpipe = 7,
SCE_KERNEL_TMID_Callback = 8,
SCE_KERNEL_TMID_ThreadEventHandler = 9,
SCE_KERNEL_TMID_Alarm = 10,
SCE_KERNEL_TMID_VTimer = 11,
SCE_KERNEL_TMID_Mutex = 12,
SCE_KERNEL_TMID_LwMutex = 13,
SCE_KERNEL_TMID_Tlspl = 14,
SCE_KERNEL_TMID_SleepThread = 64,
SCE_KERNEL_TMID_DelayThread = 65,
SCE_KERNEL_TMID_SuspendThread = 66,
SCE_KERNEL_TMID_DormantThread = 67,
// This is kept for old savestates. Not the real value.
SCE_KERNEL_TMID_Tlspl_v0 = 0x1001,
// Not official, but need ids for save states.
PPSSPP_KERNEL_TMID_Module = 0x100001,
PPSSPP_KERNEL_TMID_PMB = 0x100002,
PPSSPP_KERNEL_TMID_File = 0x100003,
PPSSPP_KERNEL_TMID_DirList = 0x100004,
PPSSPP_KERNEL_TMID_Heap = 0x100005,
};
typedef int SceUID;
typedef unsigned int SceSize;
typedef int SceSSize;
typedef unsigned char SceUChar;
typedef unsigned int SceUInt;
typedef int SceMode;
typedef s64 SceOff;
typedef u64 SceIores;
typedef s32_le SceUID_le;
typedef u32_le SceSize_le;
typedef s32_le SceSSize_le;
typedef u32_le SceUInt_le;
typedef s32_le SceMode_le;
typedef s64_le SceOff_le;
typedef s64_le SceIores_le;
struct SceKernelLoadExecParam
{
SceSize_le size; // Size of the structure
SceSize_le args; // Size of the arg string
u32_le argp; // Pointer to the arg string
u32_le keyp; // Encryption key? Not yet used
};
// Used by the VSH's own LoadExec variants (sceKernelLoadExecVSHMs2 and friends, see
// LoadExecForKernel in sceKernel.cpp) instead of SceKernelLoadExecParam above. The first four
// fields share the same layout as SceKernelLoadExecParam, which is why the plain
// sceKernelLoadExec happens to work fine even when handed one of these. Fields past "flags"
// are only present if size >= 0x30 - see JPCSP's SceKernelLoadExecVSHParam for the reference
// this was ported from.
struct SceKernelLoadExecVSHParam
{
SceSize_le size; // Size of the structure
SceSize_le args; // Size of the arg string
u32_le argp; // Pointer to the arg string
u32_le keyp; // Pointer to the encryption key string
SceSize_le vshmainArgsSize; // Size of the vshmain-specific arg buffer
u32_le vshmainArgs; // Pointer to the vshmain-specific arg buffer
u32_le configFilep; // Pointer to a config file path string
u32_le unknownString;
u32_le flags;
u32_le extArgs;
u32_le extArgp;
u32_le opt11;
};
void __KernelInit();
void __KernelShutdown();
void __KernelDoState(PointerWrap &p);
bool __KernelIsRunning();
bool __KernelLoadExec(const char *filename, SceKernelLoadExecParam *param);
// For crash reporting.
std::string __KernelStateSummary();
// These HLE functions are declared here to be included in tables that are in other files.
int sceKernelLoadExec(MIPSState *mips, const char *filename, u32 paramPtr);
void sceKernelExitGame();
void sceKernelExitGameWithStatus();
u32 sceKernelDevkitVersion();
u32 sceKernelRegisterKprintfHandler();
int sceKernelRegisterDefaultExceptionHandler();
u32 sceKernelFindModuleByName(const char *name);
void sceKernelSetGPO(u32 ledAddr);
u32 sceKernelGetGPI();
int sceKernelDcacheInvalidateRange(u32 addr, int size);
int sceKernelDcacheWritebackAll();
int sceKernelDcacheWritebackRange(u32 addr, int size);
int sceKernelDcacheWritebackInvalidateRange(u32 addr, int size);
int sceKernelDcacheWritebackInvalidateAll();
int sceKernelGetThreadStackFreeSize(SceUID threadID);
u32 sceKernelIcacheInvalidateAll(MIPSState *mips);
u32 sceKernelIcacheClearAll(MIPSState *mips);
int sceKernelIcacheInvalidateRange(MIPSState *mips, u32 addr, int size);
#define KERNELOBJECT_MAX_NAME_LENGTH 31
class KernelObjectPool;
class KernelObject {
friend class KernelObjectPool;
u32 uid;
public:
virtual ~KernelObject() {}
SceUID GetUID() const {return uid;}
virtual const char *GetTypeName() {return "[BAD KERNEL OBJECT TYPE]";}
virtual const char *GetName() {return "[UNKNOWN KERNEL OBJECT]";}
virtual int GetIDType() const = 0;
virtual void GetQuickInfo(char *ptr, int size);
virtual void GetLongInfo(char *ptr, int bufSize) const;
// Implement the following in all subclasses:
// static u32 GetMissingErrorCode()
// static int GetStaticIDType()
virtual void DoState(PointerWrap &p) {
_dbg_assert_msg_(false, "Unable to save state: bad kernel object.");
}
};
// TODO: Delete the "occupied" array, rely on non-zero pool entries?
class KernelObjectPool {
public:
KernelObjectPool();
~KernelObjectPool() {}
// Allocates a UID within the range and inserts the object into the map.
SceUID Create(KernelObject *obj, int rangeBottom = initialNextID, int rangeTop = 0x7fffffff);
void DoState(PointerWrap &p);
static KernelObject *CreateByIDType(int type);
template <class T>
u32 Destroy(SceUID handle) {
u32 error;
if (Get<T>(handle, error)) {
int index = handle - handleOffset;
occupied[index] = false;
delete pool[index];
pool[index] = nullptr;
}
return error;
};
bool IsValid(SceUID handle) const {
int index = handle - handleOffset;
if (index < 0 || index >= maxCount)
return false;
else
return occupied[index];
}
template<class T>
bool Is(SceUID handle) const {
int index = handle - handleOffset;
if (index < 0 || index >= maxCount)
return false;
else
return occupied[index] && pool[handle - handleOffset]->GetIDType() == T::GetStaticIDType();
}
template <class T>
T* Get(SceUID handle, u32 &outError) {
if (handle < handleOffset || handle >= handleOffset+maxCount || !occupied[handle-handleOffset]) {
outError = T::GetMissingErrorCode();
return nullptr;
} else {
// Previously we had a dynamic_cast here, but since RTTI was disabled traditionally,
// it just acted as a static cast and everything worked. This means that we will never
// see the Wrong type object error below, but we'll just have to live with that danger.
T *t = static_cast<T *>(pool[handle - handleOffset]);
if (t == nullptr || t->GetIDType() != T::GetStaticIDType()) {
WARN_LOG(Log::sceKernel, "Kernel: Wrong object type for %d (%08x), was %s, should have been %s", handle, handle, t ? t->GetTypeName() : "null", T::GetStaticTypeName());
outError = T::GetMissingErrorCode();
return nullptr;
}
outError = 0; // SCE_KERNEL_ERROR_OK but don't want to include the header here.
return t;
}
}
// ONLY use this when you KNOW the handle is valid.
template <class T>
T *GetFast(SceUID handle) {
const SceUID realHandle = handle - handleOffset;
_dbg_assert_(realHandle >= 0 && realHandle < maxCount && occupied[realHandle]);
return static_cast<T *>(pool[realHandle]);
}
template <typename T, typename F>
void Iterate(F func) {
int type = T::GetStaticIDType();
for (int i = 0; i < maxCount; i++) {
if (!occupied[i])
continue;
T *t = static_cast<T *>(pool[i]);
if (t->GetIDType() == type) {
if (!func(i + handleOffset, t))
break;
}
}
}
// Like Iterate<T>(), but every live object regardless of type - only the base KernelObject
// interface (GetUID/GetTypeName/GetName/GetQuickInfo/...) is available on each. Used for a
// coarse "what's alive right now" overview across every kernel object kind at once, e.g. the
// WebSocket debugger's hle.object.list.
template <typename F>
void IterateAll(F func) {
for (int i = 0; i < maxCount; i++) {
if (!occupied[i])
continue;
if (!func(i + handleOffset, pool[i]))
break;
}
}
int ListIDType(int type, SceUID_le *uids, int count) const {
int total = 0;
for (int i = 0; i < maxCount; i++) {
if (!occupied[i]) {
continue;
}
if (pool[i]->GetIDType() == type) {
if (total < count) {
*uids++ = pool[i]->GetUID();
}
++total;
}
}
return total;
}
bool GetIDType(SceUID handle, int *type) const {
if (handle < handleOffset || handle >= handleOffset+maxCount || !occupied[handle-handleOffset]) {
ERROR_LOG(Log::sceKernel, "Kernel: Bad object handle %i (%08x)", handle, handle);
return false;
}
KernelObject *t = pool[handle - handleOffset];
*type = t->GetIDType();
return true;
}
void List();
void Clear();
int GetCount() const;
enum {
maxCount = 4096,
handleOffset = 0x100,
initialNextID = 0x10
};
private:
KernelObject *pool[maxCount];
bool occupied[maxCount];
int nextID;
};
extern KernelObjectPool kernelObjects;
typedef std::pair<int, int> KernelStatsSyscall;
struct KernelStats {
void Reset() {
ResetFrame();
}
void ResetFrame() {
msInSyscalls = 0;
slowestSyscallTime = 0;
slowestSyscallName = 0;
summedMsInSyscalls.clear();
summedSlowestSyscallTime = 0;
summedSlowestSyscallName = 0;
}
double msInSyscalls;
double slowestSyscallTime;
const char *slowestSyscallName;
std::map<KernelStatsSyscall, double> summedMsInSyscalls;
double summedSlowestSyscallTime;
const char *summedSlowestSyscallName;
};
extern KernelStats kernelStats;
extern u32 registeredExitCbId;
extern u32 g_GPOBits;
extern u32 g_GPIBits;
void Register_ThreadManForUser();
void Register_ThreadManForKernel();
void Register_LoadExecForUser();
void Register_LoadExecForKernel();
void Register_ExceptionManagerForKernel();
void Register_UtilsForKernel();
void Register_InitForKernel();
// returns nullptr if not found.
const char *KernelErrorToString(u32 err);