diff --git a/core/libs/prx/libSceUsbd/Export.cpp b/core/libs/prx/libSceUsbd/Export.cpp index 7da907bf8..951809296 100644 --- a/core/libs/prx/libSceUsbd/Export.cpp +++ b/core/libs/prx/libSceUsbd/Export.cpp @@ -22,7 +22,11 @@ namespace { constexpr std::int32_t SCE_USBD_ERROR_INVALID_ARG = static_cast(0x80240002); constexpr std::int32_t SCE_USBD_ERROR_NOT_FOUND = static_cast(0x80240005); constexpr std::int32_t SCE_USBD_ERROR_OTHER = static_cast(0x802400FF); +constexpr std::uint8_t TRANSFER_TYPE_CONTROL = 0; +constexpr std::uint8_t TRANSFER_TYPE_ISOCHRONOUS = 1; +constexpr std::uint8_t TRANSFER_TYPE_BULK = 2; constexpr std::uint8_t TRANSFER_TYPE_INTERRUPT = 3; +constexpr std::int32_t CONTROL_SETUP_SIZE = 8; constexpr std::uint8_t SUPPORTED_TRANSFER_FLAGS = LIBUSB_TRANSFER_SHORT_NOT_OK | LIBUSB_TRANSFER_ADD_ZERO_PACKET; struct UsbdTimeval { @@ -249,17 +253,99 @@ void APS5_VABI sceUsbdFillInterruptTransfer(UsbdTransfer* transfer, void* device transfer->callback = callback; } +void APS5_VABI sceUsbdFillBulkTransfer(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_BULK; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->userData = userData; + transfer->callback = callback; +} + +void APS5_VABI sceUsbdFillIsoTransfer(UsbdTransfer* transfer, void* deviceHandle, std::uint8_t endpoint, std::uint8_t* buffer, std::int32_t length, std::int32_t numIsoPackets, 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_ISOCHRONOUS; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->numIsoPackets = numIsoPackets; + transfer->userData = userData; + transfer->callback = callback; +} + +void APS5_VABI sceUsbdFillControlSetup(std::uint8_t* buffer, std::uint8_t requestType, std::uint8_t request, std::uint16_t value, std::uint16_t index, std::uint16_t length) { + if (buffer == nullptr) throw std::runtime_error(std::string(__func__) + ": null buffer"); + buffer[0] = requestType; + buffer[1] = request; + buffer[2] = static_cast(value); + buffer[3] = static_cast(value >> 8); + buffer[4] = static_cast(index); + buffer[5] = static_cast(index >> 8); + buffer[6] = static_cast(length); + buffer[7] = static_cast(length >> 8); +} + +void APS5_VABI sceUsbdFillControlTransfer(UsbdTransfer* transfer, void* deviceHandle, std::uint8_t* buffer, 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 = 0; + transfer->type = TRANSFER_TYPE_CONTROL; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = buffer != nullptr ? CONTROL_SETUP_SIZE + (buffer[6] | buffer[7] << 8) : 0; + transfer->userData = userData; + transfer->callback = callback; +} + +std::uint8_t* APS5_VABI sceUsbdControlTransferGetData(UsbdTransfer* transfer) { + if (transfer == nullptr) throw std::runtime_error(std::string(__func__) + ": null transfer"); + return transfer->buffer != nullptr ? transfer->buffer + CONTROL_SETUP_SIZE : nullptr; +} + +std::uint8_t* APS5_VABI sceUsbdControlTransferGetSetup(UsbdTransfer* transfer) { + if (transfer == nullptr) throw std::runtime_error(std::string(__func__) + ": null transfer"); + return transfer->buffer; +} + +void APS5_VABI sceUsbdSetIsoPacketLengths(UsbdTransfer* transfer, std::uint32_t length) { + if (transfer == nullptr) throw std::runtime_error(std::string(__func__) + ": null transfer"); + for (std::int32_t packet = 0; packet < transfer->numIsoPackets; ++packet) transfer->isoPacketDescriptors[packet].length = length; +} + +std::uint8_t* APS5_VABI sceUsbdGetIsoPacketBuffer(UsbdTransfer* transfer, std::uint32_t packet) { + if (transfer == nullptr) throw std::runtime_error(std::string(__func__) + ": null transfer"); + if (transfer->numIsoPackets < 0 || packet >= static_cast(transfer->numIsoPackets) || transfer->buffer == nullptr) return nullptr; + std::uint8_t* pointer = transfer->buffer; + for (std::uint32_t index = 0; index < packet; ++index) pointer += transfer->isoPacketDescriptors[index].length; + return pointer; +} + +std::int32_t APS5_VABI sceUsbdBulkTransfer(void* deviceHandle, std::uint8_t endpoint, std::uint8_t* data, std::int32_t length, std::int32_t* transferred, std::uint32_t timeout) { + if (deviceHandle == nullptr || (length != 0 && data == nullptr)) return SCE_USBD_ERROR_INVALID_ARG; + if (transferred == nullptr) throw std::runtime_error(std::string(__func__) + ": null transferred output"); + *transferred = 0; + return ToGuestError(libusb_bulk_transfer(KnownHandle(deviceHandle, __func__), endpoint, data, length, transferred, timeout)); +} + +std::int32_t APS5_VABI sceUsbdInterruptTransfer(void* deviceHandle, std::uint8_t endpoint, std::uint8_t* data, std::int32_t length, std::int32_t* transferred, std::uint32_t timeout) { + if (deviceHandle == nullptr || (length != 0 && data == nullptr)) return SCE_USBD_ERROR_INVALID_ARG; + if (transferred == nullptr) throw std::runtime_error(std::string(__func__) + ": null transferred output"); + *transferred = 0; + return ToGuestError(libusb_interrupt_transfer(KnownHandle(deviceHandle, __func__), endpoint, data, length, transferred, timeout)); +} + std::int32_t APS5_VABI sceUsbdCancelTransfer(UsbdTransfer* transfer) { if (transfer == nullptr) throw std::runtime_error(std::string(__func__) + ": null transfer"); - libusb_transfer* host; - { - auto& state = State(); - std::lock_guard lock(state.mutex); - const auto found = state.inFlight.find(transfer); - if (found == state.inFlight.end()) return SCE_USBD_ERROR_NOT_FOUND; - host = found->second; - } - return ToGuestError(libusb_cancel_transfer(host)); + auto& state = State(); + std::lock_guard lock(state.mutex); + const auto found = state.inFlight.find(transfer); + if (found == state.inFlight.end()) return SCE_USBD_ERROR_NOT_FOUND; + return ToGuestError(libusb_cancel_transfer(found->second)); } std::int32_t APS5_VABI sceUsbdEventHandlingOk() { diff --git a/core/libs/tests/GuestUsbd.cpp b/core/libs/tests/GuestUsbd.cpp index f329cf814..197964110 100644 --- a/core/libs/tests/GuestUsbd.cpp +++ b/core/libs/tests/GuestUsbd.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -91,6 +92,11 @@ void APS5_VABI MarkDone(HostTransfer* transfer) { *static_cast(transfer->userData) += 1; } +void APS5_VABI MarkDoneThenSignal(HostTransfer* transfer) { + auto* notified = static_cast*>(transfer->userData); + notified->store(true, std::memory_order_release); +} + void Pump(const int& done) { const UsbdTimeval slice{0, 100000}; for (int round = 0; round < 50 && done == 0; ++round) Require(sceUsbdHandleEventsTimeout(&slice) == 0, "event handling failed while a transfer was in flight"); @@ -142,6 +148,24 @@ void Device() { Require(sceUsbdReleaseInterface(handle, interfaceNumber) == 0, "releasing an interface failed"); } Require(sceUsbdReleaseInterface(handle, 0) == static_cast(0x80240005), "releasing an unclaimed interface did not report not found"); + const std::uint8_t* config0 = static_cast(config); + const std::uint8_t* interfaceArray = *reinterpret_cast(config0 + 16); + std::uint8_t interruptAddress = 0; + std::uint16_t interruptMax = 0; + for (std::uint8_t index = 0; index < interfaces && interruptAddress == 0; ++index) { + const std::uint8_t* alternate = *reinterpret_cast(interfaceArray + index * 16); + const std::uint8_t* endpoints = *reinterpret_cast(alternate + 16); + for (std::uint8_t endpoint = 0; endpoint < alternate[4]; ++endpoint) { + const std::uint8_t* item = endpoints + endpoint * 32; + if ((item[2] & 0x80) != 0 && (item[3] & 3) == 3) { + interruptAddress = item[2]; + interruptMax = static_cast(item[4] | item[5] << 8); + } + } + } + Require(interruptAddress != 0 && interruptMax != 0, "the device has no interrupt IN endpoint"); + sceUsbdFreeConfigDescriptor(config); + auto* transfer = static_cast(sceUsbdAllocTransfer(0)); std::uint8_t setup[8 + 18] = {0x80, 6, 0x00, 0x01, 0x00, 0x00, 18, 0}; int done = 0; @@ -157,6 +181,31 @@ void Device() { Pump(done); Require(done == 1 && transfer->status == 0 && transfer->actualLength == 18, "asynchronous control transfer did not complete"); Require(std::memcmp(setup + 8, descriptor, 18) == 0, "asynchronous device descriptor differs from the cached one"); + bool broken = false; + { + const UsbdTimeval slice{0, 200}; + for (int round = 0; round < 300 && !broken; ++round) { + std::atomic notified{false}; + std::uint8_t blink[64] = {}; + sceUsbdFillInterruptTransfer(transfer, handle, interruptAddress, blink, interruptMax, reinterpret_cast(&MarkDoneThenSignal), ¬ified, 0); + const int submitResult = sceUsbdSubmitTransfer(transfer); + if (submitResult != 0) { + broken = true; + break; + } + std::thread canceller([&] { + const int outcome = sceUsbdCancelTransfer(transfer); + if (outcome != 0 && outcome != static_cast(0x80240005)) broken = true; + }); + while (!notified.load(std::memory_order_acquire)) { + if (sceUsbdHandleEventsTimeout(&slice) != 0) broken = true; + } + canceller.join(); + if (transfer->status != 0 && transfer->status != 2 && transfer->status != 3) broken = true; + } + } + Require(!broken, "the cancel race failed"); + Require(sceUsbdCancelTransfer(transfer) == static_cast(0x80240005), "cancelling a completed transfer did not report not found"); sceUsbdFreeTransfer(transfer); sceUsbdClose(handle);