Files
ppsspp/Core/HLE/sceUsb.cpp
T
Henrik RydgårdandClaude Opus 5.5 3880999ada Misc HLE: Fix heap leak, USB waits, RNG indices and small races
- sceHeapDeleteHeap never freed the heap's memory. Allocation failure
  returned -1 rather than a null pointer, and a near-4GB size wrapped.
- The USB wait timeout resumed threads that were no longer waiting on
  USB, and UsbUpdateState resumed them too, and never rescheduled.
- The Mersenne Twister drew from mt_[index_] with the index taken from
  guest memory. It's now taken modulo the state size. sceSfmt19937
  checks its index, and that fill arrays are at least a state long,
  which the library writes regardless of the length it's given.
- sceCccEncodeUTF16's surrogate test also caught U+F800 - U+FFFF, which
  includes the fullwidth forms.
- sceAudioRoutingSetVolumeMode stored the routing mode.
- A sample rate of 0 to sceAudioInput or the camera mic divided by zero,
  and a huge sample count sized a host buffer. sceP3daBridgeCore's sizes
  could wrap. sceRtc's tz offset could overflow tm_min.
- Camera shutdown freed the frame buffer and config without the lock
  the capture thread uses. GPS data is set from the platform's location
  thread, so it's now locked too, and the satellite index is bounded.
- A loop in the sys event handler list hung the unregister.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-27 10:39:45 +01:00

