fix(libSceUsbd): cancel transfers under the state lock
This commit is contained in:
parent
98d594c9c1
commit
faff4a6453
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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), ¬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<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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue