fix(libSceUsbd): cancel transfers under the state lock

This commit is contained in:
Semir Kabir 2026-10-11 18:59:35 +00:00 committed by Adrià Franch
parent 98d594c9c1
commit faff4a6453
2 changed files with 144 additions and 9 deletions

View File

@ -22,7 +22,11 @@ namespace {
constexpr std::int32_t SCE_USBD_ERROR_INVALID_ARG = static_cast<std::int32_t>(0x80240002);
constexpr std::int32_t SCE_USBD_ERROR_NOT_FOUND = static_cast<std::int32_t>(0x80240005);
constexpr std::int32_t SCE_USBD_ERROR_OTHER = static_cast<std::int32_t>(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<std::uint8_t>(value);
buffer[3] = static_cast<std::uint8_t>(value >> 8);
buffer[4] = static_cast<std::uint8_t>(index);
buffer[5] = static_cast<std::uint8_t>(index >> 8);
buffer[6] = static_cast<std::uint8_t>(length);
buffer[7] = static_cast<std::uint8_t>(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<std::uint32_t>(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() {

View File

@ -3,6 +3,7 @@
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <atomic>
#include <cstdlib>
#include <cstring>
#include <string>
@ -91,6 +92,11 @@ void APS5_VABI MarkDone(HostTransfer* transfer) {
*static_cast<int*>(transfer->userData) += 1;
}
void APS5_VABI MarkDoneThenSignal(HostTransfer* transfer) {
auto* notified = static_cast<std::atomic<bool>*>(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<std::int32_t>(0x80240005), "releasing an unclaimed interface did not report not found");
const std::uint8_t* config0 = static_cast<const std::uint8_t*>(config);
const std::uint8_t* interfaceArray = *reinterpret_cast<const std::uint8_t* const*>(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<const std::uint8_t* const*>(interfaceArray + index * 16);
const std::uint8_t* endpoints = *reinterpret_cast<const std::uint8_t* const*>(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<std::uint16_t>(item[4] | item[5] << 8);
}
}
}
Require(interruptAddress != 0 && interruptMax != 0, "the device has no interrupt IN endpoint");
sceUsbdFreeConfigDescriptor(config);
auto* transfer = static_cast<HostTransfer*>(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<bool> notified{false};
std::uint8_t blink[64] = {};
sceUsbdFillInterruptTransfer(transfer, handle, interruptAddress, blink, interruptMax, reinterpret_cast<void*>(&MarkDoneThenSignal), &notified, 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<std::int32_t>(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<std::int32_t>(0x80240005), "cancelling a completed transfer did not report not found");
sceUsbdFreeTransfer(transfer);
sceUsbdClose(handle);