feat(libSceUsbd): report a USB stack with no devices attached

Init and exit succeed, the device list is empty, event handling waits for its timeout, and calls that need a device throw.
This commit is contained in:
Gjon Gojcevic 2026-10-07 20:00:41 -04:00 committed by Adrià Franch
parent 4f41832dc0
commit 6e5d70f0cd
4 changed files with 246 additions and 2 deletions

View File

@ -0,0 +1,15 @@
add_library(libSceUsbd SHARED EXCLUDE_FROM_ALL Export.cpp)
target_include_directories(libSceUsbd PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceUsbd)
if(BUILD_TESTING)
add_test_executable(guest_usbd_tests ${LIBS_INCLUDE_DIR}/tests/GuestUsbd.cpp)
target_include_directories(guest_usbd_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_usbd_tests PRIVATE libSceUsbd libc)
configure_windows_unwind(guest_usbd_tests)
add_test(NAME guest_usbd COMMAND guest_usbd_tests)
if(WIN32)
set_tests_properties(guest_usbd PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceUsbd>")
endif()
endif()

View File

@ -3,8 +3,172 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
#include <chrono>
#include <string>
#include <thread>
APS5_DUMMY_FUN
namespace {
constexpr std::int32_t SCE_USBD_ERROR_INVALID_ARG = static_cast<std::int32_t>(0x80240002);
struct UsbdTimeval {
std::int64_t seconds;
std::int64_t microseconds;
};
void* g_emptyDeviceList[1] = {nullptr};
}
extern "C" {
std::int32_t APS5_VABI sceUsbdInit() {
return 0;
}
void APS5_VABI sceUsbdExit() {
}
std::int64_t APS5_VABI sceUsbdGetDeviceList(void*** list) {
if (list == nullptr) return SCE_USBD_ERROR_INVALID_ARG;
*list = g_emptyDeviceList;
return 0;
}
void APS5_VABI sceUsbdFreeDeviceList(void** list, std::int32_t unrefDevices) {
(void)unrefDevices;
if (list != nullptr && list != g_emptyDeviceList) throw std::runtime_error(std::string(__func__) + ": list was not returned by sceUsbdGetDeviceList");
}
std::int32_t APS5_VABI sceUsbdHandleEventsTimeout(const UsbdTimeval* timeout) {
if (timeout == nullptr || timeout->seconds < 0 || timeout->microseconds < 0 || timeout->microseconds >= 1000000) return SCE_USBD_ERROR_INVALID_ARG;
std::this_thread::sleep_for(std::chrono::seconds(timeout->seconds) + std::chrono::microseconds(timeout->microseconds));
return 0;
}
int APS5_VABI sceUsbdAllocTransfer() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdAttachKernelDriver() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdCancelTransfer() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdCheckConnected() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdClaimInterface() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdClose() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdControlTransfer() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdEventHandlingOk() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdFillInterruptTransfer() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdFreeConfigDescriptor() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdFreeTransfer() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdGetActiveConfigDescriptor() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdGetBusNumber() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdGetConfigDescriptor() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdGetDeviceAddress() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdGetDeviceDescriptor() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdGetStringDescriptor() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdKernelDriverActive() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdOpen() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdRefDevice() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdReleaseInterface() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdResetDevice() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdSetConfiguration() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdSubmitTransfer() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdUnrefDevice() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -0,0 +1,64 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <stdexcept>
namespace {
struct UsbdTimeval {
std::int64_t seconds;
std::int64_t microseconds;
};
}
extern "C" {
std::int32_t APS5_VABI sceUsbdInit();
void APS5_VABI sceUsbdExit();
std::int64_t APS5_VABI sceUsbdGetDeviceList(void*** list);
void APS5_VABI sceUsbdFreeDeviceList(void** list, std::int32_t unrefDevices);
std::int32_t APS5_VABI sceUsbdHandleEventsTimeout(const UsbdTimeval* timeout);
int APS5_VABI sceUsbdOpen();
}
namespace {
constexpr std::int32_t invalidArgument = static_cast<std::int32_t>(0x80240002);
void Require(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "USBD: %s\n", message);
std::abort();
}
}
}
int main() {
Require(sceUsbdInit() == 0, "initialization failed");
void** list = nullptr;
Require(sceUsbdGetDeviceList(&list) == 0, "a device was listed");
Require(list != nullptr && list[0] == nullptr, "device list is not an empty null-terminated array");
sceUsbdFreeDeviceList(list, 1);
Require(sceUsbdGetDeviceList(nullptr) == invalidArgument, "null list accepted");
Require(sceUsbdHandleEventsTimeout(nullptr) == invalidArgument, "null timeout accepted");
const UsbdTimeval invalid{0, 1000000};
Require(sceUsbdHandleEventsTimeout(&invalid) == invalidArgument, "out-of-range microseconds accepted");
const UsbdTimeval timeout{0, 50000};
const auto start = std::chrono::steady_clock::now();
Require(sceUsbdHandleEventsTimeout(&timeout) == 0, "event handling failed");
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(45), "event handling returned before the timeout");
bool threw = false;
try {
sceUsbdOpen();
} catch (const std::runtime_error&) {
threw = true;
}
Require(threw, "opening a device did not throw");
sceUsbdExit();
std::puts("USBD tests passed");
return 0;
}

View File

@ -21,6 +21,7 @@ Throughout the project, every function at every stage either **does exactly what
- [libSceMbus](../../core/libs/prx/libSceMbus/Export.cpp) implements only the no-device path: initialization and termination succeed, no device events are reported and device queries return no entries
- [libSceAudioPropagation](../../core/libs/prx/libSceAudioPropagation/Export.cpp) implements only the no-acoustic-propagation path: the system keeps a 16-byte header in the title's CPU memory (no GPU memory), rooms, portals, sources and materials are tracked handles, attributes are validated and have no effect, no rays are requested and sources report no audio paths. Ray results, path calculation and audio paths are accepted only when empty and throw otherwise; `sceAudioPropagationSourceRender` writes silence (zeroes) to each source's output buffer for the given size, without a dry signal. Objects still alive when their system is destroyed become unusable, and their later destroy or unregister is a no-op: PPSA21567 releases its reference-counted room, portal and source owners just before `sceAudioPropagationSystemDestroy`, so the order for every object is not proven. Occlusion, reflections and reverb from level geometry are absent; in-game behaviour remains unverified.
- [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls and `sceNpCommerceSetPsStoreIconLayout` do nothing
- [libSceUsbd](../../core/libs/prx/libSceUsbd/Export.cpp) implements only the no-device path: `sceUsbdInit` and `sceUsbdExit` succeed without a host USB context, the device list is always empty and event handling waits for its timeout; calls that need a device throw
- [libSceSystemService](../../core/libs/prx/libSceSystemService/Export.cpp) - `sceSystemServicePowerTick`, `sceSystemServiceReportAbnormalTermination`, `sceSystemServiceDisableMusicPlayer` and `sceSystemServiceReenableMusicPlayer` return success and do nothing: the host has no auto power-off timer to reset, no system crash reporter to notify and no system music player to pause or resume
- [libSceBluetoothHid](../../core/libs/prx/libSceBluetoothHid/Export.cpp) implements only the no-device path: initialization and callback and device registration succeed, the callback is never called because no device connects, and calls that need a connected device throw
- [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling