Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 96 additions & 20 deletions core/libs/prx/libSceUsbd/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <libusb.h>

#include <algorithm>
#include <chrono>
#include <climits>
#include <cstdlib>
Expand Down Expand Up @@ -47,6 +48,7 @@ struct UsbdState {
std::unordered_map<libusb_device*, std::uint32_t> devices;
std::unordered_set<libusb_device**> lists;
std::unordered_set<libusb_device_handle*> handles;
std::unordered_map<libusb_device_handle*, std::uint32_t> claimed;
std::unordered_set<libusb_config_descriptor*> configs;
std::unordered_map<UsbdTransfer*, libusb_transfer*> inFlight;
};
Expand All @@ -72,11 +74,15 @@ std::int32_t ToGuestError(int result) {
return static_cast<std::int32_t>(0x80240000u - static_cast<std::uint32_t>(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<libusb_device*>(device);
if (!state.devices.contains(host)) throw std::runtime_error(AbsentDevice(function));
if (!IsKnownDevice(state, host)) throw std::runtime_error(AbsentDevice(function));
return host;
}

Expand Down Expand Up @@ -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<std::uint16_t>(LIBUSB_DT_STRING << 8 | descriptorIndex);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -367,6 +402,7 @@ void APS5_VABI sceUsbdClose(void* deviceHandle) {
std::lock_guard lock(state.mutex);
auto* host = static_cast<libusb_device_handle*>(deviceHandle);
if (state.handles.erase(host) == 0) throw std::runtime_error(UnopenedHandle(__func__));
state.claimed.erase(host);
libusb_close(host);
}

Expand Down Expand Up @@ -449,14 +485,21 @@ void* APS5_VABI sceUsbdRefDevice(void* device) {
auto& state = State();
std::lock_guard lock(state.mutex);
auto* host = static_cast<libusb_device*>(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) {
Expand Down Expand Up @@ -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<std::uint8_t>(interfaceNumber)) == 0 ? 0 : SCE_USBD_ERROR_OTHER;
}

std::int32_t APS5_VABI sceUsbdEventHandlerActive() {
Expand Down Expand Up @@ -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<int>(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) {
Expand Down Expand Up @@ -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<std::uint8_t>(alternateSetting)) == 0 ? 0 : SCE_USBD_ERROR_OTHER;
}

std::int32_t APS5_VABI sceUsbdTryLockEvents() {
Expand Down
54 changes: 54 additions & 0 deletions core/libs/tests/GuestUsbd.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "prx/libSceUsbd/include/Api.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"

#include <algorithm>
#include <chrono>
#include <cstdint>
#include <cstdio>
Expand All @@ -18,6 +19,10 @@ namespace {
namespace {

constexpr std::int32_t invalidArgument = static_cast<std::int32_t>(0x80240002);
constexpr std::int32_t noDevice = static_cast<std::int32_t>(0x80240004);
constexpr std::int32_t notFound = static_cast<std::int32_t>(0x80240005);
constexpr std::int32_t otherError = static_cast<std::int32_t>(0x802400FF);
constexpr std::uint8_t absentEndpoint = 0x70;

void Require(bool condition, const char* message) {
if (!condition) {
Expand Down Expand Up @@ -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] = {};
Expand Down Expand Up @@ -131,6 +140,7 @@ void Device() {
const std::uint8_t* interfaceArray = *reinterpret_cast<const std::uint8_t* const*>(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");
}
Expand All @@ -144,11 +154,29 @@ void Device() {
if ((item[2] & 0x80) != 0 && (item[3] & 3) == 3) {
interruptAddress = item[2];
interruptMax = static_cast<std::uint16_t>(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<UsbdTransfer*>(sceUsbdAllocTransfer(0));
std::uint8_t setup[8 + 18] = {0x80, 6, 0x00, 0x01, 0x00, 0x00, 18, 0};
Expand Down Expand Up @@ -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<std::uint16_t>(vendor), static_cast<std::uint16_t>(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");
}
Expand Down Expand Up @@ -291,6 +329,21 @@ int main(int argc, char** argv) {

Require(Throws([&] { sceUsbdSubmitTransfer(reinterpret_cast<UsbdTransfer*>(transfer)); }), "submitting on a handle that was never opened did not throw");
sceUsbdFreeTransfer(reinterpret_cast<UsbdTransfer*>(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");
Expand All @@ -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;
Expand Down
Loading
Loading