mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-06 15:01:28 +12:00
1614 lines
48 KiB
C++
1614 lines
48 KiB
C++
#include "stdafx.h"
|
|
#include "sys_usbd.h"
|
|
#include "sys_ppu_thread.h"
|
|
#include "sys_sync.h"
|
|
|
|
#include <queue>
|
|
#include "Emu/System.h"
|
|
#include "Emu/system_config.h"
|
|
#include "Emu/IdManager.h"
|
|
#include "Emu/vfs_config.h"
|
|
|
|
#include "Emu/Cell/PPUThread.h"
|
|
#include "Emu/Cell/ErrorCodes.h"
|
|
#include "Emu/Cell/timers.hpp"
|
|
|
|
#include "Emu/Io/usb_device.h"
|
|
#include "Emu/Io/usb_vfs.h"
|
|
#include "Emu/Io/Skylander.h"
|
|
#include "Emu/Io/Infinity.h"
|
|
#include "Emu/Io/Dimensions.h"
|
|
#include "Emu/Io/GHLtar.h"
|
|
#include "Emu/Io/ghltar_config.h"
|
|
#include "Emu/Io/guncon3_config.h"
|
|
#include "Emu/Io/topshotelite_config.h"
|
|
#include "Emu/Io/topshotfearmaster_config.h"
|
|
#include "Emu/Io/Buzz.h"
|
|
#include "Emu/Io/buzz_config.h"
|
|
#include "Emu/Io/GameTablet.h"
|
|
#include "Emu/Io/GunCon3.h"
|
|
#include "Emu/Io/TopShotElite.h"
|
|
#include "Emu/Io/TopShotFearmaster.h"
|
|
#include "Emu/Io/Turntable.h"
|
|
#include "Emu/Io/turntable_config.h"
|
|
#include "Emu/Io/RB3MidiKeyboard.h"
|
|
#include "Emu/Io/RB3MidiGuitar.h"
|
|
#include "Emu/Io/RB3MidiDrums.h"
|
|
#include "Emu/Io/rb3drums_config.h"
|
|
#include "Emu/Io/usio.h"
|
|
#include "Emu/Io/usio_config.h"
|
|
#include "Emu/Io/midi_config_types.h"
|
|
#ifdef HAVE_SDL3
|
|
#include "Emu/Io/LogitechG27.h"
|
|
#endif
|
|
|
|
#include <libusb.h>
|
|
|
|
LOG_CHANNEL(sys_usbd);
|
|
|
|
cfg_buzz g_cfg_buzz;
|
|
cfg_ghltars g_cfg_ghltar;
|
|
cfg_turntables g_cfg_turntable;
|
|
cfg_usios g_cfg_usio;
|
|
cfg_guncon3 g_cfg_guncon3;
|
|
cfg_topshotelite g_cfg_topshotelite;
|
|
cfg_topshotfearmaster g_cfg_topshotfearmaster;
|
|
|
|
template <>
|
|
void fmt_class_string<libusb_transfer>::format(std::string& out, u64 arg)
|
|
{
|
|
const auto& transfer = get_object(arg);
|
|
const int data_start = transfer.type == LIBUSB_TRANSFER_TYPE_CONTROL ? LIBUSB_CONTROL_SETUP_SIZE : 0;
|
|
fmt::append(out, "TR[r:%d][sz:%d] => %s", +transfer.status, transfer.actual_length, fmt::buf_to_hexstring(&transfer.buffer[data_start], transfer.actual_length));
|
|
}
|
|
|
|
struct UsbLdd
|
|
{
|
|
u16 id_vendor{};
|
|
u16 id_product_min{};
|
|
u16 id_product_max{};
|
|
};
|
|
|
|
struct UsbPipe
|
|
{
|
|
std::shared_ptr<usb_device> device = nullptr;
|
|
|
|
u8 endpoint = 0;
|
|
};
|
|
|
|
struct usb_allow_list_entry
|
|
{
|
|
u16 id_vendor;
|
|
u16 id_product_min;
|
|
u16 id_product_max;
|
|
std::string_view device_name;
|
|
u16(*max_device_count)(void);
|
|
std::shared_ptr<usb_device>(*make_instance)(u32, const std::array<u8, 7>&);
|
|
auto operator<(const usb_allow_list_entry& r) const
|
|
{
|
|
return std::tuple(id_vendor, id_product_min, id_product_max, device_name, max_device_count, make_instance) < std::tuple(r.id_vendor, r.id_product_min, r.id_product_max, device_name, max_device_count, make_instance);
|
|
}
|
|
};
|
|
class usb_handler_thread
|
|
{
|
|
public:
|
|
usb_handler_thread();
|
|
~usb_handler_thread();
|
|
|
|
SAVESTATE_INIT_POS(14);
|
|
|
|
usb_handler_thread(utils::serial& ar) : usb_handler_thread()
|
|
{
|
|
is_init = !!ar.pop<u8>();
|
|
}
|
|
|
|
void save(utils::serial& ar)
|
|
{
|
|
ar(u8{is_init.load()});
|
|
}
|
|
|
|
// Thread loop
|
|
void operator()();
|
|
|
|
// Called by the libusb callback function to notify transfer completion
|
|
void transfer_complete(libusb_transfer* transfer);
|
|
|
|
// LDDs handling functions
|
|
bool add_ldd(std::string_view product, u16 id_vendor, u16 id_product_min, u16 id_product_max);
|
|
bool remove_ldd(std::string_view product);
|
|
|
|
// Pipe functions
|
|
u32 open_pipe(u32 device_handle, u8 endpoint);
|
|
bool close_pipe(u32 pipe_id);
|
|
bool is_pipe(u32 pipe_id) const;
|
|
const UsbPipe& get_pipe(u32 pipe_id) const;
|
|
|
|
// Events related functions
|
|
bool get_event(vm::ptr<u64>& arg1, vm::ptr<u64>& arg2, vm::ptr<u64>& arg3);
|
|
void add_event(u64 arg1, u64 arg2, u64 arg3);
|
|
|
|
// Transfers related functions
|
|
std::pair<u32, UsbTransfer&> get_free_transfer();
|
|
std::pair<u32, u32> get_transfer_status(u32 transfer_id);
|
|
std::pair<u32, UsbDeviceIsoRequest> get_isochronous_transfer_status(u32 transfer_id);
|
|
void push_fake_transfer(UsbTransfer* transfer);
|
|
|
|
const std::array<u8, 7>& get_new_location();
|
|
void connect_usb_device(std::shared_ptr<usb_device> dev, bool update_usb_devices = false);
|
|
void disconnect_usb_device(std::shared_ptr<usb_device> dev, bool update_usb_devices = false);
|
|
|
|
// Map of devices actively handled by the ps3(device_id, device)
|
|
std::map<u32, std::pair<UsbInternalDevice, std::shared_ptr<usb_device>>> handled_devices;
|
|
std::map<u8, std::pair<input::product_type, std::shared_ptr<usb_device>>> pad_to_usb;
|
|
|
|
shared_mutex mutex;
|
|
atomic_t<bool> is_init = false;
|
|
|
|
// sys_usbd_receive_event PPU Threads
|
|
shared_mutex mutex_sq;
|
|
ppu_thread* sq{};
|
|
|
|
atomic_t<u64> usb_hotplug_timeout = umax;
|
|
|
|
static constexpr auto thread_name = "Usb Manager Thread"sv;
|
|
|
|
private:
|
|
// Lock free functions for internal use(ie make sure to lock before using those)
|
|
UsbTransfer& get_transfer(u32 transfer_id);
|
|
u32 get_free_transfer_id();
|
|
|
|
void send_message(u32 message, u32 tr_id);
|
|
void perform_scan();
|
|
|
|
private:
|
|
// Counters for device IDs, transfer IDs and pipe IDs
|
|
atomic_t<u8> dev_counter = 1;
|
|
u32 transfer_counter = 0;
|
|
u32 pipe_counter = 0x10; // Start at 0x10 only for tracing purposes
|
|
|
|
// List of device drivers
|
|
std::unordered_map<std::string, UsbLdd, fmt::string_hash, std::equal_to<>> ldds;
|
|
|
|
const std::vector<usb_allow_list_entry> device_allow_list
|
|
{
|
|
// Portals
|
|
{0x1430, 0x0150, 0x0150, "Skylanders Portal", &usb_device_skylander::get_num_emu_devices, &usb_device_skylander::make_instance},
|
|
{0x0E6F, 0x0129, 0x0129, "Disney Infinity Base", &usb_device_infinity::get_num_emu_devices, &usb_device_infinity::make_instance},
|
|
{0x0E6F, 0x0241, 0x0241, "Lego Dimensions Portal", &usb_device_dimensions::get_num_emu_devices, &usb_device_dimensions::make_instance},
|
|
{0x0E6F, 0x200A, 0x200A, "Kamen Rider Summonride Portal", nullptr, nullptr},
|
|
|
|
// Cameras
|
|
// {0x1415, 0x0020, 0x2000, "Sony Playstation Eye", nullptr, nullptr}, // TODO: verifiy
|
|
|
|
// Music devices
|
|
{0x1415, 0x0000, 0x0000, "Singstar Microphone", nullptr, nullptr},
|
|
// {0x1415, 0x0020, 0x0020, "SingStar Microphone Wireless", nullptr, nullptr}, // TODO: verifiy
|
|
|
|
{0x12BA, 0x00FF, 0x00FF, "Rocksmith Guitar Adapter", nullptr, nullptr},
|
|
{0x12BA, 0x0100, 0x0100, "Guitar Hero Guitar", nullptr, nullptr},
|
|
{0x12BA, 0x0120, 0x0120, "Guitar Hero Drums", nullptr, nullptr},
|
|
{0x12BA, 0x074B, 0x074B, "Guitar Hero Live Guitar", &usb_device_ghltar::get_num_emu_devices, &usb_device_ghltar::make_instance},
|
|
|
|
{0x12BA, 0x0140, 0x0140, "DJ Hero Turntable", &usb_device_turntable::get_num_emu_devices, &usb_device_turntable::make_instance},
|
|
{0x12BA, 0x0200, 0x020F, "Harmonix Guitar", nullptr, nullptr},
|
|
{0x12BA, 0x0210, 0x021F, "Harmonix Drums", nullptr, nullptr},
|
|
{0x12BA, 0x2330, 0x233F, "Harmonix Keyboard", nullptr, nullptr},
|
|
{0x12BA, 0x2430, 0x243F, "Harmonix Button Guitar", nullptr, nullptr},
|
|
{0x12BA, 0x2530, 0x253F, "Harmonix Real Guitar", nullptr, nullptr},
|
|
|
|
{0x1BAD, 0x0004, 0x0004, "Harmonix RB1 Guitar - Wii", nullptr, nullptr},
|
|
{0x1BAD, 0x0005, 0x0005, "Harmonix RB1 Drums - Wii", nullptr, nullptr},
|
|
{0x1BAD, 0x3010, 0x301F, "Harmonix RB2 Guitar - Wii", nullptr, nullptr},
|
|
{0x1BAD, 0x3110, 0x313F, "Harmonix RB2 Drums - Wii", nullptr, nullptr},
|
|
{0x1BAD, 0x3330, 0x333F, "Harmonix Keyboard - Wii", nullptr, nullptr},
|
|
{0x1BAD, 0x3430, 0x343F, "Harmonix Button Guitar - Wii", nullptr, nullptr},
|
|
{0x1BAD, 0x3530, 0x353F, "Harmonix Real Guitar - Wii", nullptr, nullptr},
|
|
|
|
//Top Shot Elite controllers
|
|
{0x12BA, 0x04A0, 0x04A0, "Top Shot Elite", nullptr, nullptr},
|
|
{0x12BA, 0x04A1, 0x04A1, "Top Shot Fearmaster", nullptr, nullptr},
|
|
{0x12BA, 0x04B0, 0x04B0, "Rapala Fishing Rod", nullptr, nullptr},
|
|
|
|
|
|
// GT5 Wheels&co
|
|
#ifdef HAVE_SDL3
|
|
{0x046D, 0xC283, 0xC29B, "lgFF_c283_c29b", &usb_device_logitech_g27::get_num_emu_devices, &usb_device_logitech_g27::make_instance},
|
|
#else
|
|
{0x046D, 0xC283, 0xC29B, "lgFF_c283_c29b", nullptr, nullptr},
|
|
#endif
|
|
{0x044F, 0xB653, 0xB653, "Thrustmaster RGT FFB Pro", nullptr, nullptr},
|
|
{0x044F, 0xB65A, 0xB65A, "Thrustmaster F430", nullptr, nullptr},
|
|
{0x044F, 0xB65D, 0xB65D, "Thrustmaster FFB", nullptr, nullptr},
|
|
{0x044F, 0xB65E, 0xB65E, "Thrustmaster TRS", nullptr, nullptr},
|
|
{0x044F, 0xB660, 0xB660, "Thrustmaster T500 RS Gear Shift", nullptr, nullptr},
|
|
|
|
// GT6
|
|
{0x2833, 0x0001, 0x0001, "Oculus", nullptr, nullptr},
|
|
{0x046D, 0xCA03, 0xCA03, "lgFF_ca03_ca03", nullptr, nullptr},
|
|
|
|
// Buzz controllers
|
|
{0x054C, 0x1000, 0x1040, "buzzer0", &usb_device_buzz::get_num_emu_devices, &usb_device_buzz::make_instance},
|
|
{0x054C, 0x0001, 0x0041, "buzzer1", nullptr, nullptr},
|
|
{0x054C, 0x0042, 0x0042, "buzzer2", nullptr, nullptr},
|
|
{0x046D, 0xC220, 0xC220, "buzzer9", nullptr, nullptr},
|
|
|
|
// GCon3 Gun
|
|
{0x0B9A, 0x0800, 0x0800, "guncon3", nullptr, nullptr},
|
|
|
|
// uDraw GameTablet
|
|
{0x20D6, 0xCB17, 0xCB17, "uDraw GameTablet", nullptr, nullptr},
|
|
|
|
// DVB-T
|
|
{0x1415, 0x0003, 0x0003, "PlayTV SCEH-0036", nullptr, nullptr},
|
|
|
|
// PSP Devices
|
|
{0x054C, 0x01C8, 0x01C8, "PSP Type A", nullptr, nullptr},
|
|
{0x054C, 0x01C9, 0x01C9, "PSP Type B", nullptr, nullptr},
|
|
{0x054C, 0x01CA, 0x01CA, "PSP Type C", nullptr, nullptr},
|
|
{0x054C, 0x01CB, 0x01CB, "PSP Type D", nullptr, nullptr},
|
|
{0x054C, 0x02D2, 0x02D2, "PSP Slim", nullptr, nullptr},
|
|
|
|
// 0x0900: "H050 USJ(C) PCB rev00", 0x0910: "USIO PCB rev00"
|
|
{0x0B9A, 0x0900, 0x0910, "PS3A-USJ", &usb_device_usio::get_num_emu_devices, &usb_device_usio::make_instance},
|
|
|
|
// Densha de GO! controller
|
|
{0x0AE4, 0x0004, 0x0004, "Densha de GO! Type 2 Controller", nullptr, nullptr},
|
|
|
|
// EA Active 2 dongle for connecting wristbands & legband
|
|
{0x21A4, 0xAC27, 0xAC27, "EA Active 2 Dongle", nullptr, nullptr},
|
|
|
|
// Tony Hawk RIDE Skateboard
|
|
{0x12BA, 0x0400, 0x0400, "Tony Hawk RIDE Skateboard Controller", nullptr, nullptr},
|
|
|
|
// PSP in UsbPspCm mode
|
|
{0x054C, 0x01CB, 0x01CB, "UsbPspcm", nullptr, nullptr},
|
|
|
|
// Sony Stereo Headsets
|
|
{0x12BA, 0x0032, 0x0032, "Wireless Stereo Headset", nullptr, nullptr},
|
|
{0x12BA, 0x0042, 0x0042, "Wireless Stereo Headset", nullptr, nullptr},
|
|
};
|
|
|
|
// List of pipes
|
|
std::map<u32, UsbPipe> open_pipes;
|
|
// Transfers infos
|
|
shared_mutex mutex_transfers;
|
|
std::array<UsbTransfer, MAX_SYS_USBD_TRANSFERS> transfers;
|
|
std::vector<UsbTransfer*> fake_transfers;
|
|
|
|
// Queue of pending usbd events
|
|
std::queue<std::tuple<u64, u64, u64>> usbd_events;
|
|
|
|
// List of devices "connected" to the ps3
|
|
std::array<u8, 7> location{};
|
|
std::vector<std::shared_ptr<usb_device>> usb_devices;
|
|
std::unordered_map<uint64_t, std::shared_ptr<usb_device>> usb_passthrough_devices;
|
|
|
|
libusb_context* ctx = nullptr;
|
|
|
|
#ifndef _WIN32
|
|
#if LIBUSB_API_VERSION >= 0x01000102
|
|
libusb_hotplug_callback_handle callback_handle {};
|
|
#endif
|
|
#endif
|
|
|
|
bool hotplug_supported = false;
|
|
};
|
|
|
|
void LIBUSB_CALL callback_transfer(struct libusb_transfer* transfer)
|
|
{
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
if (!usbh.is_init)
|
|
return;
|
|
|
|
usbh.transfer_complete(transfer);
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
#if LIBUSB_API_VERSION >= 0x01000102
|
|
static int LIBUSB_CALL hotplug_callback(libusb_context* /*ctx*/, libusb_device * /*dev*/, libusb_hotplug_event event, void * /*user_data*/)
|
|
{
|
|
handle_hotplug_event(event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED);
|
|
return 0;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if LIBUSB_API_VERSION >= 0x0100010A
|
|
static void LIBUSB_CALL log_cb(libusb_context* /*ctx*/, enum libusb_log_level level, const char* str)
|
|
{
|
|
if (!str)
|
|
return;
|
|
|
|
const std::string msg = fmt::trim(str, " \t\n");
|
|
|
|
switch (level)
|
|
{
|
|
case LIBUSB_LOG_LEVEL_ERROR:
|
|
sys_usbd.error("libusb log: %s", msg);
|
|
break;
|
|
case LIBUSB_LOG_LEVEL_WARNING:
|
|
sys_usbd.warning("libusb log: %s", msg);
|
|
break;
|
|
case LIBUSB_LOG_LEVEL_INFO:
|
|
sys_usbd.notice("libusb log: %s", msg);
|
|
break;
|
|
case LIBUSB_LOG_LEVEL_DEBUG:
|
|
sys_usbd.trace("libusb log: %s", msg);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void usb_handler_thread::perform_scan()
|
|
{
|
|
// look if any device which we could be interested in is actually connected
|
|
libusb_device** list = nullptr;
|
|
const ssize_t ndev = libusb_get_device_list(ctx, &list);
|
|
std::set<uint64_t> seen_usb_devices;
|
|
|
|
if (ndev < 0)
|
|
{
|
|
sys_usbd.error("Failed to get device list: %s", libusb_error_name(static_cast<s32>(ndev)));
|
|
return;
|
|
}
|
|
|
|
for (ssize_t index = 0; index < ndev; index++)
|
|
{
|
|
libusb_device* dev = list[index];
|
|
libusb_device_descriptor desc;
|
|
if (int res = libusb_get_device_descriptor(dev, &desc); res < 0)
|
|
{
|
|
sys_usbd.error("Failed to get device descriptor: %s", libusb_error_name(res));
|
|
continue;
|
|
}
|
|
|
|
const u8 port = libusb_get_port_number(dev);
|
|
const u8 address = libusb_get_device_address(dev);
|
|
const u64 usb_id = (static_cast<uint64_t>(desc.idVendor) << 48) | (static_cast<uint64_t>(desc.idProduct) << 32) | (static_cast<uint64_t>(port) << 8) | address;
|
|
|
|
seen_usb_devices.insert(usb_id);
|
|
if (usb_passthrough_devices.contains(usb_id))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (const auto& entry : device_allow_list)
|
|
{
|
|
// attach
|
|
if (desc.idVendor == entry.id_vendor
|
|
&& desc.idProduct >= entry.id_product_min
|
|
&& desc.idProduct <= entry.id_product_max)
|
|
{
|
|
sys_usbd.success("Found device: %s", std::basic_string(entry.device_name));
|
|
libusb_ref_device(dev);
|
|
std::shared_ptr<usb_device_passthrough> usb_dev = std::make_shared<usb_device_passthrough>(dev, desc, get_new_location());
|
|
connect_usb_device(usb_dev, true);
|
|
usb_passthrough_devices[usb_id] = usb_dev;
|
|
}
|
|
}
|
|
|
|
if (desc.idVendor == 0x1209 && desc.idProduct == 0x2882)
|
|
{
|
|
sys_usbd.success("Found device: Santroller");
|
|
// Send the device a specific control transfer so that it jumps to a RPCS3 compatible mode
|
|
libusb_device_handle* lusb_handle;
|
|
if (libusb_open(dev, &lusb_handle) == LIBUSB_SUCCESS)
|
|
{
|
|
#ifdef __linux__
|
|
libusb_set_auto_detach_kernel_driver(lusb_handle, true);
|
|
libusb_claim_interface(lusb_handle, 2);
|
|
#endif
|
|
libusb_control_transfer(lusb_handle, +LIBUSB_ENDPOINT_IN | +LIBUSB_REQUEST_TYPE_CLASS | +LIBUSB_RECIPIENT_INTERFACE, 0x01, 0x03f2, 2, nullptr, 0, 5000);
|
|
libusb_close(lusb_handle);
|
|
}
|
|
else
|
|
{
|
|
sys_usbd.error("Unable to open Santroller device, make sure Santroller isn't open in the background.");
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto it = usb_passthrough_devices.begin(); it != usb_passthrough_devices.end();)
|
|
{
|
|
auto& dev = *it;
|
|
// If a device is no longer visible, disconnect it
|
|
if (seen_usb_devices.contains(dev.first))
|
|
{
|
|
++it;
|
|
}
|
|
else
|
|
{
|
|
disconnect_usb_device(dev.second, true);
|
|
it = usb_passthrough_devices.erase(it);
|
|
}
|
|
|
|
}
|
|
libusb_free_device_list(list, 1);
|
|
}
|
|
|
|
usb_handler_thread::usb_handler_thread()
|
|
{
|
|
#if LIBUSB_API_VERSION >= 0x0100010A
|
|
libusb_init_option log_lv_opt{};
|
|
log_lv_opt.option = LIBUSB_OPTION_LOG_LEVEL;
|
|
log_lv_opt.value.ival = LIBUSB_LOG_LEVEL_WARNING;// You can also set the LIBUSB_DEBUG env variable instead
|
|
|
|
libusb_init_option log_cb_opt{};
|
|
log_cb_opt.option = LIBUSB_OPTION_LOG_CB;
|
|
log_cb_opt.value.log_cbval = &log_cb;
|
|
|
|
std::vector<libusb_init_option> options = {
|
|
std::move(log_lv_opt),
|
|
std::move(log_cb_opt)
|
|
};
|
|
|
|
if (int res = libusb_init_context(&ctx, options.data(), static_cast<int>(options.size())); res < 0)
|
|
#else
|
|
if (int res = libusb_init(&ctx); res < 0)
|
|
#endif
|
|
{
|
|
sys_usbd.error("Failed to initialize sys_usbd: %s", libusb_error_name(res));
|
|
return;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
hotplug_supported = true;
|
|
#elif LIBUSB_API_VERSION >= 0x01000102
|
|
if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG))
|
|
{
|
|
if (int res = libusb_hotplug_register_callback(ctx, static_cast<libusb_hotplug_event>(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
|
|
LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), static_cast<libusb_hotplug_flag>(0), LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
|
|
LIBUSB_HOTPLUG_MATCH_ANY, static_cast<libusb_hotplug_callback_fn>(hotplug_callback), nullptr,
|
|
&callback_handle); res < 0)
|
|
{
|
|
sys_usbd.error("Failed to initialize sys_usbd hotplug: %s", libusb_error_name(res));
|
|
}
|
|
else
|
|
{
|
|
hotplug_supported = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
for (u32 index = 0; index < MAX_SYS_USBD_TRANSFERS; index++)
|
|
{
|
|
transfers[index].transfer = libusb_alloc_transfer(8);
|
|
transfers[index].transfer_id = index;
|
|
}
|
|
|
|
if (!g_cfg_usio.load())
|
|
{
|
|
sys_usbd.notice("Could not load usio config. Using defaults.");
|
|
}
|
|
|
|
sys_usbd.notice("USIO config=\n", g_cfg_usio.to_string());
|
|
|
|
if (g_cfg.io.ghltar == ghltar_handler::one_controller || g_cfg.io.ghltar == ghltar_handler::two_controllers)
|
|
{
|
|
if (!g_cfg_ghltar.load())
|
|
{
|
|
sys_usbd.notice("Could not load ghltar config. Using defaults.");
|
|
}
|
|
|
|
sys_usbd.notice("Ghltar config=\n", g_cfg_ghltar.to_string());
|
|
}
|
|
|
|
if (g_cfg.io.turntable == turntable_handler::one_controller || g_cfg.io.turntable == turntable_handler::two_controllers)
|
|
{
|
|
if (!g_cfg_turntable.load())
|
|
{
|
|
sys_usbd.notice("Could not load turntable config. Using defaults.");
|
|
}
|
|
|
|
sys_usbd.notice("Turntable config=\n", g_cfg_turntable.to_string());
|
|
}
|
|
|
|
if (g_cfg.io.buzz == buzz_handler::one_controller || g_cfg.io.buzz == buzz_handler::two_controllers)
|
|
{
|
|
if (!g_cfg_buzz.load())
|
|
{
|
|
sys_usbd.notice("Could not load buzz config. Using defaults.");
|
|
}
|
|
|
|
sys_usbd.notice("Buzz config=\n", g_cfg_buzz.to_string());
|
|
}
|
|
|
|
perform_scan();
|
|
|
|
// Set up emulated devices for any devices that are not already being passed through
|
|
std::map<usb_allow_list_entry, int> passthrough_usb_device_counts;
|
|
for (const auto& dev : usb_devices)
|
|
{
|
|
for (const auto& entry : device_allow_list)
|
|
{
|
|
const u16 idVendor = dev->device._device.idVendor;
|
|
const u16 idProduct = dev->device._device.idProduct;
|
|
if (entry.max_device_count != nullptr && (idVendor == entry.id_vendor && idProduct >= entry.id_product_min && idProduct <= entry.id_product_max))
|
|
{
|
|
passthrough_usb_device_counts[entry]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto& entry : device_allow_list)
|
|
{
|
|
if (entry.max_device_count && entry.make_instance)
|
|
{
|
|
const int count = passthrough_usb_device_counts[entry];
|
|
for (int i = count; i < entry.max_device_count(); i++)
|
|
{
|
|
sys_usbd.success("Emulating device: %s (%d)", std::basic_string(entry.device_name), i + 1);
|
|
auto usb_dev = entry.make_instance(i, get_new_location());
|
|
connect_usb_device(usb_dev, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 8; i++) // Add VFS USB mass storage devices (/dev_usbXXX) to the USB device list
|
|
{
|
|
const auto usb_info = g_cfg_vfs.get_device(g_cfg_vfs.dev_usb, fmt::format("/dev_usb%03d", i));
|
|
if (fs::is_dir(usb_info.path))
|
|
usb_devices.push_back(std::make_shared<usb_device_vfs>(usb_info, get_new_location()));
|
|
}
|
|
|
|
const std::vector<std::string> devices_list = fmt::split(g_cfg.io.midi_devices.to_string(), { "@@@" });
|
|
for (usz index = 0; index < std::min(max_midi_devices, devices_list.size()); index++)
|
|
{
|
|
const midi_device device = midi_device::from_string(::at32(devices_list, index));
|
|
if (device.name.empty()) continue;
|
|
|
|
sys_usbd.notice("Adding Emulated Midi Pro Adapter (type=%s, name=%s)", device.type, device.name);
|
|
|
|
switch (device.type)
|
|
{
|
|
case midi_device_type::guitar:
|
|
usb_devices.push_back(std::make_shared<usb_device_rb3_midi_guitar>(get_new_location(), device.name, false));
|
|
break;
|
|
case midi_device_type::guitar_22fret:
|
|
usb_devices.push_back(std::make_shared<usb_device_rb3_midi_guitar>(get_new_location(), device.name, true));
|
|
break;
|
|
case midi_device_type::keyboard:
|
|
usb_devices.push_back(std::make_shared<usb_device_rb3_midi_keyboard>(get_new_location(), device.name));
|
|
break;
|
|
case midi_device_type::drums:
|
|
if (!g_cfg_rb3drums.load())
|
|
{
|
|
sys_usbd.notice("Could not load rb3drums config. Using defaults.");
|
|
}
|
|
|
|
usb_devices.push_back(std::make_shared<usb_device_rb3_midi_drums>(get_new_location(), device.name));
|
|
|
|
sys_usbd.notice("RB3 drums config=\n", g_cfg_rb3drums.to_string());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
usb_handler_thread::~usb_handler_thread()
|
|
{
|
|
// Ensures shared_ptr<usb_device> are all cleared before terminating libusb
|
|
handled_devices.clear();
|
|
open_pipes.clear();
|
|
usb_devices.clear();
|
|
usb_passthrough_devices.clear();
|
|
|
|
for (u32 index = 0; index < MAX_SYS_USBD_TRANSFERS; index++)
|
|
{
|
|
if (transfers[index].transfer)
|
|
libusb_free_transfer(transfers[index].transfer);
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
#if LIBUSB_API_VERSION >= 0x01000102
|
|
if (ctx && hotplug_supported)
|
|
libusb_hotplug_deregister_callback(ctx, callback_handle);
|
|
#endif
|
|
#endif
|
|
|
|
if (ctx)
|
|
libusb_exit(ctx);
|
|
}
|
|
|
|
void usb_handler_thread::operator()()
|
|
{
|
|
timeval lusb_tv{0, 0};
|
|
if (!hotplug_supported)
|
|
{
|
|
usb_hotplug_timeout = get_system_time() + 4'000'000ull;
|
|
}
|
|
while (ctx && thread_ctrl::state() != thread_state::aborting)
|
|
{
|
|
const u64 now = get_system_time();
|
|
if (now > usb_hotplug_timeout)
|
|
{
|
|
// If we did the hotplug scan each cycle the game performance was significantly degraded, so we only perform this scan
|
|
// every 4 seconds.
|
|
// On systems where hotplug is native, we wait a little bit for devices to settle before we start the scan
|
|
perform_scan();
|
|
usb_hotplug_timeout = hotplug_supported ? umax : get_system_time() + 4'000'000ull;
|
|
}
|
|
|
|
// Process asynchronous requests that are pending
|
|
libusb_handle_events_timeout_completed(ctx, &lusb_tv, nullptr);
|
|
|
|
// Process fake transfers
|
|
if (!fake_transfers.empty())
|
|
{
|
|
std::lock_guard lock_tf(mutex_transfers);
|
|
u64 timestamp = get_system_time() - Emu.GetPauseTime();
|
|
|
|
for (auto it = fake_transfers.begin(); it != fake_transfers.end();)
|
|
{
|
|
auto transfer = *it;
|
|
|
|
ensure(transfer->busy && transfer->fake);
|
|
|
|
if (transfer->expected_time > timestamp)
|
|
{
|
|
++it;
|
|
continue;
|
|
}
|
|
|
|
transfer->result = transfer->expected_result;
|
|
transfer->count = transfer->expected_count;
|
|
transfer->fake = false;
|
|
transfer->busy = false;
|
|
|
|
send_message(SYS_USBD_TRANSFER_COMPLETE, transfer->transfer_id);
|
|
it = fake_transfers.erase(it); // if we've processed this, then we erase this entry (replacing the iterator with the new reference)
|
|
}
|
|
}
|
|
|
|
// If there is no handled devices usb thread is not actively needed
|
|
if (handled_devices.empty())
|
|
thread_ctrl::wait_for(500'000);
|
|
else
|
|
thread_ctrl::wait_for(1'000);
|
|
}
|
|
}
|
|
|
|
void usb_handler_thread::send_message(u32 message, u32 tr_id)
|
|
{
|
|
add_event(message, tr_id, 0x00);
|
|
}
|
|
|
|
void usb_handler_thread::transfer_complete(struct libusb_transfer* transfer)
|
|
{
|
|
std::lock_guard lock_tf(mutex_transfers);
|
|
|
|
UsbTransfer* usbd_transfer = static_cast<UsbTransfer*>(transfer->user_data);
|
|
|
|
if (transfer->status != 0)
|
|
{
|
|
sys_usbd.error("Transfer Error: %d", +transfer->status);
|
|
}
|
|
|
|
switch (transfer->status)
|
|
{
|
|
case LIBUSB_TRANSFER_COMPLETED: usbd_transfer->result = HC_CC_NOERR; break;
|
|
case LIBUSB_TRANSFER_TIMED_OUT: usbd_transfer->result = EHCI_CC_XACT; break;
|
|
case LIBUSB_TRANSFER_OVERFLOW: usbd_transfer->result = EHCI_CC_BABBLE; break;
|
|
case LIBUSB_TRANSFER_NO_DEVICE:
|
|
usbd_transfer->result = EHCI_CC_HALTED;
|
|
for (const auto& dev : usb_devices)
|
|
{
|
|
if (dev->assigned_number == usbd_transfer->assigned_number)
|
|
{
|
|
disconnect_usb_device(dev, true);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case LIBUSB_TRANSFER_ERROR:
|
|
case LIBUSB_TRANSFER_CANCELLED:
|
|
case LIBUSB_TRANSFER_STALL:
|
|
default:
|
|
usbd_transfer->result = EHCI_CC_HALTED;
|
|
break;
|
|
}
|
|
|
|
usbd_transfer->count = transfer->actual_length;
|
|
|
|
for (s32 index = 0; index < transfer->num_iso_packets; index++)
|
|
{
|
|
u8 iso_status;
|
|
switch (transfer->iso_packet_desc[index].status)
|
|
{
|
|
case LIBUSB_TRANSFER_COMPLETED: iso_status = USBD_HC_CC_NOERR; break;
|
|
case LIBUSB_TRANSFER_TIMED_OUT: iso_status = USBD_HC_CC_XACT; break;
|
|
case LIBUSB_TRANSFER_OVERFLOW: iso_status = USBD_HC_CC_BABBLE; break;
|
|
case LIBUSB_TRANSFER_ERROR:
|
|
case LIBUSB_TRANSFER_CANCELLED:
|
|
case LIBUSB_TRANSFER_STALL:
|
|
case LIBUSB_TRANSFER_NO_DEVICE:
|
|
default: iso_status = USBD_HC_CC_MISSMF; break;
|
|
}
|
|
|
|
usbd_transfer->iso_request.packets[index] = ((iso_status & 0xF) << 12 | (transfer->iso_packet_desc[index].actual_length & 0xFFF));
|
|
}
|
|
|
|
if (transfer->type == LIBUSB_TRANSFER_TYPE_CONTROL && usbd_transfer->control_destbuf)
|
|
{
|
|
memcpy(usbd_transfer->control_destbuf, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, transfer->actual_length);
|
|
usbd_transfer->control_destbuf = nullptr;
|
|
}
|
|
|
|
usbd_transfer->busy = false;
|
|
|
|
send_message(SYS_USBD_TRANSFER_COMPLETE, usbd_transfer->transfer_id);
|
|
|
|
sys_usbd.trace("Transfer complete(0x%x): %s", usbd_transfer->transfer_id, *transfer);
|
|
}
|
|
|
|
bool usb_handler_thread::add_ldd(std::string_view product, u16 id_vendor, u16 id_product_min, u16 id_product_max)
|
|
{
|
|
if (ldds.try_emplace(std::string(product), UsbLdd{id_vendor, id_product_min, id_product_max}).second)
|
|
{
|
|
for (const auto& dev : usb_devices)
|
|
{
|
|
if (dev->assigned_number)
|
|
continue;
|
|
|
|
if (dev->device._device.idVendor == id_vendor && dev->device._device.idProduct >= id_product_min && dev->device._device.idProduct <= id_product_max)
|
|
{
|
|
connect_usb_device(dev);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool usb_handler_thread::remove_ldd(std::string_view product)
|
|
{
|
|
if (const auto iterator = ldds.find(product); iterator != ldds.end())
|
|
{
|
|
for (const auto& dev : usb_devices)
|
|
{
|
|
if (!dev->assigned_number)
|
|
continue;
|
|
|
|
if (dev->device._device.idVendor == iterator->second.id_vendor && dev->device._device.idProduct >= iterator->second.id_product_min && dev->device._device.idProduct <= iterator->second.id_product_max)
|
|
{
|
|
disconnect_usb_device(dev);
|
|
}
|
|
}
|
|
|
|
ldds.erase(iterator);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
u32 usb_handler_thread::open_pipe(u32 device_handle, u8 endpoint)
|
|
{
|
|
open_pipes.emplace(pipe_counter, UsbPipe{handled_devices[device_handle].second, endpoint});
|
|
return pipe_counter++;
|
|
}
|
|
|
|
bool usb_handler_thread::close_pipe(u32 pipe_id)
|
|
{
|
|
return open_pipes.erase(pipe_id) != 0;
|
|
}
|
|
|
|
bool usb_handler_thread::is_pipe(u32 pipe_id) const
|
|
{
|
|
return open_pipes.count(pipe_id) != 0;
|
|
}
|
|
|
|
const UsbPipe& usb_handler_thread::get_pipe(u32 pipe_id) const
|
|
{
|
|
return ::at32(open_pipes, pipe_id);
|
|
}
|
|
|
|
bool usb_handler_thread::get_event(vm::ptr<u64>& arg1, vm::ptr<u64>& arg2, vm::ptr<u64>& arg3)
|
|
{
|
|
if (!usbd_events.empty())
|
|
{
|
|
const auto& usb_event = usbd_events.front();
|
|
*arg1 = std::get<0>(usb_event);
|
|
*arg2 = std::get<1>(usb_event);
|
|
*arg3 = std::get<2>(usb_event);
|
|
usbd_events.pop();
|
|
sys_usbd.trace("Received event: arg1=0x%x arg2=0x%x arg3=0x%x", *arg1, *arg2, *arg3);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void usb_handler_thread::add_event(u64 arg1, u64 arg2, u64 arg3)
|
|
{
|
|
// sys_usbd events use an internal event queue with SYS_SYNC_PRIORITY protocol
|
|
std::lock_guard lock_sq(mutex_sq);
|
|
|
|
if (const auto cpu = lv2_obj::schedule<ppu_thread>(sq, SYS_SYNC_PRIORITY))
|
|
{
|
|
sys_usbd.trace("Sending event(queue): arg1=0x%x arg2=0x%x arg3=0x%x", arg1, arg2, arg3);
|
|
cpu->gpr[4] = arg1;
|
|
cpu->gpr[5] = arg2;
|
|
cpu->gpr[6] = arg3;
|
|
lv2_obj::awake(cpu);
|
|
}
|
|
else
|
|
{
|
|
sys_usbd.trace("Sending event: arg1=0x%x arg2=0x%x arg3=0x%x", arg1, arg2, arg3);
|
|
usbd_events.emplace(arg1, arg2, arg3);
|
|
}
|
|
}
|
|
|
|
u32 usb_handler_thread::get_free_transfer_id()
|
|
{
|
|
u32 num_loops = 0;
|
|
do
|
|
{
|
|
num_loops++;
|
|
transfer_counter++;
|
|
|
|
if (transfer_counter >= MAX_SYS_USBD_TRANSFERS)
|
|
{
|
|
transfer_counter = 0;
|
|
}
|
|
|
|
if (num_loops > MAX_SYS_USBD_TRANSFERS)
|
|
{
|
|
sys_usbd.fatal("Usb transfers are saturated!");
|
|
}
|
|
} while (transfers[transfer_counter].busy);
|
|
|
|
return transfer_counter;
|
|
}
|
|
|
|
UsbTransfer& usb_handler_thread::get_transfer(u32 transfer_id)
|
|
{
|
|
return transfers[transfer_id];
|
|
}
|
|
|
|
std::pair<u32, UsbTransfer&> usb_handler_thread::get_free_transfer()
|
|
{
|
|
std::lock_guard lock_tf(mutex_transfers);
|
|
|
|
u32 transfer_id = get_free_transfer_id();
|
|
auto& transfer = get_transfer(transfer_id);
|
|
transfer.busy = true;
|
|
|
|
return {transfer_id, transfer};
|
|
}
|
|
|
|
std::pair<u32, u32> usb_handler_thread::get_transfer_status(u32 transfer_id)
|
|
{
|
|
std::lock_guard lock_tf(mutex_transfers);
|
|
|
|
const auto& transfer = get_transfer(transfer_id);
|
|
|
|
return {transfer.result, transfer.count};
|
|
}
|
|
|
|
std::pair<u32, UsbDeviceIsoRequest> usb_handler_thread::get_isochronous_transfer_status(u32 transfer_id)
|
|
{
|
|
std::lock_guard lock_tf(mutex_transfers);
|
|
|
|
const auto& transfer = get_transfer(transfer_id);
|
|
|
|
return {transfer.result, transfer.iso_request};
|
|
}
|
|
|
|
void usb_handler_thread::push_fake_transfer(UsbTransfer* transfer)
|
|
{
|
|
std::lock_guard lock_tf(mutex_transfers);
|
|
fake_transfers.push_back(transfer);
|
|
}
|
|
|
|
const std::array<u8, 7>& usb_handler_thread::get_new_location()
|
|
{
|
|
location[0]++;
|
|
return location;
|
|
}
|
|
|
|
void usb_handler_thread::connect_usb_device(std::shared_ptr<usb_device> dev, bool update_usb_devices)
|
|
{
|
|
if (update_usb_devices)
|
|
usb_devices.push_back(dev);
|
|
|
|
for (const auto& [name, ldd] : ldds)
|
|
{
|
|
if (dev->device._device.idVendor == ldd.id_vendor && dev->device._device.idProduct >= ldd.id_product_min && dev->device._device.idProduct <= ldd.id_product_max)
|
|
{
|
|
if (!dev->open_device())
|
|
{
|
|
sys_usbd.error("Failed to open USB device(VID=0x%04x, PID=0x%04x) for LDD <%s>", dev->device._device.idVendor, dev->device._device.idProduct, name);
|
|
disconnect_usb_device(dev);
|
|
return;
|
|
}
|
|
|
|
dev->read_descriptors();
|
|
dev->assigned_number = dev_counter++; // assign current dev_counter, and atomically increment0
|
|
handled_devices.emplace(dev->assigned_number, std::pair(UsbInternalDevice{0x00, narrow<u8>(dev->assigned_number), 0x02, 0x40}, dev));
|
|
send_message(SYS_USBD_ATTACH, dev->assigned_number);
|
|
sys_usbd.success("USB device(VID=0x%04x, PID=0x%04x) matches up with LDD <%s>, assigned as handled_device=0x%x", dev->device._device.idVendor, dev->device._device.idProduct, name, dev->assigned_number);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void usb_handler_thread::disconnect_usb_device(std::shared_ptr<usb_device> dev, bool update_usb_devices)
|
|
{
|
|
if (dev->assigned_number && handled_devices.erase(dev->assigned_number))
|
|
{
|
|
send_message(SYS_USBD_DETACH, dev->assigned_number);
|
|
sys_usbd.success("USB device(VID=0x%04x, PID=0x%04x) unassigned, handled_device=0x%x", dev->device._device.idVendor, dev->device._device.idProduct, dev->assigned_number);
|
|
dev->assigned_number = 0;
|
|
std::erase_if(open_pipes, [&](const auto& val)
|
|
{
|
|
return val.second.device == dev;
|
|
});
|
|
}
|
|
if (update_usb_devices)
|
|
{
|
|
std::erase_if(usb_devices, [&](const auto& val)
|
|
{
|
|
return val == dev;
|
|
});
|
|
}
|
|
}
|
|
|
|
void connect_usb_controller(u8 index, input::product_type type)
|
|
{
|
|
auto usbh = g_fxo->try_get<named_thread<usb_handler_thread>>();
|
|
if (!usbh)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool already_connected = false;
|
|
|
|
if (const auto it = usbh->pad_to_usb.find(index); it != usbh->pad_to_usb.end())
|
|
{
|
|
if (it->second.first == type)
|
|
{
|
|
already_connected = true;
|
|
}
|
|
else
|
|
{
|
|
usbh->disconnect_usb_device(it->second.second, true);
|
|
usbh->pad_to_usb.erase(it->first);
|
|
}
|
|
}
|
|
|
|
if (!already_connected)
|
|
{
|
|
switch (type)
|
|
{
|
|
case input::product_type::guncon_3:
|
|
{
|
|
if (!g_cfg_guncon3.load())
|
|
{
|
|
sys_usbd.notice("Could not load GunCon3 config. Using defaults.");
|
|
}
|
|
|
|
sys_usbd.success("Adding emulated GunCon3 (controller %d)", index);
|
|
std::shared_ptr<usb_device> dev = std::make_shared<usb_device_guncon3>(index, usbh->get_new_location());
|
|
usbh->connect_usb_device(dev, true);
|
|
usbh->pad_to_usb.emplace(index, std::pair(type, dev));
|
|
|
|
sys_usbd.notice("GunCon3 config=\n", g_cfg_guncon3.to_string());
|
|
break;
|
|
}
|
|
case input::product_type::top_shot_elite:
|
|
{
|
|
if (!g_cfg_topshotelite.load())
|
|
{
|
|
sys_usbd.notice("Could not load Top Shot Elite config. Using defaults.");
|
|
}
|
|
|
|
sys_usbd.success("Adding emulated Top Shot Elite (controller %d)", index);
|
|
std::shared_ptr<usb_device> dev = std::make_shared<usb_device_topshotelite>(index, usbh->get_new_location());
|
|
usbh->connect_usb_device(dev, true);
|
|
usbh->pad_to_usb.emplace(index, std::pair(type, dev));
|
|
|
|
sys_usbd.notice("Top shot elite config=\n", g_cfg_topshotelite.to_string());
|
|
break;
|
|
}
|
|
case input::product_type::top_shot_fearmaster:
|
|
{
|
|
if (!g_cfg_topshotfearmaster.load())
|
|
{
|
|
sys_usbd.notice("Could not load Top Shot Fearmaster config. Using defaults.");
|
|
}
|
|
|
|
sys_usbd.success("Adding emulated Top Shot Fearmaster (controller %d)", index);
|
|
std::shared_ptr<usb_device> dev = std::make_shared<usb_device_topshotfearmaster>(index, usbh->get_new_location());
|
|
usbh->connect_usb_device(dev, true);
|
|
usbh->pad_to_usb.emplace(index, std::pair(type, dev));
|
|
|
|
sys_usbd.notice("Top shot fearmaster config=\n", g_cfg_topshotfearmaster.to_string());
|
|
break;
|
|
}
|
|
case input::product_type::udraw_gametablet:
|
|
{
|
|
sys_usbd.success("Adding emulated uDraw GameTablet (controller %d)", index);
|
|
std::shared_ptr<usb_device> dev = std::make_shared<usb_device_gametablet>(index, usbh->get_new_location());
|
|
usbh->connect_usb_device(dev, true);
|
|
usbh->pad_to_usb.emplace(index, std::pair(type, dev));
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void handle_hotplug_event(bool connected)
|
|
{
|
|
if (auto usbh = g_fxo->try_get<named_thread<usb_handler_thread>>())
|
|
{
|
|
usbh->usb_hotplug_timeout = get_system_time() + (connected ? 1'000'000ull : 0);
|
|
}
|
|
}
|
|
|
|
|
|
error_code sys_usbd_initialize(ppu_thread& ppu, vm::ptr<u32> handle)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.warning("sys_usbd_initialize(handle=*0x%x)", handle);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
{
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
// Must not occur (lv2 allows multiple handles, cellUsbd does not)
|
|
ensure(!usbh.is_init.exchange(true));
|
|
}
|
|
|
|
ppu.check_state();
|
|
*handle = 0x115B;
|
|
|
|
// TODO
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_finalize(ppu_thread& ppu, u32 handle)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.warning("sys_usbd_finalize(handle=0x%x)", handle);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
usbh.is_init = false;
|
|
|
|
// Forcefully awake all waiters
|
|
while (auto cpu = lv2_obj::schedule<ppu_thread>(usbh.sq, SYS_SYNC_FIFO))
|
|
{
|
|
// Special ternimation signal value
|
|
cpu->gpr[4] = 4;
|
|
cpu->gpr[5] = 0;
|
|
cpu->gpr[6] = 0;
|
|
lv2_obj::awake(cpu);
|
|
}
|
|
|
|
// TODO
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_get_device_list(ppu_thread& ppu, u32 handle, vm::ptr<UsbInternalDevice> device_list, u32 max_devices)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.warning("sys_usbd_get_device_list(handle=0x%x, device_list=*0x%x, max_devices=0x%x)", handle, device_list, max_devices);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
if (!usbh.is_init)
|
|
return CELL_EINVAL;
|
|
|
|
// TODO: was std::min<s32>
|
|
u32 i_tocopy = std::min<u32>(max_devices, ::size32(usbh.handled_devices));
|
|
|
|
for (u32 index = 0; index < i_tocopy; index++)
|
|
{
|
|
device_list[index] = usbh.handled_devices[index].first;
|
|
}
|
|
|
|
return not_an_error(i_tocopy);
|
|
}
|
|
|
|
error_code sys_usbd_register_extra_ldd(ppu_thread& ppu, u32 handle, vm::cptr<char> s_product, u16 slen_product, u16 id_vendor, u16 id_product_min, u16 id_product_max)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.trace("sys_usbd_register_extra_ldd(handle=0x%x, s_product=%s, slen_product=%d, id_vendor=0x%04x, id_product_min=0x%04x, id_product_max=0x%04x)", handle, s_product, slen_product, id_vendor, id_product_min, id_product_max);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
if (!usbh.is_init)
|
|
return CELL_EINVAL;
|
|
|
|
std::string_view product{s_product.get_ptr(), slen_product};
|
|
|
|
if (usbh.add_ldd(product, id_vendor, id_product_min, id_product_max))
|
|
return CELL_OK;
|
|
|
|
return CELL_EEXIST;
|
|
}
|
|
|
|
error_code sys_usbd_unregister_extra_ldd(ppu_thread& ppu, u32 handle, vm::cptr<char> s_product, u16 slen_product)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.trace("sys_usbd_unregister_extra_ldd(handle=0x%x, s_product=%s, slen_product=%d)", handle, s_product, slen_product);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
if (!usbh.is_init)
|
|
return CELL_EINVAL;
|
|
|
|
std::string_view product{s_product.get_ptr(), slen_product};
|
|
|
|
if (usbh.remove_ldd(product))
|
|
return CELL_OK;
|
|
|
|
return CELL_ESRCH;
|
|
}
|
|
|
|
error_code sys_usbd_get_descriptor_size(ppu_thread& ppu, u32 handle, u32 device_handle)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.trace("sys_usbd_get_descriptor_size(handle=0x%x, deviceNumber=0x%x)", handle, device_handle);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
if (!usbh.is_init || !usbh.handled_devices.count(device_handle))
|
|
{
|
|
return CELL_EINVAL;
|
|
}
|
|
|
|
return not_an_error(usbh.handled_devices[device_handle].second->device.get_size());
|
|
}
|
|
|
|
error_code sys_usbd_get_descriptor(ppu_thread& ppu, u32 handle, u32 device_handle, vm::ptr<void> descriptor, u32 desc_size)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.trace("sys_usbd_get_descriptor(handle=0x%x, deviceNumber=0x%x, descriptor=0x%x, desc_size=0x%x)", handle, device_handle, descriptor, desc_size);
|
|
|
|
if (!descriptor)
|
|
{
|
|
return CELL_EINVAL;
|
|
}
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
if (!usbh.is_init || !usbh.handled_devices.count(device_handle))
|
|
{
|
|
return CELL_EINVAL;
|
|
}
|
|
|
|
if (!desc_size)
|
|
{
|
|
return CELL_ENOMEM;
|
|
}
|
|
|
|
usbh.handled_devices[device_handle].second->device.write_data(reinterpret_cast<u8*>(descriptor.get_ptr()), desc_size);
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
// This function is used for psp(cellUsbPspcm), ps3 arcade usj io(PS3A-USJ), ps2 cam(eyetoy), generic usb camera?(sample_usb2cam)
|
|
error_code sys_usbd_register_ldd(ppu_thread& ppu, u32 handle, vm::cptr<char> s_product, u16 slen_product)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
std::string_view product{s_product.get_ptr(), slen_product};
|
|
|
|
// slightly hacky way of getting Namco GCon3 gun to work.
|
|
// The register_ldd appears to be a more promiscuous mode function, where all device 'inserts' would be presented to the cellUsbd for Probing.
|
|
// Unsure how many more devices might need similar treatment (i.e. just a compare and force VID/PID add), or if it's worth adding a full promiscuous capability
|
|
static const std::unordered_map<std::string, UsbLdd, fmt::string_hash, std::equal_to<>> predefined_ldds
|
|
{
|
|
{"cellUsbPspcm", {0x054C, 0x01CB, 0x01CB}},
|
|
{"guncon3", {0x0B9A, 0x0800, 0x0800}},
|
|
{"PS3A-USJ", {0x0B9A, 0x0900, 0x0910}}
|
|
};
|
|
|
|
if (const auto iterator = predefined_ldds.find(product); iterator != predefined_ldds.end())
|
|
{
|
|
sys_usbd.trace("sys_usbd_register_ldd(handle=0x%x, s_product=%s, slen_product=%d) -> Redirecting to sys_usbd_register_extra_ldd()", handle, s_product, slen_product);
|
|
return sys_usbd_register_extra_ldd(ppu, handle, s_product, slen_product, iterator->second.id_vendor, iterator->second.id_product_min, iterator->second.id_product_max);
|
|
}
|
|
|
|
sys_usbd.todo("sys_usbd_register_ldd(handle=0x%x, s_product=%s, slen_product=%d)", handle, s_product, slen_product);
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_unregister_ldd(ppu_thread& ppu, u32 handle, vm::cptr<char> s_product, u16 slen_product)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.trace("sys_usbd_unregister_ldd(handle=0x%x, s_product=%s, slen_product=%d) -> Redirecting to sys_usbd_unregister_extra_ldd()", handle, s_product, slen_product);
|
|
|
|
return sys_usbd_unregister_extra_ldd(ppu, handle, s_product, slen_product);
|
|
}
|
|
|
|
// TODO: determine what the unknown params are
|
|
// attributes (bmAttributes) : 2=Bulk, 3=Interrupt
|
|
error_code sys_usbd_open_pipe(ppu_thread& ppu, u32 handle, u32 device_handle, u32 unk1, u64 unk2, u64 unk3, u32 endpoint, u64 attributes)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.warning("sys_usbd_open_pipe(handle=0x%x, device_handle=0x%x, unk1=0x%x, unk2=0x%x, unk3=0x%x, endpoint=0x%x, attributes=0x%x)", handle, device_handle, unk1, unk2, unk3, endpoint, attributes);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
if (!usbh.is_init || !usbh.handled_devices.count(device_handle))
|
|
{
|
|
return CELL_EINVAL;
|
|
}
|
|
|
|
return not_an_error(usbh.open_pipe(device_handle, static_cast<u8>(endpoint)));
|
|
}
|
|
|
|
error_code sys_usbd_open_default_pipe(ppu_thread& ppu, u32 handle, u32 device_handle)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.trace("sys_usbd_open_default_pipe(handle=0x%x, device_handle=0x%x)", handle, device_handle);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
if (!usbh.is_init || !usbh.handled_devices.count(device_handle))
|
|
{
|
|
return CELL_EINVAL;
|
|
}
|
|
|
|
return not_an_error(usbh.open_pipe(device_handle, 0));
|
|
}
|
|
|
|
error_code sys_usbd_close_pipe(ppu_thread& ppu, u32 handle, u32 pipe_handle)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.todo("sys_usbd_close_pipe(handle=0x%x, pipe_handle=0x%x)", handle, pipe_handle);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
if (!usbh.is_init || !usbh.is_pipe(pipe_handle))
|
|
{
|
|
return CELL_EINVAL;
|
|
}
|
|
|
|
usbh.close_pipe(pipe_handle);
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
// From RE:
|
|
// In libusbd_callback_thread
|
|
// *arg1 = 4 will terminate CellUsbd libusbd_callback_thread
|
|
// *arg1 = 3 will do some extra processing right away(notification of transfer finishing)
|
|
// *arg1 < 1 || *arg1 > 4 are ignored(rewait instantly for event)
|
|
// *arg1 == 1 || *arg1 == 2 will send a sys_event to internal CellUsbd event queue with same parameters as received and loop(attach and detach event)
|
|
error_code sys_usbd_receive_event(ppu_thread& ppu, u32 handle, vm::ptr<u64> arg1, vm::ptr<u64> arg2, vm::ptr<u64> arg3)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.trace("sys_usbd_receive_event(handle=0x%x, arg1=*0x%x, arg2=*0x%x, arg3=*0x%x)", handle, arg1, arg2, arg3);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
{
|
|
std::lock_guard lock_sq(usbh.mutex_sq);
|
|
|
|
if (!usbh.is_init)
|
|
return CELL_EINVAL;
|
|
|
|
if (usbh.get_event(arg1, arg2, arg3))
|
|
{
|
|
// hack for Guitar Hero Live
|
|
// Attaching the device too fast seems to result in a nullptr along the way
|
|
if (*arg1 == SYS_USBD_ATTACH)
|
|
lv2_obj::sleep(ppu), lv2_obj::wait_timeout(5000);
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
lv2_obj::sleep(ppu);
|
|
lv2_obj::emplace(usbh.sq, &ppu);
|
|
}
|
|
|
|
while (auto state = +ppu.state)
|
|
{
|
|
if (state & cpu_flag::signal && ppu.state.test_and_reset(cpu_flag::signal))
|
|
{
|
|
sys_usbd.trace("Received event(queued): arg1=0x%x arg2=0x%x arg3=0x%x", ppu.gpr[4], ppu.gpr[5], ppu.gpr[6]);
|
|
break;
|
|
}
|
|
|
|
if (is_stopped(state))
|
|
{
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
for (auto cpu = +usbh.sq; cpu; cpu = cpu->next_cpu)
|
|
{
|
|
if (cpu == &ppu)
|
|
{
|
|
ppu.state += cpu_flag::again;
|
|
sys_usbd.trace("sys_usbd_receive_event: aborting");
|
|
return {};
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
ppu.state.wait(state);
|
|
}
|
|
|
|
ppu.check_state();
|
|
*arg1 = ppu.gpr[4];
|
|
*arg2 = ppu.gpr[5];
|
|
*arg3 = ppu.gpr[6];
|
|
|
|
if (*arg1 == SYS_USBD_ATTACH)
|
|
lv2_obj::sleep(ppu), lv2_obj::wait_timeout(5000);
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_detect_event(ppu_thread& ppu)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.todo("sys_usbd_detect_event()");
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_attach(ppu_thread& ppu, u32 handle, u32 unk1, u32 unk2, u32 device_handle)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.todo("sys_usbd_attach(handle=0x%x, unk1=0x%x, unk2=0x%x, device_handle=0x%x)", handle, unk1, unk2, device_handle);
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_transfer_data(ppu_thread& ppu, u32 handle, u32 id_pipe, vm::ptr<u8> buf, u32 buf_size, vm::ptr<UsbDeviceRequest> request, u32 type_transfer)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.trace("sys_usbd_transfer_data(handle=0x%x, id_pipe=0x%x, buf=*0x%x, buf_length=0x%x, request=*0x%x, type=0x%x)", handle, id_pipe, buf, buf_size, request, type_transfer);
|
|
|
|
if (sys_usbd.trace && request)
|
|
{
|
|
sys_usbd.trace("RequestType:0x%02x, Request:0x%02x, wValue:0x%04x, wIndex:0x%04x, wLength:0x%04x", request->bmRequestType, request->bRequest, request->wValue, request->wIndex, request->wLength);
|
|
|
|
if ((request->bmRequestType & 0x80) == 0 && buf && buf_size != 0)
|
|
sys_usbd.trace("Control sent:\n%s", fmt::buf_to_hexstring(buf.get_ptr(), buf_size));
|
|
}
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
if (!usbh.is_init || !usbh.is_pipe(id_pipe))
|
|
{
|
|
return CELL_EINVAL;
|
|
}
|
|
|
|
const auto& pipe = usbh.get_pipe(id_pipe);
|
|
auto&& [transfer_id, transfer] = usbh.get_free_transfer();
|
|
|
|
transfer.assigned_number = pipe.device->assigned_number;
|
|
|
|
// Default endpoint is control endpoint
|
|
if (pipe.endpoint == 0)
|
|
{
|
|
if (!request)
|
|
{
|
|
sys_usbd.error("Tried to use control pipe without proper request pointer");
|
|
return CELL_EINVAL;
|
|
}
|
|
|
|
// Claiming interface
|
|
switch (request->bmRequestType)
|
|
{
|
|
case 0U /*silences warning*/ | LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE:
|
|
{
|
|
switch (request->bRequest)
|
|
{
|
|
case LIBUSB_REQUEST_SET_CONFIGURATION:
|
|
{
|
|
pipe.device->set_configuration(static_cast<u8>(+request->wValue));
|
|
pipe.device->set_interface(0);
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
break;
|
|
}
|
|
case 0U /*silences warning*/ | LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE:
|
|
{
|
|
if (!buf)
|
|
{
|
|
sys_usbd.error("Invalid buffer for control_transfer");
|
|
return CELL_EFAULT;
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
|
|
pipe.device->control_transfer(request->bmRequestType, request->bRequest, request->wValue, request->wIndex, request->wLength, buf_size, buf.get_ptr(), &transfer);
|
|
}
|
|
else
|
|
{
|
|
// If output endpoint
|
|
if (!(pipe.endpoint & 0x80))
|
|
sys_usbd.trace("Write Int(s: %d):\n%s", buf_size, fmt::buf_to_hexstring(buf.get_ptr(), buf_size));
|
|
|
|
pipe.device->interrupt_transfer(buf_size, buf.get_ptr(), pipe.endpoint, &transfer);
|
|
}
|
|
|
|
if (transfer.fake)
|
|
{
|
|
usbh.push_fake_transfer(&transfer);
|
|
}
|
|
|
|
// returns an identifier specific to the transfer
|
|
return not_an_error(transfer_id);
|
|
}
|
|
|
|
error_code sys_usbd_isochronous_transfer_data(ppu_thread& ppu, u32 handle, u32 id_pipe, vm::ptr<UsbDeviceIsoRequest> iso_request)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.todo("sys_usbd_isochronous_transfer_data(handle=0x%x, id_pipe=0x%x, iso_request=*0x%x)", handle, id_pipe, iso_request);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
if (!usbh.is_init || !usbh.is_pipe(id_pipe))
|
|
{
|
|
return CELL_EINVAL;
|
|
}
|
|
|
|
const auto& pipe = usbh.get_pipe(id_pipe);
|
|
auto&& [transfer_id, transfer] = usbh.get_free_transfer();
|
|
|
|
pipe.device->isochronous_transfer(&transfer);
|
|
|
|
// returns an identifier specific to the transfer
|
|
return not_an_error(transfer_id);
|
|
}
|
|
|
|
error_code sys_usbd_get_transfer_status(ppu_thread& ppu, u32 handle, u32 id_transfer, u32 unk1, vm::ptr<u32> result, vm::ptr<u32> count)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.trace("sys_usbd_get_transfer_status(handle=0x%x, id_transfer=0x%x, unk1=0x%x, result=*0x%x, count=*0x%x)", handle, id_transfer, unk1, result, count);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
if (!usbh.is_init)
|
|
return CELL_EINVAL;
|
|
|
|
const auto status = usbh.get_transfer_status(id_transfer);
|
|
*result = status.first;
|
|
*count = status.second;
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_get_isochronous_transfer_status(ppu_thread& ppu, u32 handle, u32 id_transfer, u32 unk1, vm::ptr<UsbDeviceIsoRequest> request, vm::ptr<u32> result)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.todo("sys_usbd_get_isochronous_transfer_status(handle=0x%x, id_transfer=0x%x, unk1=0x%x, request=*0x%x, result=*0x%x)", handle, id_transfer, unk1, request, result);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
if (!usbh.is_init)
|
|
return CELL_EINVAL;
|
|
|
|
const auto status = usbh.get_isochronous_transfer_status(id_transfer);
|
|
|
|
*result = status.first;
|
|
*request = status.second;
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_get_device_location(ppu_thread& ppu, u32 handle, u32 device_handle, vm::ptr<u8> location)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.notice("sys_usbd_get_device_location(handle=0x%x, device_handle=0x%x, location=*0x%x)", handle, device_handle, location);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
if (!usbh.is_init || !usbh.handled_devices.count(device_handle))
|
|
return CELL_EINVAL;
|
|
|
|
usbh.handled_devices[device_handle].second->get_location(location.get_ptr());
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_send_event(ppu_thread& ppu)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.todo("sys_usbd_send_event()");
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_event_port_send(ppu_thread& ppu, u32 handle, u64 arg1, u64 arg2, u64 arg3)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.warning("sys_usbd_event_port_send(handle=0x%x, arg1=0x%x, arg2=0x%x, arg3=0x%x)", handle, arg1, arg2, arg3);
|
|
|
|
auto& usbh = g_fxo->get<named_thread<usb_handler_thread>>();
|
|
|
|
std::lock_guard lock(usbh.mutex);
|
|
|
|
if (!usbh.is_init)
|
|
return CELL_EINVAL;
|
|
|
|
usbh.add_event(arg1, arg2, arg3);
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_allocate_memory(ppu_thread& ppu)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.todo("sys_usbd_allocate_memory()");
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_free_memory(ppu_thread& ppu)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.todo("sys_usbd_free_memory()");
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code sys_usbd_get_device_speed(ppu_thread& ppu)
|
|
{
|
|
ppu.state += cpu_flag::wait;
|
|
|
|
sys_usbd.todo("sys_usbd_get_device_speed()");
|
|
return CELL_OK;
|
|
}
|