From 64ae578ddaefd1ec32681024aefe695dc61a08d2 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 21 Oct 2026 20:20:20 +0200 Subject: [PATCH] feat(libSceUsbd): pass the device, packet size, alternate setting and halt calls through to libusb sceUsbdGetDevice, sceUsbdGetDeviceSpeed, sceUsbdGetMaxPacketSize, sceUsbdGetMaxIsoPacketSize, sceUsbdOpenDeviceWithVidPid, sceUsbdSetInterfaceAltSetting, sceUsbdClearHalt and sceUsbdDetachKernelDriver follow FreeBSD 11.4 libusb10.c over the host libusb. The packet sizes and the alternate setting checks are done as FreeBSD does them, so the library records which interfaces are claimed, and a device stays known while a handle to it is open. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceUsbd/Export.cpp | 116 +++++++++++++++++++++++----- core/libs/tests/GuestUsbd.cpp | 54 +++++++++++++ docs/dev/debt/libSceUsbd.md | 4 +- 3 files changed, 153 insertions(+), 21 deletions(-) diff --git a/core/libs/prx/libSceUsbd/Export.cpp b/core/libs/prx/libSceUsbd/Export.cpp index 46219b9973b4..edc6a19de005 100644 --- a/core/libs/prx/libSceUsbd/Export.cpp +++ b/core/libs/prx/libSceUsbd/Export.cpp @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -47,6 +48,7 @@ struct UsbdState { std::unordered_map devices; std::unordered_set lists; std::unordered_set handles; + std::unordered_map claimed; std::unordered_set configs; std::unordered_map inFlight; }; @@ -72,11 +74,15 @@ std::int32_t ToGuestError(int result) { return static_cast(0x80240000u - static_cast(result)); } +bool IsKnownDevice(const UsbdState& state, libusb_device* device) { + return state.devices.contains(device) || std::any_of(state.handles.begin(), state.handles.end(), [device](libusb_device_handle* handle) { return libusb_get_device(handle) == device; }); +} + libusb_device* KnownDevice(void* device, const char* function) { auto& state = State(); std::lock_guard lock(state.mutex); auto* host = static_cast(device); - if (!state.devices.contains(host)) throw std::runtime_error(AbsentDevice(function)); + if (!IsKnownDevice(state, host)) throw std::runtime_error(AbsentDevice(function)); return host; } @@ -115,6 +121,27 @@ void LIBUSB_CALL CompleteTransfer(libusb_transfer* host) { if (guest->callback != nullptr) guest->callback(guest); } +int MaxPacketSize(libusb_device* device, std::uint8_t endpoint) { + libusb_config_descriptor* config = nullptr; + const int result = libusb_get_active_config_descriptor(device, &config); + if (result < 0) return result; + int size = LIBUSB_ERROR_NOT_FOUND; + for (int index = 0; index < config->bNumInterfaces && size == LIBUSB_ERROR_NOT_FOUND; ++index) { + const auto& entry = config->interface[index]; + for (int alternate = 0; alternate < entry.num_altsetting && size == LIBUSB_ERROR_NOT_FOUND; ++alternate) { + const auto& setting = entry.altsetting[alternate]; + for (int item = 0; item < setting.bNumEndpoints; ++item) { + if (setting.endpoint[item].bEndpointAddress == endpoint) { + size = setting.endpoint[item].wMaxPacketSize; + break; + } + } + } + } + libusb_free_config_descriptor(config); + return size; +} + bool RequestStringDescriptor(libusb_device_handle* handle, std::uint8_t descriptorIndex, std::uint16_t languageId, std::uint8_t* descriptor) { std::memset(descriptor, 0, STRING_DESCRIPTOR_SIZE); const auto value = static_cast(LIBUSB_DT_STRING << 8 | descriptorIndex); @@ -143,6 +170,7 @@ void APS5_VABI sceUsbdExit() { if (state.initCount == 0 || --state.initCount != 0) return; if (!state.inFlight.empty()) throw std::runtime_error(std::string(__func__) + ": transfers are still in flight"); for (auto* handle : state.handles) libusb_close(handle); + state.claimed.clear(); for (auto* config : state.configs) libusb_free_config_descriptor(config); for (auto* list : state.lists) libusb_free_device_list(list, 0); for (const auto& [device, references] : state.devices) { @@ -358,7 +386,14 @@ std::int32_t APS5_VABI sceUsbdCheckConnected(void* deviceHandle) { std::int32_t APS5_VABI sceUsbdClaimInterface(void* deviceHandle, std::int32_t interfaceNumber) { if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; - return ToGuestError(libusb_claim_interface(KnownHandle(deviceHandle, __func__), interfaceNumber)); + auto* handle = KnownHandle(deviceHandle, __func__); + const int result = libusb_claim_interface(handle, interfaceNumber); + if (result == 0) { + auto& state = State(); + std::lock_guard lock(state.mutex); + state.claimed[handle] |= 1u << interfaceNumber; + } + return ToGuestError(result); } void APS5_VABI sceUsbdClose(void* deviceHandle) { @@ -367,6 +402,7 @@ void APS5_VABI sceUsbdClose(void* deviceHandle) { std::lock_guard lock(state.mutex); auto* host = static_cast(deviceHandle); if (state.handles.erase(host) == 0) throw std::runtime_error(UnopenedHandle(__func__)); + state.claimed.erase(host); libusb_close(host); } @@ -449,14 +485,21 @@ void* APS5_VABI sceUsbdRefDevice(void* device) { auto& state = State(); std::lock_guard lock(state.mutex); auto* host = static_cast(device); - if (!state.devices.contains(host)) throw std::runtime_error(AbsentDevice(__func__)); + if (!IsKnownDevice(state, host)) throw std::runtime_error(AbsentDevice(__func__)); AddDeviceReference(state, host); return libusb_ref_device(host); } std::int32_t APS5_VABI sceUsbdReleaseInterface(void* deviceHandle, std::int32_t interfaceNumber) { if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; - return ToGuestError(libusb_release_interface(KnownHandle(deviceHandle, __func__), interfaceNumber)); + auto* handle = KnownHandle(deviceHandle, __func__); + const int result = libusb_release_interface(handle, interfaceNumber); + if (result == 0) { + auto& state = State(); + std::lock_guard lock(state.mutex); + state.claimed[handle] &= ~(1u << interfaceNumber); + } + return ToGuestError(result); } std::int32_t APS5_VABI sceUsbdResetDevice(void* deviceHandle) { @@ -516,13 +559,16 @@ void APS5_VABI sceUsbdUnrefDevice(void* device) { } std::int32_t APS5_VABI sceUsbdClearHalt(void* deviceHandle, std::uint8_t endpoint) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (deviceHandle == nullptr) throw std::runtime_error(std::string(__func__) + ": null handle"); + const int result = libusb_clear_halt(KnownHandle(deviceHandle, __func__), endpoint); + if (result == 0) return 0; + if (result == LIBUSB_ERROR_NOT_FOUND) return SCE_USBD_ERROR_OTHER; + throw std::runtime_error(std::string(__func__) + ": libusb error " + std::to_string(result) + " has no known console result"); } std::int32_t APS5_VABI sceUsbdDetachKernelDriver(void* deviceHandle, int interfaceNumber) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + return libusb_detach_kernel_driver(KnownHandle(deviceHandle, __func__), static_cast(interfaceNumber)) == 0 ? 0 : SCE_USBD_ERROR_OTHER; } std::int32_t APS5_VABI sceUsbdEventHandlerActive() { @@ -559,23 +605,27 @@ std::int32_t APS5_VABI sceUsbdGetDescriptor(void* deviceHandle, std::uint8_t des } void* APS5_VABI sceUsbdGetDevice(void* deviceHandle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (deviceHandle == nullptr) return nullptr; + return libusb_get_device(KnownHandle(deviceHandle, __func__)); } int APS5_VABI sceUsbdGetDeviceSpeed(void* device) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (device == nullptr) return LIBUSB_SPEED_UNKNOWN; + return std::min(libusb_get_device_speed(KnownDevice(device, __func__)), static_cast(LIBUSB_SPEED_SUPER)); } std::int32_t APS5_VABI sceUsbdGetMaxIsoPacketSize(void* device, std::uint8_t endpoint) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (device == nullptr) throw std::runtime_error(std::string(__func__) + ": null device"); + auto* host = KnownDevice(device, __func__); + int size = MaxPacketSize(host, endpoint); + const int speed = libusb_get_device_speed(host); + if (speed != LIBUSB_SPEED_LOW && speed != LIBUSB_SPEED_FULL && size >= 0) size = (size & 0x7FF) * std::min(1 + ((size >> 11) & 3), 3); + return ToGuestError(size); } std::int32_t APS5_VABI sceUsbdGetMaxPacketSize(void* device, std::uint8_t endpoint) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (device == nullptr) return ToGuestError(LIBUSB_ERROR_NO_DEVICE); + return ToGuestError(MaxPacketSize(KnownDevice(device, __func__), endpoint)); } std::int32_t APS5_VABI sceUsbdGetStringDescriptorAscii(void* deviceHandle, std::uint8_t descriptorIndex, std::uint8_t* data, std::int32_t length) { @@ -629,13 +679,39 @@ void APS5_VABI sceUsbdLockEvents() { } void* APS5_VABI sceUsbdOpenDeviceWithVidPid(std::uint16_t vendorId, std::uint16_t productId) { - NotImplemented_nid_no_patch(__func__); - return 0; + auto& state = State(); + std::lock_guard lock(state.mutex); + if (state.context == nullptr) return nullptr; + libusb_device** devices = nullptr; + const ssize_t count = libusb_get_device_list(state.context, &devices); + if (count < 0) return nullptr; + libusb_device_handle* handle = nullptr; + for (ssize_t index = 0; index < count; ++index) { + libusb_device_descriptor descriptor{}; + if (libusb_get_device_descriptor(devices[index], &descriptor) == 0 && descriptor.idVendor == vendorId && descriptor.idProduct == productId) { + if (libusb_open(devices[index], &handle) != 0) handle = nullptr; + break; + } + } + libusb_free_device_list(devices, 1); + if (handle != nullptr) state.handles.insert(handle); + return handle; } std::int32_t APS5_VABI sceUsbdSetInterfaceAltSetting(void* deviceHandle, int interfaceNumber, int alternateSetting) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + auto* handle = KnownHandle(deviceHandle, __func__); + if (interfaceNumber < 0 || interfaceNumber > 31) return SCE_USBD_ERROR_INVALID_ARG; + { + auto& state = State(); + std::lock_guard lock(state.mutex); + const auto claimed = state.claimed.find(handle); + if (claimed == state.claimed.end() || (claimed->second & (1u << interfaceNumber)) == 0) return ToGuestError(LIBUSB_ERROR_NOT_FOUND); + for (const auto& [guest, host] : state.inFlight) { + if (host->dev_handle == handle) throw std::runtime_error(std::string(__func__) + ": the device has transfers in flight, which FreeBSD libusb cancels first"); + } + } + return libusb_set_interface_alt_setting(handle, interfaceNumber, static_cast(alternateSetting)) == 0 ? 0 : SCE_USBD_ERROR_OTHER; } std::int32_t APS5_VABI sceUsbdTryLockEvents() { diff --git a/core/libs/tests/GuestUsbd.cpp b/core/libs/tests/GuestUsbd.cpp index 512bee8071ee..eb6cc9af4454 100644 --- a/core/libs/tests/GuestUsbd.cpp +++ b/core/libs/tests/GuestUsbd.cpp @@ -1,6 +1,7 @@ #include "prx/libSceUsbd/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include @@ -18,6 +19,10 @@ namespace { namespace { constexpr std::int32_t invalidArgument = static_cast(0x80240002); +constexpr std::int32_t noDevice = static_cast(0x80240004); +constexpr std::int32_t notFound = static_cast(0x80240005); +constexpr std::int32_t otherError = static_cast(0x802400FF); +constexpr std::uint8_t absentEndpoint = 0x70; void Require(bool condition, const char* message) { if (!condition) { @@ -87,6 +92,10 @@ void Device() { void* handle = nullptr; Require(sceUsbdOpen(device, &handle) == 0 && handle != nullptr, "opening the device failed"); Require(sceUsbdCheckConnected(handle) == 0, "an open device was reported disconnected"); + Require(sceUsbdGetDevice(handle) == device, "the device of an open handle is not the device it was opened from"); + const int speed = sceUsbdGetDeviceSpeed(device); + Require(speed >= 1 && speed <= 4, "the device speed is not low, full, high or super speed"); + std::printf("device speed %d\n", speed); std::int32_t configuration = -1; Require(sceUsbdGetConfiguration(handle, &configuration) == 0 && configuration == activeValue, "configuration differs from the active config descriptor"); std::uint8_t text[256] = {}; @@ -131,6 +140,7 @@ void Device() { const std::uint8_t* interfaceArray = *reinterpret_cast(config0 + 16); std::uint8_t interruptAddress = 0; std::uint16_t interruptMax = 0; + std::uint8_t interruptInterface = 0; if (interfaceArray == nullptr) { Require(false, "the active config descriptor has no interface array"); } @@ -144,11 +154,29 @@ void Device() { if ((item[2] & 0x80) != 0 && (item[3] & 3) == 3) { interruptAddress = item[2]; interruptMax = static_cast(item[4] | item[5] << 8); + interruptInterface = alternate[2]; } } } Require(interruptAddress != 0 && interruptMax != 0, "the device has no interrupt IN endpoint"); sceUsbdFreeConfigDescriptor(config); + Require(sceUsbdGetMaxPacketSize(device, interruptAddress) == interruptMax, "the max packet size differs from the endpoint descriptor"); + const int expectedIso = speed <= 2 ? interruptMax : (interruptMax & 0x7FF) * std::min(1 + ((interruptMax >> 11) & 3), 3); + Require(sceUsbdGetMaxIsoPacketSize(device, interruptAddress) == expectedIso, "the max iso packet size differs from the endpoint descriptor"); + Require(sceUsbdGetMaxPacketSize(device, absentEndpoint) == notFound && sceUsbdGetMaxIsoPacketSize(device, absentEndpoint) == notFound, "an absent endpoint has a max packet size"); + Require(sceUsbdSetInterfaceAltSetting(handle, interruptInterface, 0) == notFound, "an unclaimed interface took an alternate setting"); + Require(sceUsbdSetInterfaceAltSetting(handle, 32, 0) == invalidArgument && sceUsbdSetInterfaceAltSetting(handle, -1, 0) == invalidArgument, "an interface number outside 0 to 31 was accepted"); + Require(sceUsbdClaimInterface(handle, interruptInterface) == 0, "claiming the interrupt endpoint's interface failed"); + const std::int32_t alternate = sceUsbdSetInterfaceAltSetting(handle, interruptInterface, 0); + Require(alternate == 0 || alternate == otherError, "alternate setting 0 returned neither success nor the other error"); + std::printf("alternate setting 0 of interface %u: %d\n", interruptInterface, alternate); + Require(sceUsbdClearHalt(handle, interruptAddress) == 0, "clearing the interrupt endpoint's halt failed"); + Require(sceUsbdClearHalt(handle, absentEndpoint) == otherError, "clearing the halt of an absent endpoint did not return the other error"); + const std::int32_t detached = sceUsbdDetachKernelDriver(handle, interruptInterface); + Require(detached == 0 || detached == otherError, "detaching the kernel driver returned neither success nor the other error"); + std::printf("kernel driver detach: %d\n", detached); + Require(sceUsbdReleaseInterface(handle, interruptInterface) == 0, "releasing the interrupt endpoint's interface failed"); + Require(sceUsbdSetInterfaceAltSetting(handle, interruptInterface, 0) == notFound, "a released interface took an alternate setting"); auto* transfer = reinterpret_cast(sceUsbdAllocTransfer(0)); std::uint8_t setup[8 + 18] = {0x80, 6, 0x00, 0x01, 0x00, 0x00, 18, 0}; @@ -196,7 +224,17 @@ void Device() { sceUsbdClose(handle); Require(Throws([&] { sceUsbdClose(handle); }), "a handle was closed twice"); + Require(Throws([&] { sceUsbdGetDevice(handle); }), "the device of a closed handle was returned"); sceUsbdUnrefDevice(device); + void* byId = sceUsbdOpenDeviceWithVidPid(static_cast(vendor), static_cast(product)); + Require(byId != nullptr, "opening the device by vendor and product failed"); + void* byIdDevice = sceUsbdGetDevice(byId); + std::uint8_t byIdDescriptor[18] = {}; + Require(sceUsbdGetDeviceDescriptor(byIdDevice, byIdDescriptor) == 0, "the device of a handle opened by vendor and product has no descriptor"); + Require((byIdDescriptor[8] | byIdDescriptor[9] << 8) == vendor && (byIdDescriptor[10] | byIdDescriptor[11] << 8) == product, "the device opened by vendor and product has another vendor or product"); + Require(sceUsbdGetMaxPacketSize(byIdDevice, interruptAddress) == interruptMax, "the max packet size through a handle's device differs"); + sceUsbdClose(byId); + Require(Throws([&] { sceUsbdGetDeviceDescriptor(byIdDevice, byIdDescriptor); }), "the device of a closed handle stayed known"); std::puts("USBD device tests passed"); } @@ -291,6 +329,21 @@ int main(int argc, char** argv) { Require(Throws([&] { sceUsbdSubmitTransfer(reinterpret_cast(transfer)); }), "submitting on a handle that was never opened did not throw"); sceUsbdFreeTransfer(reinterpret_cast(transfer)); + Require(Throws([&] { sceUsbdClearHalt(nullptr, 0x81); }), "clearing a halt on a null handle did not throw"); + Require(Throws([&] { sceUsbdClearHalt(&foreign, 0x81); }), "clearing a halt on a handle that was never opened did not throw"); + Require(sceUsbdDetachKernelDriver(nullptr, 0) == invalidArgument, "null handle kernel driver detach accepted"); + Require(Throws([&] { sceUsbdDetachKernelDriver(&foreign, 0); }), "kernel driver detach on a handle that was never opened did not throw"); + Require(sceUsbdGetDevice(nullptr) == nullptr, "a null handle has a device"); + Require(Throws([&] { sceUsbdGetDevice(&foreign); }), "the device of a handle that was never opened did not throw"); + Require(sceUsbdGetDeviceSpeed(nullptr) == 0, "a null device has a speed"); + Require(Throws([&] { sceUsbdGetDeviceSpeed(&foreign); }), "the speed of a device that was never listed did not throw"); + Require(sceUsbdGetMaxPacketSize(nullptr, 0x81) == noDevice, "a null device has a max packet size"); + Require(Throws([&] { sceUsbdGetMaxPacketSize(&foreign, 0x81); }), "the max packet size of a device that was never listed did not throw"); + Require(Throws([&] { sceUsbdGetMaxIsoPacketSize(nullptr, 0x81); }), "the max iso packet size of a null device did not throw"); + Require(Throws([&] { sceUsbdGetMaxIsoPacketSize(&foreign, 0x81); }), "the max iso packet size of a device that was never listed did not throw"); + Require(sceUsbdSetInterfaceAltSetting(nullptr, 0, 0) == invalidArgument, "null handle alternate setting accepted"); + Require(Throws([&] { sceUsbdSetInterfaceAltSetting(&foreign, 0, 0); }), "an alternate setting on a handle that was never opened did not throw"); + Require(sceUsbdOpenDeviceWithVidPid(0, 0) == nullptr, "a device with vendor and product 0 was opened"); sceUsbdFreeTransfer(nullptr); Require(sceUsbdEventHandlingOk() == 1, "event handling not allowed"); @@ -306,6 +359,7 @@ int main(int argc, char** argv) { Require(setupBuffer[6] == 18 && setupBuffer[7] == 0, "length not filled little-endian"); if (argc == 2 && std::strcmp(argv[1], "device") == 0) Device(); sceUsbdExit(); + Require(sceUsbdOpenDeviceWithVidPid(0, 0) == nullptr, "a device was opened after the library exited"); std::puts("USBD tests passed"); return 0; diff --git a/docs/dev/debt/libSceUsbd.md b/docs/dev/debt/libSceUsbd.md index 3f234ef7b40f..e7e535f68b69 100644 --- a/docs/dev/debt/libSceUsbd.md +++ b/docs/dev/debt/libSceUsbd.md @@ -6,8 +6,10 @@ ### Unknown function info -- [sceUsbdClearHalt, sceUsbdDetachKernelDriver, sceUsbdEventHandlerActive, sceUsbdGetDevice, sceUsbdGetDeviceSpeed, sceUsbdGetMaxIsoPacketSize, sceUsbdGetMaxPacketSize, sceUsbdHandleEvents, sceUsbdHandleEventsLocked, sceUsbdLockEventWaiters, sceUsbdLockEvents, sceUsbdOpenDeviceWithVidPid, sceUsbdSetInterfaceAltSetting, sceUsbdTryLockEvents, sceUsbdUnlockEventWaiters, sceUsbdUnlockEvents, sceUsbdWaitForEvent](../../../core/libs/prx/libSceUsbd/Export.cpp) (libSceUsbd) - signatures follow the console type database in [orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L2268-L2322), but their behavior is unknown and each call throws; not measured on a console +- [sceUsbdEventHandlerActive, sceUsbdHandleEvents, sceUsbdHandleEventsLocked, sceUsbdLockEventWaiters, sceUsbdLockEvents, sceUsbdTryLockEvents, sceUsbdUnlockEventWaiters, sceUsbdUnlockEvents, sceUsbdWaitForEvent](../../../core/libs/prx/libSceUsbd/Export.cpp) (libSceUsbd) - signatures follow the console type database in [orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L2268-L2322), but their behavior is unknown and each call throws; not measured on a console ### Functional - [sceUsbdGetConfiguration, sceUsbdGetConfigDescriptorByValue, sceUsbdGetDescriptor, sceUsbdGetStringDescriptorAscii](../../../core/libs/prx/libSceUsbd/Export.cpp) (libSceUsbd) - not measured on a console; they follow FreeBSD 11.4 ([libusb10_desc.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libusb/libusb10_desc.c), [libusb20.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libusb/libusb20.c)). The configuration and descriptor queries return host libusb errors. `sceUsbdGetStringDescriptorAscii` rebuilds `libusb20_dev_req_string_simple_sync` with only its 255-byte read: libusb's Windows HID backend reports the requested length as `bLength`, cutting a 4-byte header read to one character. Unlike the host `libusb_get_string_descriptor_ascii` it accepts index 0, drops non-printable characters and `<>` instead of writing `?`, and fails with `0x802400FF`. Closes when: measured on a PS5 +- [sceUsbdGetDevice, sceUsbdGetDeviceSpeed, sceUsbdGetMaxPacketSize, sceUsbdGetMaxIsoPacketSize, sceUsbdOpenDeviceWithVidPid](../../../core/libs/prx/libSceUsbd/Export.cpp) (libSceUsbd) - not measured on a console; they follow FreeBSD 11.4 [libusb10.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libusb/libusb10.c) over the host libusb. The packet sizes are searched in the active configuration and the iso size is scaled as FreeBSD does, not through the host's versions, which differ for an unreadable configuration and for SuperSpeed endpoints. A host SuperSpeed+ device reports SuperSpeed, the fastest speed FreeBSD 11.4 has. A null device in `sceUsbdGetMaxIsoPacketSize` throws, as FreeBSD dereferences it. A device stays known while a handle to it is open, so the device of a handle from `sceUsbdOpenDeviceWithVidPid` can be used until that handle is closed. Closes when: measured on a PS5 +- [sceUsbdSetInterfaceAltSetting, sceUsbdClearHalt, sceUsbdDetachKernelDriver](../../../core/libs/prx/libSceUsbd/Export.cpp) (libSceUsbd) - not measured on a console; they follow FreeBSD 11.4 [libusb10.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libusb/libusb10.c) over the host libusb. `sceUsbdSetInterfaceAltSetting` checks the interface range and this library's record of claimed interfaces, and returns `0x802400FF` for any other failure; it throws while the device has transfers in flight, which FreeBSD cancels first. `sceUsbdClearHalt` returns 0, or `0x802400FF` for an endpoint the host does not find; FreeBSD ignores the result of the clear itself, so the host's other errors throw, and so does a null handle, which FreeBSD dereferences. `sceUsbdDetachKernelDriver` returns `0x802400FF` for any failure, which on Windows is every call, as libusb cannot detach drivers there. Closes when: measured on a PS5