feat(libSceUsbd): implement transfer allocation and event handling for the no-device path

This commit is contained in:
Semir Kabir 2026-10-10 06:35:48 +00:00 committed by Semir Kabir
parent a31732c5ff
commit cbd18363bd
3 changed files with 90 additions and 29 deletions

View File

@ -4,6 +4,9 @@
#include "prx/libc/include/General.hpp"
#include <chrono>
#include <cstdlib>
#include <new>
#include <stdexcept>
#include <string>
#include <thread>
@ -16,6 +19,35 @@ struct UsbdTimeval {
std::int64_t microseconds;
};
constexpr std::int32_t SCE_USBD_ERROR_NOT_FOUND = static_cast<std::int32_t>(0x80240005);
constexpr std::uint8_t TRANSFER_TYPE_INTERRUPT = 3;
struct UsbdIsoPacketDescriptor {
std::uint32_t length;
std::uint32_t actualLength;
std::int32_t status;
};
using UsbdTransferCallback = void (APS5_VABI*)(struct UsbdTransfer*);
struct UsbdTransfer {
void* deviceHandle;
std::uint8_t flags;
std::uint8_t endpoint;
std::uint8_t type;
std::uint32_t timeout;
std::int32_t status;
std::int32_t length;
std::int32_t actualLength;
UsbdTransferCallback callback;
void* userData;
std::uint8_t* buffer;
std::int32_t numIsoPackets;
UsbdIsoPacketDescriptor isoPacketDescriptors[1];
};
static_assert(offsetof(UsbdTransfer, timeout) == 12 && offsetof(UsbdTransfer, callback) == 32 && offsetof(UsbdTransfer, numIsoPackets) == 56 && offsetof(UsbdTransfer, isoPacketDescriptors) == 60);
void* g_emptyDeviceList[1] = {nullptr};
}
@ -46,9 +78,42 @@ std::int32_t APS5_VABI sceUsbdHandleEventsTimeout(const UsbdTimeval* timeout) {
return 0;
}
int APS5_VABI sceUsbdAllocTransfer() {
NotImplemented_nid_no_patch(__func__);
return 0;
UsbdTransfer* APS5_VABI sceUsbdAllocTransfer(std::int32_t isoPackets) {
if (isoPackets < 0) return nullptr;
const std::size_t size = offsetof(UsbdTransfer, isoPacketDescriptors) + sizeof(UsbdIsoPacketDescriptor) * static_cast<std::size_t>(isoPackets);
auto* transfer = static_cast<UsbdTransfer*>(std::calloc(1, size));
if (transfer == nullptr) return nullptr;
transfer->numIsoPackets = isoPackets;
return transfer;
}
void APS5_VABI sceUsbdFreeTransfer(UsbdTransfer* transfer) {
std::free(transfer);
}
void APS5_VABI sceUsbdFillInterruptTransfer(UsbdTransfer* transfer, void* deviceHandle, std::uint8_t endpoint, std::uint8_t* buffer, std::int32_t length, UsbdTransferCallback callback, void* userData, std::uint32_t timeout) {
if (transfer == nullptr) throw std::runtime_error(std::string(__func__) + ": null transfer");
transfer->deviceHandle = deviceHandle;
transfer->endpoint = endpoint;
transfer->type = TRANSFER_TYPE_INTERRUPT;
transfer->timeout = timeout;
transfer->buffer = buffer;
transfer->length = length;
transfer->userData = userData;
transfer->callback = callback;
}
std::int32_t APS5_VABI sceUsbdCancelTransfer(UsbdTransfer* transfer) {
if (transfer == nullptr) throw std::runtime_error(std::string(__func__) + ": null transfer");
return SCE_USBD_ERROR_NOT_FOUND;
}
std::int32_t APS5_VABI sceUsbdEventHandlingOk() {
return 1;
}
void APS5_VABI sceUsbdFreeConfigDescriptor(void* config) {
if (config != nullptr) throw std::runtime_error(std::string(__func__) + ": descriptor was not returned by this library");
}
int APS5_VABI sceUsbdAttachKernelDriver() {
@ -56,11 +121,6 @@ int APS5_VABI sceUsbdAttachKernelDriver() {
return 0;
}
int APS5_VABI sceUsbdCancelTransfer() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceUsbdCheckConnected() {
NotImplemented_nid_no_patch(__func__);
return 0;
@ -81,26 +141,6 @@ int APS5_VABI sceUsbdControlTransfer() {
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;

View File

@ -22,6 +22,12 @@ 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();
void* APS5_VABI sceUsbdAllocTransfer(std::int32_t isoPackets);
void APS5_VABI sceUsbdFreeTransfer(void* transfer);
void APS5_VABI sceUsbdFillInterruptTransfer(void* transfer, void* deviceHandle, std::uint8_t endpoint, std::uint8_t* buffer, std::int32_t length, void* callback, void* userData, std::uint32_t timeout);
std::int32_t APS5_VABI sceUsbdCancelTransfer(void* transfer);
std::int32_t APS5_VABI sceUsbdEventHandlingOk();
void APS5_VABI sceUsbdFreeConfigDescriptor(void* config);
}
namespace {
@ -58,6 +64,21 @@ int main() {
threw = true;
}
Require(threw, "opening a device did not throw");
auto* transfer = static_cast<std::uint8_t*>(sceUsbdAllocTransfer(2));
Require(transfer != nullptr, "transfer allocation failed");
Require(*reinterpret_cast<std::int32_t*>(transfer + 56) == 2, "iso packet count not stored");
Require(sceUsbdAllocTransfer(-1) == nullptr, "negative iso packet count accepted");
std::uint8_t buffer[8] = {};
int user = 0;
sceUsbdFillInterruptTransfer(transfer, nullptr, 0x81, buffer, 8, reinterpret_cast<void*>(&Require), &user, 250);
Require(transfer[9] == 0x81 && transfer[10] == 3, "endpoint or interrupt type not filled");
Require(*reinterpret_cast<std::uint32_t*>(transfer + 12) == 250 && *reinterpret_cast<std::int32_t*>(transfer + 20) == 8, "timeout or length not filled");
Require(*reinterpret_cast<void**>(transfer + 40) == &user && *reinterpret_cast<std::uint8_t**>(transfer + 48) == buffer, "user data or buffer not filled");
Require(sceUsbdCancelTransfer(transfer) == static_cast<std::int32_t>(0x80240005), "cancelling an unsubmitted transfer did not report not found");
sceUsbdFreeTransfer(transfer);
sceUsbdFreeTransfer(nullptr);
Require(sceUsbdEventHandlingOk() == 1, "event handling not allowed");
sceUsbdFreeConfigDescriptor(nullptr);
sceUsbdExit();
std::puts("USBD tests passed");
return 0;

View File

@ -25,7 +25,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
- [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. Transfers follow the `libusb_transfer` layout from [libusb.h](https://github.com/libusb/libusb/blob/master/libusb/libusb.h), which [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/usbd/usbd.h) passes to titles as `SceUsbdTransfer`: `sceUsbdAllocTransfer` zero-allocates one with its iso packet count (a negative count returns null), `sceUsbdFillInterruptTransfer` fills the fields `libusb_fill_interrupt_transfer` sets, and `sceUsbdCancelTransfer` reports `LIBUSB_ERROR_NOT_FOUND` mapped to `0x80240005` by shadPS4's `0x80240000 - error` rule, since no transfer can be submitted. `sceUsbdEventHandlingOk` returns 1 because no other thread handles events, and `sceUsbdFreeConfigDescriptor` accepts only null because no descriptor is ever returned. The layout and error codes are unverified on PS5
- [libSceSystemService](../../core/libs/prx/libSceSystemService/Export.cpp) - `sceSystemServicePowerTick`, `sceSystemServiceReportAbnormalTermination`, `sceSystemServiceDisableMusicPlayer`, `sceSystemServiceReenableMusicPlayer`, `sceSystemServiceDisableMediaPlay` and `sceSystemServiceReenableMediaPlay` return success and do nothing: the host has no auto power-off timer to reset, no system crash reporter to notify, no system music player to pause or resume and no system media playback to disable. PPSA01521 calls `sceSystemServiceDisableMediaPlay` during start-up, and its wrappers for both pass no arguments
- [sceSystemServiceLaunchWebBrowser](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - AnyPS5 launches no web browser, so the call fails and nothing is shown. RommPS PPSA76677 then tells the player to scan its QR code instead
- [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