264 lines
8.6 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 "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Core/CoreTiming.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/ErrorCodes.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/HLE/KernelWaitHelpers.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/HLE/sceUsb.h"
#include "Core/MIPS/MIPS.h"
#include "Core/Reporting.h"
// TODO: Map by driver name
static bool usbStarted = false;
// TODO: Check actual status
static bool usbConnected = true;
// TODO: Activation by product id
static bool usbActivated = false;
static int usbWaitTimer = -1;
static std::vector<SceUID> waitingThreads;
enum UsbStatus {
USB_STATUS_STOPPED = 0x001,
USB_STATUS_STARTED = 0x002,
USB_STATUS_DISCONNECTED = 0x010,
USB_STATUS_CONNECTED = 0x020,
USB_STATUS_DEACTIVATED = 0x100,
USB_STATUS_ACTIVATED = 0x200,
};
static int UsbCurrentState() {
int state = 0;
if (usbStarted) {
state = USB_STATUS_STARTED
| (usbConnected ? USB_STATUS_CONNECTED : USB_STATUS_DISCONNECTED)
| (usbActivated ? USB_STATUS_ACTIVATED : USB_STATUS_DEACTIVATED);
}
return state;
}
static bool UsbMatchState(int state, u32 mode) {
int match = state & UsbCurrentState();
if (mode == 0) {
return match == state;
}
return match != 0;
}
static void UsbSetTimeout(PSPPointer<int> timeout) {
if (!timeout.IsValid() || usbWaitTimer == -1)
return;
// This should call __UsbWaitTimeout() later, unless we cancel it.
CoreTiming::ScheduleEvent(usToCycles(*timeout), usbWaitTimer, __KernelGetCurThread());
}
static void UsbWaitExecTimeout(u64 userdata, int cycleslate) {
u32 error;
SceUID threadID = (SceUID)userdata;
// The thread may have stopped waiting on USB since (for example, released), keeping the event.
if (__KernelGetWaitID(threadID, WAITTYPE_USB, error) == 0 || error != 0) {
HLEKernel::RemoveWaitingThread(waitingThreads, threadID);
return;
}
PSPPointer<int> timeout = PSPPointer<int>::Create(__KernelGetWaitTimeoutPtr(threadID, error));
if (timeout.IsValid())
*timeout = 0;
HLEKernel::RemoveWaitingThread(waitingThreads, threadID);
__KernelResumeThreadFromWait(threadID, SCE_ERROR_USB_WAIT_TIMEOUT);
__KernelReSchedule("wait timed out");
}
static void UsbUpdateState() {
u32 error;
bool wokeThreads = false;
for (size_t i = 0; i < waitingThreads.size(); ++i) {
SceUID threadID = waitingThreads[i];
int state = __KernelGetWaitID(threadID, WAITTYPE_USB, error);
// 0 means it's not in a USB wait anymore. Waits always have a nonzero state.
if (error != 0 || state == 0)
continue;
u32 mode = __KernelGetWaitValue(threadID, error);
if (UsbMatchState(state, mode)) {
waitingThreads.erase(waitingThreads.begin() + i);
--i;
PSPPointer<int> timeout = PSPPointer<int>::Create(__KernelGetWaitTimeoutPtr(threadID, error));
if (timeout.IsValid() && usbWaitTimer != -1) {
// Remove any event for this thread.
s64 cyclesLeft = CoreTiming::UnscheduleEvent(usbWaitTimer, threadID);
if (cyclesLeft < 0)
cyclesLeft = 0;
*timeout = (int)cyclesToUs(cyclesLeft);
}
__KernelResumeThreadFromWait(threadID, UsbCurrentState());
wokeThreads = true;
}
}
if (wokeThreads)
hleReSchedule("usb state change");
}
void __UsbInit() {
usbStarted = false;
usbConnected = true;
usbActivated = false;
waitingThreads.clear();
usbWaitTimer = CoreTiming::RegisterEvent("UsbWaitTimeout", UsbWaitExecTimeout);
}
void __UsbDoState(PointerWrap &p) {
auto s = p.Section("sceUsb", 1, 3);
if (!s)
return;
if (s >= 2) {
Do(p, usbStarted);
Do(p, usbConnected);
} else {
usbStarted = false;
usbConnected = true;
}
Do(p, usbActivated);
if (s >= 3) {
Do(p, waitingThreads);
Do(p, usbWaitTimer);
} else {
waitingThreads.clear();
usbWaitTimer = -1;
}
CoreTiming::RestoreRegisterEvent(usbWaitTimer, "UsbWaitTimeout", UsbWaitExecTimeout);
}
static int sceUsbStart(const char* driverName, u32 argsSize, u32 argsPtr) {
usbStarted = true;
UsbUpdateState();
return hleLogInfo(Log::HLE, 0);
}
static int sceUsbStop(const char* driverName, u32 argsSize, u32 argsPtr) {
usbStarted = false;
UsbUpdateState();
return hleLogInfo(Log::HLE, 0);
}
static int sceUsbGetState() {
int state = 0;
if (!usbStarted) {
state = 0x80243007;
} else {
state = UsbCurrentState();
}
return hleLogDebug(Log::HLE, state);
}
static int sceUsbActivate(u32 pid) {
usbActivated = true;
UsbUpdateState();
return hleLogDebug(Log::HLE, 0);
}
static int sceUsbDeactivate(u32 pid) {
usbActivated = false;
UsbUpdateState();
return hleLogDebug(Log::HLE, 0);
}
static int sceUsbWaitState(int state, u32 waitMode, u32 timeoutPtr) {
hleEatCycles(10000);
if (waitMode >= 2)
return hleLogError(Log::HLE, SCE_KERNEL_ERROR_ILLEGAL_MODE, "invalid mode");
if (state == 0)
return hleLogError(Log::HLE, SCE_KERNEL_ERROR_EVF_ILPAT, "bad state");
if (UsbMatchState(state, waitMode)) {
return hleLogDebug(Log::HLE, UsbCurrentState());
}
// We'll have to wait as long as it takes. Cleanup first, just in case.
HLEKernel::RemoveWaitingThread(waitingThreads, __KernelGetCurThread());
waitingThreads.push_back(__KernelGetCurThread());
UsbSetTimeout(PSPPointer<int>::Create(timeoutPtr));
__KernelWaitCurThread(WAITTYPE_USB, state, waitMode, timeoutPtr, false, "usb state waited");
return hleLogDebug(Log::HLE, 0, "waiting");
}
static int sceUsbWaitStateCB(int state, u32 waitMode, u32 timeoutPtr) {
ERROR_LOG_REPORT(Log::HLE, "UNIMPL sceUsbWaitStateCB(%i, %i, %08x)", state, waitMode, timeoutPtr);
return 0;
}
static int sceUsbstorBootSetCapacity(u32 capacity) {
return hleReportError(Log::HLE, 0, "unimplemented");
}
// Purpose unknown - JPCSP names it after its NID and returns 0.
static int sceUsb_8BFC3DE8(u32 arg) {
return hleLogWarning(Log::sceMisc, 0, "UNIMPL");
}
const HLEFunction sceUsb[] =
{
{0X8BFC3DE8, &WrapI_U<sceUsb_8BFC3DE8>, "sceUsb_8BFC3DE8", 'i', "x" },
{0XAE5DE6AF, &WrapI_CUU<sceUsbStart>, "sceUsbStart", 'i', "sxx"},
{0XC2464FA0, &WrapI_CUU<sceUsbStop>, "sceUsbStop", 'i', "sxx"},
{0XC21645A4, &WrapI_V<sceUsbGetState>, "sceUsbGetState", 'i', "" },
{0X4E537366, nullptr, "sceUsbGetDrvList", '?', "" },
{0X112CC951, nullptr, "sceUsbGetDrvState", '?', "" },
{0X586DB82C, &WrapI_U<sceUsbActivate>, "sceUsbActivate", 'i', "x" },
{0XC572A9C8, &WrapI_U<sceUsbDeactivate>, "sceUsbDeactivate", 'i', "x" },
{0X5BE0E002, &WrapI_IUU<sceUsbWaitState>, "sceUsbWaitState", 'x', "xip"},
{0X616F2B61, &WrapI_IUU<sceUsbWaitStateCB>, "sceUsbWaitStateCB", 'x', "xip"},
{0X1C360735, nullptr, "sceUsbWaitCancel", '?', "" },
};
const HLEFunction sceUsbstor[] =
{
{0X60066CFE, nullptr, "sceUsbstorGetStatus", '?', "" },
};
const HLEFunction sceUsbstorBoot[] =
{
{0XE58818A8, &WrapI_U<sceUsbstorBootSetCapacity>,"sceUsbstorBootSetCapacity", 'i', "x" },
{0X594BBF95, nullptr, "sceUsbstorBootSetLoadAddr", '?', "" },
{0X6D865ECD, nullptr, "sceUsbstorBootGetDataSize", '?', "" },
{0XA1119F0D, nullptr, "sceUsbstorBootSetStatus", '?', "" },
{0X1F080078, nullptr, "sceUsbstorBootRegisterNotify", '?', "" },
{0XA55C9E16, nullptr, "sceUsbstorBootUnregisterNotify", '?', "" },
};
void Register_sceUsb()
{
RegisterHLEModule("sceUsbstor", ARRAY_SIZE(sceUsbstor), sceUsbstor);
RegisterHLEModule("sceUsbstorBoot", ARRAY_SIZE(sceUsbstorBoot), sceUsbstorBoot);
RegisterHLEModule("sceUsb", ARRAY_SIZE(sceUsb), sceUsb);
}